Merge "Introduce PrecomputedText version of MessagingTextMessage" into udc-qpr-dev
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 2b5175c..634089b 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -248,6 +248,13 @@
public boolean topActivityEligibleForUserAspectRatioButton;
/**
+ * Whether the user has forced the activity to be fullscreen through the user aspect ratio
+ * settings.
+ * @hide
+ */
+ public boolean isUserFullscreenOverrideEnabled;
+
+ /**
* Hint about the letterbox state of the top activity.
* @hide
*/
@@ -543,7 +550,8 @@
&& isSleeping == that.isSleeping
&& Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
&& parentTaskId == that.parentTaskId
- && Objects.equals(topActivity, that.topActivity);
+ && Objects.equals(topActivity, that.topActivity)
+ && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled;
}
/**
@@ -574,7 +582,8 @@
&& (!hasCompatUI() || configuration.getLayoutDirection()
== that.configuration.getLayoutDirection())
&& (!hasCompatUI() || configuration.uiMode == that.configuration.uiMode)
- && (!hasCompatUI() || isVisible == that.isVisible);
+ && (!hasCompatUI() || isVisible == that.isVisible)
+ && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled;
}
/**
@@ -630,6 +639,7 @@
topActivityLetterboxHorizontalPosition = source.readInt();
topActivityLetterboxWidth = source.readInt();
topActivityLetterboxHeight = source.readInt();
+ isUserFullscreenOverrideEnabled = source.readBoolean();
}
/**
@@ -686,6 +696,7 @@
dest.writeInt(topActivityLetterboxHorizontalPosition);
dest.writeInt(topActivityLetterboxWidth);
dest.writeInt(topActivityLetterboxHeight);
+ dest.writeBoolean(isUserFullscreenOverrideEnabled);
}
@Override
@@ -732,6 +743,7 @@
+ topActivityLetterboxHorizontalPosition
+ " topActivityLetterboxWidth=" + topActivityLetterboxWidth
+ " topActivityLetterboxHeight=" + topActivityLetterboxHeight
+ + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled
+ " locusId=" + mTopActivityLocusId
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " cameraCompatControlState="
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 4b883cd..537e7d4 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2978,6 +2978,8 @@
* <li>{@code requireUserAction} is set to {@link #USER_ACTION_NOT_REQUIRED}.</li>
* <li>The app being installed targets:
* <ul>
+ * <li>{@link android.os.Build.VERSION_CODES#Q API 29} or higher on
+ * Android S ({@link android.os.Build.VERSION_CODES#S API 31})</li>
* <li>{@link android.os.Build.VERSION_CODES#R API 30} or higher on
* Android T ({@link android.os.Build.VERSION_CODES#TIRAMISU API 33})</li>
* <li>{@link android.os.Build.VERSION_CODES#S API 31} or higher <b>after</b>
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 795eb4a..a04c6bb 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -576,6 +576,12 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public static final long DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE = 148086656L;
+ /**
+ * Enable the logic to allow hiding the IME caption bar ("fake" IME navigation bar).
+ * @hide
+ */
+ public static final boolean ENABLE_HIDE_IME_CAPTION_BAR = false;
+
LayoutInflater mInflater;
TypedArray mThemeAttrs;
@UnsupportedAppUsage
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 78388ef..c01664e 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -16,6 +16,8 @@
package android.inputmethodservice;
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
+import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import android.animation.ValueAnimator;
@@ -230,6 +232,16 @@
setIconTintInternal(calculateTargetDarkIntensity(mAppearance,
mDrawLegacyNavigationBarBackground));
+
+ if (ENABLE_HIDE_IME_CAPTION_BAR) {
+ mNavigationBarFrame.setOnApplyWindowInsetsListener((view, insets) -> {
+ if (mNavigationBarFrame != null) {
+ boolean visible = insets.isVisible(captionBar());
+ mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ return view.onApplyWindowInsets(insets);
+ });
+ }
}
private void uninstallNavigationBarFrameIfNecessary() {
@@ -240,6 +252,9 @@
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(mNavigationBarFrame);
}
+ if (ENABLE_HIDE_IME_CAPTION_BAR) {
+ mNavigationBarFrame.setOnApplyWindowInsetsListener(null);
+ }
mNavigationBarFrame = null;
}
@@ -414,7 +429,9 @@
decor.bringChildToFront(mNavigationBarFrame);
}
}
- mNavigationBarFrame.setVisibility(View.VISIBLE);
+ if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+ mNavigationBarFrame.setVisibility(View.VISIBLE);
+ }
}
}
@@ -435,6 +452,11 @@
mShouldShowImeSwitcherWhenImeIsShown;
mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
+ if (ENABLE_HIDE_IME_CAPTION_BAR) {
+ mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
+ .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight());
+ }
+
if (imeDrawsImeNavBar) {
installNavigationBarFrameIfNecessary();
if (mNavigationBarFrame == null) {
@@ -528,6 +550,16 @@
return drawLegacyNavigationBarBackground;
}
+ /**
+ * Returns the height of the IME caption bar if this should be shown, or {@code 0} instead.
+ */
+ private int getImeCaptionBarHeight() {
+ return mImeDrawsImeNavBar
+ ? mService.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_frame_height)
+ : 0;
+ }
+
@Override
public String toDebugString() {
return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4ecfc40..c6d8bd1 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -16,10 +16,12 @@
package android.view;
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
import static android.os.Trace.TRACE_TAG_VIEW;
import static android.view.InsetsControllerProto.CONTROL;
import static android.view.InsetsControllerProto.STATE;
import static android.view.InsetsSource.ID_IME;
+import static android.view.InsetsSource.ID_IME_CAPTION_BAR;
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.WindowInsets.Type.FIRST;
import static android.view.WindowInsets.Type.LAST;
@@ -40,6 +42,7 @@
import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.os.Handler;
@@ -652,6 +655,7 @@
private int mLastWindowingMode;
private boolean mStartingAnimation;
private int mCaptionInsetsHeight = 0;
+ private int mImeCaptionBarInsetsHeight = 0;
private boolean mAnimationsDisabled;
private boolean mCompatSysUiVisibilityStaled;
@@ -693,6 +697,9 @@
if (!CAPTION_ON_SHELL && source1.getType() == captionBar()) {
return;
}
+ if (source1.getId() == ID_IME_CAPTION_BAR) {
+ return;
+ }
// Don't change the indexes of the sources while traversing. Remove it later.
mPendingRemoveIndexes.add(index1);
@@ -823,6 +830,9 @@
if (mFrame.equals(frame)) {
return;
}
+ if (mImeCaptionBarInsetsHeight != 0) {
+ setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight);
+ }
mHost.notifyInsetsChanged();
mFrame.set(frame);
}
@@ -1007,6 +1017,12 @@
// Ensure to update all existing source consumers
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+ if (consumer.getId() == ID_IME_CAPTION_BAR) {
+ // The inset control for the IME caption bar will never be dispatched
+ // by the server.
+ continue;
+ }
+
final InsetsSourceControl control = mTmpControlArray.get(consumer.getId());
if (control != null) {
controllableTypes |= control.getType();
@@ -1499,7 +1515,8 @@
continue;
}
final InsetsSourceControl control = consumer.getControl();
- if (control != null && control.getLeash() != null) {
+ if (control != null
+ && (control.getLeash() != null || control.getId() == ID_IME_CAPTION_BAR)) {
controls.put(control.getId(), new InsetsSourceControl(control));
typesReady |= consumer.getType();
}
@@ -1885,6 +1902,35 @@
}
@Override
+ public void setImeCaptionBarInsetsHeight(int height) {
+ if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+ return;
+ }
+ Rect newFrame = new Rect(mFrame.left, mFrame.bottom - height, mFrame.right, mFrame.bottom);
+ InsetsSource source = mState.peekSource(ID_IME_CAPTION_BAR);
+ if (mImeCaptionBarInsetsHeight != height
+ || (source != null && !newFrame.equals(source.getFrame()))) {
+ mImeCaptionBarInsetsHeight = height;
+ if (mImeCaptionBarInsetsHeight != 0) {
+ mState.getOrCreateSource(ID_IME_CAPTION_BAR, captionBar())
+ .setFrame(newFrame);
+ getSourceConsumer(ID_IME_CAPTION_BAR, captionBar()).setControl(
+ new InsetsSourceControl(ID_IME_CAPTION_BAR, captionBar(),
+ null /* leash */, false /* initialVisible */,
+ new Point(), Insets.NONE),
+ new int[1], new int[1]);
+ } else {
+ mState.removeSource(ID_IME_CAPTION_BAR);
+ InsetsSourceConsumer sourceConsumer = mSourceConsumers.get(ID_IME_CAPTION_BAR);
+ if (sourceConsumer != null) {
+ sourceConsumer.setControl(null, new int[1], new int[1]);
+ }
+ }
+ mHost.notifyInsetsChanged();
+ }
+ }
+
+ @Override
public void setSystemBarsBehavior(@Behavior int behavior) {
mHost.setSystemBarsBehavior(behavior);
}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 6441186..60a67de 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -20,6 +20,7 @@
import static android.view.InsetsSourceProto.TYPE;
import static android.view.InsetsSourceProto.VISIBLE;
import static android.view.InsetsSourceProto.VISIBLE_FRAME;
+import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.ime;
import android.annotation.IntDef;
@@ -47,6 +48,9 @@
/** The insets source ID of IME */
public static final int ID_IME = createId(null, 0, ime());
+ /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */
+ static final int ID_IME_CAPTION_BAR =
+ InsetsSource.createId(null /* owner */, 1 /* index */, captionBar());
/**
* Controls whether this source suppresses the scrim. If the scrim is ignored, the system won't
@@ -215,7 +219,9 @@
// During drag-move and drag-resizing, the caption insets position may not get updated
// before the app frame get updated. To layout the app content correctly during drag events,
// we always return the insets with the corresponding height covering the top.
- if (getType() == WindowInsets.Type.captionBar()) {
+ // However, with the "fake" IME navigation bar treated as a caption bar, we skip this case
+ // to set the actual insets towards the bottom of the screen.
+ if (getType() == WindowInsets.Type.captionBar() && getId() != ID_IME_CAPTION_BAR) {
return Insets.of(0, frame.height(), 0, 0);
}
// Checks for whether there is shared edge with insets for 0-width/height window.
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index e8f62fc..a4cbc52 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -44,6 +44,7 @@
private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
= new ArrayList<>();
private int mCaptionInsetsHeight = 0;
+ private int mImeCaptionBarInsetsHeight = 0;
private WindowInsetsAnimationControlListener mLoggingListener;
private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
@@ -91,6 +92,11 @@
}
@Override
+ public void setImeCaptionBarInsetsHeight(int height) {
+ mImeCaptionBarInsetsHeight = height;
+ }
+
+ @Override
public void setSystemBarsBehavior(int behavior) {
if (mReplayedInsetsController != null) {
mReplayedInsetsController.setSystemBarsBehavior(behavior);
@@ -168,6 +174,9 @@
if (mCaptionInsetsHeight != 0) {
controller.setCaptionInsetsHeight(mCaptionInsetsHeight);
}
+ if (mImeCaptionBarInsetsHeight != 0) {
+ controller.setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight);
+ }
if (mAnimationsDisabled) {
controller.setAnimationsDisabled(true);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c11f497..e673676 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -47,6 +47,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.gui.DropInputMode;
+import android.gui.StalledTransactionInfo;
import android.hardware.DataSpace;
import android.hardware.HardwareBuffer;
import android.hardware.OverlayProperties;
@@ -292,6 +293,7 @@
long nativeObject, long nativeTpc, TrustedPresentationThresholds thresholds);
private static native void nativeClearTrustedPresentationCallback(long transactionObj,
long nativeObject);
+ private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
/**
* Transforms that can be applied to buffers as they are displayed to a window.
@@ -4363,4 +4365,11 @@
callback.accept(fence);
}
+ /**
+ * @hide
+ */
+ public static StalledTransactionInfo getStalledTransactionInfo(int pid) {
+ return nativeGetStalledTransactionInfo(pid);
+ }
+
}
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index bc0bab7..9028a18 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -250,6 +250,15 @@
void setCaptionInsetsHeight(int height);
/**
+ * Sets the insets height for the IME caption bar, which corresponds to the
+ * "fake" IME navigation bar.
+ *
+ * @param height the insets height of the IME caption bar.
+ * @hide
+ */
+ void setImeCaptionBarInsetsHeight(int height);
+
+ /**
* Controls the behavior of system bars.
*
* @param behavior Determines how the bars behave when being hidden by the application.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c576286..9195509 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1302,7 +1302,7 @@
* ratio or orientation specified in the app manifest.
*
* <p>The aspect ratio compatibility override is exposed to users in device
- * settings. A menu in device settings lists all apps that don't opt out of
+ * settings. A menu in device settings lists all apps that have not opted out of
* the compatibility override. Users select apps from the menu and set the
* app aspect ratio on a per-app basis. Typically, the menu is available
* only on large screen devices.
@@ -1347,11 +1347,11 @@
* Application level
* {@link android.content.pm.PackageManager.Property PackageManager.Property}
* tag that (when set to false) informs the system the app has opted out of the
- * full-screen option of the aspect ratio compatibility override. (For
- * background information about the aspect ratio compatibility override, see
+ * full-screen option of the user aspect ratio compatibility override settings. (For
+ * background information about the user aspect ratio compatibility override, see
* {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE}.)
*
- * <p>When users apply the aspect ratio compatibility override, the orientation
+ * <p>When users apply the full-screen compatibility override, the orientation
* of the activity is forced to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}.
*
* <p>The user override is intended to improve the app experience on devices
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4249253..dbe0338 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -257,6 +257,14 @@
jmethodID onTrustedPresentationChanged;
} gTrustedPresentationCallbackClassInfo;
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+ jfieldID layerName;
+ jfieldID bufferId;
+ jfieldID frameNumber;
+} gStalledTransactionInfoClassInfo;
+
constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
switch (colorMode) {
case ui::ColorMode::DISPLAY_P3:
@@ -2032,6 +2040,29 @@
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeTpc));
}
+static jobject nativeGetStalledTransactionInfo(JNIEnv* env, jclass clazz, jint pid) {
+ std::optional<gui::StalledTransactionInfo> stalledTransactionInfo =
+ SurfaceComposerClient::getStalledTransactionInfo(pid);
+ if (!stalledTransactionInfo) {
+ return nullptr;
+ }
+
+ jobject jStalledTransactionInfo = env->NewObject(gStalledTransactionInfoClassInfo.clazz,
+ gStalledTransactionInfoClassInfo.ctor);
+ if (!jStalledTransactionInfo) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+ return nullptr;
+ }
+
+ env->SetObjectField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.layerName,
+ env->NewStringUTF(String8{stalledTransactionInfo->layerName}));
+ env->SetLongField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.bufferId,
+ static_cast<jlong>(stalledTransactionInfo->bufferId));
+ env->SetLongField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.frameNumber,
+ static_cast<jlong>(stalledTransactionInfo->frameNumber));
+ return jStalledTransactionInfo;
+}
+
// ----------------------------------------------------------------------------
SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env,
@@ -2281,6 +2312,8 @@
{"nativeCreateTpc", "(Landroid/view/SurfaceControl$TrustedPresentationCallback;)J",
(void*)nativeCreateTpc},
{"getNativeTrustedPresentationCallbackFinalizer", "()J", (void*)getNativeTrustedPresentationCallbackFinalizer },
+ {"nativeGetStalledTransactionInfo", "(I)Landroid/gui/StalledTransactionInfo;",
+ (void*) nativeGetStalledTransactionInfo },
// clang-format on
};
@@ -2524,6 +2557,18 @@
gTrustedPresentationCallbackClassInfo.onTrustedPresentationChanged =
GetMethodIDOrDie(env, trustedPresentationCallbackClazz, "onTrustedPresentationChanged",
"(Z)V");
+
+ jclass stalledTransactionInfoClazz = FindClassOrDie(env, "android/gui/StalledTransactionInfo");
+ gStalledTransactionInfoClassInfo.clazz = MakeGlobalRefOrDie(env, stalledTransactionInfoClazz);
+ gStalledTransactionInfoClassInfo.ctor =
+ GetMethodIDOrDie(env, stalledTransactionInfoClazz, "<init>", "()V");
+ gStalledTransactionInfoClassInfo.layerName =
+ GetFieldIDOrDie(env, stalledTransactionInfoClazz, "layerName", "Ljava/lang/String;");
+ gStalledTransactionInfoClassInfo.bufferId =
+ GetFieldIDOrDie(env, stalledTransactionInfoClazz, "bufferId", "J");
+ gStalledTransactionInfoClassInfo.frameNumber =
+ GetFieldIDOrDie(env, stalledTransactionInfoClazz, "frameNumber", "J");
+
return err;
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index cbc6c2a..d86d53f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -45,9 +45,7 @@
<item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
- <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_connected_display</xliff:g></item>
- <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_camera</xliff:g></item>
@@ -56,6 +54,8 @@
<item><xliff:g id="id">@string/status_bar_mute</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_volume</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_zen</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml
new file mode 100644
index 0000000..6e4752c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="@color/compat_controls_background"
+ android:strokeAlpha="0.8"
+ android:fillAlpha="0.8"
+ android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
+ <group
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:fillColor="@color/compat_controls_text"
+ android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml
new file mode 100644
index 0000000..141a1ce
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/compat_background_ripple">
+ <item android:drawable="@drawable/user_aspect_ratio_settings_button"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index dfaeeeb..257fe15 100644
--- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -55,7 +55,7 @@
<include android:id="@+id/size_compat_hint"
android:visibility="gone"
- android:layout_width="@dimen/size_compat_hint_width"
+ android:layout_width="@dimen/compat_hint_width"
android:layout_height="wrap_content"
layout="@layout/compat_mode_hint"/>
diff --git a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
new file mode 100644
index 0000000..433d854
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<com.android.wm.shell.compatui.UserAspectRatioSettingsLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="bottom|end">
+
+ <include android:id="@+id/user_aspect_ratio_settings_hint"
+ android:visibility="gone"
+ android:layout_width="@dimen/compat_hint_width"
+ android:layout_height="wrap_content"
+ layout="@layout/compat_mode_hint"/>
+
+ <ImageButton
+ android:id="@+id/user_aspect_ratio_settings_button"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/compat_button_margin"
+ android:layout_marginBottom="@dimen/compat_button_margin"
+ android:src="@drawable/user_aspect_ratio_settings_button_ripple"
+ android:background="@android:color/transparent"
+ android:contentDescription="@string/user_aspect_ratio_settings_button_description"/>
+
+</com.android.wm.shell.compatui.UserAspectRatioSettingsLayout>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 64fed1c..0502a99 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -260,8 +260,8 @@
+ compat_button_margin - compat_hint_corner_radius - compat_hint_point_width / 2). -->
<dimen name="compat_hint_padding_end">7dp</dimen>
- <!-- The width of the size compat hint. -->
- <dimen name="size_compat_hint_width">188dp</dimen>
+ <!-- The width of the compat hint. -->
+ <dimen name="compat_hint_width">188dp</dimen>
<!-- The width of the camera compat hint. -->
<dimen name="camera_compat_hint_width">143dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index b192fdf..8cbc3d0 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -173,7 +173,13 @@
<string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
<!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
- <string name="restart_button_description">Tap to restart this app for a better view.</string>
+ <string name="restart_button_description">Tap to restart this app for a better view</string>
+
+ <!-- Tooltip text of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] -->
+ <string name="user_aspect_ratio_settings_button_hint">Change this app\'s aspect ratio in Settings</string>
+
+ <!-- Content description of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] -->
+ <string name="user_aspect_ratio_settings_button_description">Change aspect ratio</string>
<!-- Description of the camera compat button for applying stretched issues treatment in the hint for
compatibility control. [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 0998e71..54f8984 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -16,12 +16,17 @@
package com.android.wm.shell.compatui;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -41,7 +46,6 @@
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -104,6 +108,13 @@
private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>();
/**
+ * The active user aspect ratio settings button layout if there is one (there can be at most
+ * one active).
+ */
+ @Nullable
+ private UserAspectRatioSettingsWindowManager mUserAspectRatioSettingsLayout;
+
+ /**
* The active Letterbox Education layout if there is one (there can be at most one active).
*
* <p>An active layout is a layout that is eligible to be shown for the associated task but
@@ -121,38 +132,51 @@
/** Avoid creating display context frequently for non-default display. */
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
+ @NonNull
private final Context mContext;
+ @NonNull
private final ShellController mShellController;
+ @NonNull
private final DisplayController mDisplayController;
+ @NonNull
private final DisplayInsetsController mDisplayInsetsController;
+ @NonNull
private final DisplayImeController mImeController;
+ @NonNull
private final SyncTransactionQueue mSyncQueue;
+ @NonNull
private final ShellExecutor mMainExecutor;
+ @NonNull
private final Lazy<Transitions> mTransitionsLazy;
+ @NonNull
private final DockStateReader mDockStateReader;
+ @NonNull
private final CompatUIConfiguration mCompatUIConfiguration;
// Only show each hint once automatically in the process life.
+ @NonNull
private final CompatUIHintsState mCompatUIHintsState;
+ @NonNull
private final CompatUIShellCommandHandler mCompatUIShellCommandHandler;
- private CompatUICallback mCallback;
+ @Nullable
+ private CompatUICallback mCompatUICallback;
// Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
// be shown.
private boolean mKeyguardShowing;
- public CompatUIController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- DisplayController displayController,
- DisplayInsetsController displayInsetsController,
- DisplayImeController imeController,
- SyncTransactionQueue syncQueue,
- ShellExecutor mainExecutor,
- Lazy<Transitions> transitionsLazy,
- DockStateReader dockStateReader,
- CompatUIConfiguration compatUIConfiguration,
- CompatUIShellCommandHandler compatUIShellCommandHandler) {
+ public CompatUIController(@NonNull Context context,
+ @NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
+ @NonNull DisplayController displayController,
+ @NonNull DisplayInsetsController displayInsetsController,
+ @NonNull DisplayImeController imeController,
+ @NonNull SyncTransactionQueue syncQueue,
+ @NonNull ShellExecutor mainExecutor,
+ @NonNull Lazy<Transitions> transitionsLazy,
+ @NonNull DockStateReader dockStateReader,
+ @NonNull CompatUIConfiguration compatUIConfiguration,
+ @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -175,9 +199,9 @@
mCompatUIShellCommandHandler.onInit();
}
- /** Sets the callback for UI interactions. */
- public void setCompatUICallback(CompatUICallback callback) {
- mCallback = callback;
+ /** Sets the callback for Compat UI interactions. */
+ public void setCompatUICallback(@NonNull CompatUICallback compatUiCallback) {
+ mCompatUICallback = compatUiCallback;
}
/**
@@ -187,7 +211,7 @@
* @param taskInfo {@link TaskInfo} task the activity is in.
* @param taskListener listener to handle the Task Surface placement.
*/
- public void onCompatInfoChanged(TaskInfo taskInfo,
+ public void onCompatInfoChanged(@NonNull TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (taskInfo != null && !taskInfo.topActivityInSizeCompat) {
mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
@@ -203,6 +227,16 @@
createOrUpdateRestartDialogLayout(taskInfo, taskListener);
if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+ // The user aspect ratio button should not be handled when a new TaskInfo is
+ // sent because of a double tap or when in multi-window mode.
+ if (taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ return;
+ }
+ if (!taskInfo.isFromLetterboxDoubleTap) {
+ createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
+ }
}
}
@@ -280,8 +314,8 @@
return mDisplaysWithIme.contains(displayId);
}
- private void createOrUpdateCompatLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateCompatLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId);
if (layout != null) {
if (layout.needsToBeRecreated(taskInfo, taskListener)) {
@@ -314,7 +348,7 @@
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
return new CompatUIWindowManager(context,
- taskInfo, mSyncQueue, mCallback, taskListener,
+ taskInfo, mSyncQueue, mCompatUICallback, taskListener,
mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState,
mCompatUIConfiguration, this::onRestartButtonClicked);
}
@@ -328,12 +362,12 @@
mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
onCompatInfoChanged(taskInfoState.first, taskInfoState.second);
} else {
- mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
+ mCompatUICallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
}
}
- private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateLetterboxEduLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mActiveLetterboxEduLayout != null) {
if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
mActiveLetterboxEduLayout.release();
@@ -377,8 +411,8 @@
mDockStateReader, mCompatUIConfiguration);
}
- private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateRestartDialogLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
RestartDialogWindowManager layout =
mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
if (layout != null) {
@@ -423,7 +457,7 @@
private void onRestartDialogCallback(
Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId);
- mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
+ mCompatUICallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
}
private void onRestartDialogDismissCallback(
@@ -432,8 +466,8 @@
onCompatInfoChanged(stateInfo.first, stateInfo.second);
}
- private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mActiveReachabilityEduLayout != null) {
if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
mActiveReachabilityEduLayout.release();
@@ -474,14 +508,67 @@
ShellTaskOrganizer.TaskListener taskListener) {
return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue,
taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
- mCompatUIConfiguration, mMainExecutor);
+ mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed);
}
+ private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo,
+ @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+ // We need to update the UI otherwise it will not be shown until the user relaunches the app
+ createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
+ }
+
+ private void createOrUpdateUserAspectRatioSettingsLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ if (mUserAspectRatioSettingsLayout != null) {
+ if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ } else {
+ // UI already exists, update the UI layout.
+ if (!mUserAspectRatioSettingsLayout.updateCompatInfo(taskInfo, taskListener,
+ showOnDisplay(mUserAspectRatioSettingsLayout.getDisplayId()))) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ }
+ return;
+ }
+ }
+
+ // Create a new UI layout.
+ final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+ if (context == null) {
+ return;
+ }
+ final UserAspectRatioSettingsWindowManager newLayout =
+ createUserAspectRatioSettingsWindowManager(context, taskInfo, taskListener);
+ if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) {
+ // The new layout is eligible to be shown, add it the active layouts.
+ mUserAspectRatioSettingsLayout = newLayout;
+ }
+ }
+
+ @VisibleForTesting
+ @NonNull
+ UserAspectRatioSettingsWindowManager createUserAspectRatioSettingsWindowManager(
+ @NonNull Context context, @NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ return new UserAspectRatioSettingsWindowManager(context, taskInfo, mSyncQueue,
+ taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
+ mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor);
+ }
+
+ private void launchUserAspectRatioSettings(
+ @NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+ final Intent intent = new Intent(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+ }
private void removeLayouts(int taskId) {
- final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
- if (layout != null) {
- layout.release();
+ final CompatUIWindowManager compatLayout = mActiveCompatLayouts.get(taskId);
+ if (compatLayout != null) {
+ compatLayout.release();
mActiveCompatLayouts.remove(taskId);
}
@@ -502,6 +589,12 @@
mActiveReachabilityEduLayout.release();
mActiveReachabilityEduLayout = null;
}
+
+ if (mUserAspectRatioSettingsLayout != null
+ && mUserAspectRatioSettingsLayout.getTaskId() == taskId) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ }
}
private Context getOrCreateDisplayContext(int displayId) {
@@ -557,6 +650,10 @@
if (mActiveReachabilityEduLayout != null && condition.test(mActiveReachabilityEduLayout)) {
callback.accept(mActiveReachabilityEduLayout);
}
+ if (mUserAspectRatioSettingsLayout != null && condition.test(
+ mUserAspectRatioSettingsLayout)) {
+ callback.accept(mUserAspectRatioSettingsLayout);
+ }
}
/** An implementation of {@link OnInsetsChangedListener} for a given display id. */
@@ -591,4 +688,14 @@
insetsChanged(insetsState);
}
}
+
+ /**
+ * A class holding the state of the compat UI hints, which is shared between all compat UI
+ * window managers.
+ */
+ static class CompatUIHintsState {
+ boolean mHasShownSizeCompatHint;
+ boolean mHasShownCameraCompatHint;
+ boolean mHasShownUserAspectRatioSettingsButtonHint;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 065806d..ce3c509 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -38,6 +38,7 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import java.util.function.Consumer;
@@ -235,15 +236,4 @@
return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN
&& mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED;
}
-
- /**
- * A class holding the state of the compat UI hints, which is shared between all compat UI
- * window managers.
- */
- static class CompatUIHintsState {
- @VisibleForTesting
- boolean mHasShownSizeCompatHint;
- @VisibleForTesting
- boolean mHasShownCameraCompatHint;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index 95bb1fe..9de3f9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -36,6 +36,8 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import java.util.function.BiConsumer;
+
/**
* Window manager for the reachability education
*/
@@ -73,6 +75,8 @@
// we need to animate them.
private boolean mHasLetterboxSizeChanged;
+ private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback;
+
@Nullable
@VisibleForTesting
ReachabilityEduLayout mLayout;
@@ -80,7 +84,8 @@
ReachabilityEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
- CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor) {
+ CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor,
+ BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
@@ -89,6 +94,7 @@
mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
mCompatUIConfiguration = compatUIConfiguration;
mMainExecutor = mainExecutor;
+ mOnDismissCallback = onDismissCallback;
}
@Override
@@ -217,13 +223,17 @@
return;
}
final TaskInfo lastTaskInfo = getLastTaskInfo();
+ final boolean hasSeenHorizontalReachabilityEdu =
+ mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo);
+ final boolean hasSeenVerticalReachabilityEdu =
+ mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo);
final boolean eligibleForDisplayHorizontalEducation = mForceUpdate
- || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo)
+ || !hasSeenHorizontalReachabilityEdu
|| (mHasUserDoubleTapped
&& (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION
|| mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
final boolean eligibleForDisplayVerticalEducation = mForceUpdate
- || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo)
+ || !hasSeenVerticalReachabilityEdu
|| (mHasUserDoubleTapped
&& (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION
|| mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
@@ -239,6 +249,14 @@
if (!mHasLetterboxSizeChanged) {
updateHideTime();
mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS);
+ // If reachability education has been seen for the first time, trigger callback to
+ // display aspect ratio settings button once reachability education disappears
+ if (hasShownHorizontalReachabilityEduFirstTime(hasSeenHorizontalReachabilityEdu)
+ || hasShownVerticalReachabilityEduFirstTime(
+ hasSeenVerticalReachabilityEdu)) {
+ mMainExecutor.executeDelayed(this::triggerOnDismissCallback,
+ DISAPPEAR_DELAY_MS);
+ }
}
mHasUserDoubleTapped = false;
} else {
@@ -246,6 +264,38 @@
}
}
+ /**
+ * Compares the value of
+ * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} before and after the
+ * layout is shown. Horizontal reachability education is considered seen for the first time if
+ * prior to viewing the layout,
+ * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} is {@code false}
+ * but becomes {@code true} once the current layout is shown.
+ */
+ private boolean hasShownHorizontalReachabilityEduFirstTime(
+ boolean previouslyShownHorizontalReachabilityEducation) {
+ return !previouslyShownHorizontalReachabilityEducation
+ && mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(getLastTaskInfo());
+ }
+
+ /**
+ * Compares the value of
+ * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} before and after the
+ * layout is shown. Horizontal reachability education is considered seen for the first time if
+ * prior to viewing the layout,
+ * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} is {@code false}
+ * but becomes {@code true} once the current layout is shown.
+ */
+ private boolean hasShownVerticalReachabilityEduFirstTime(
+ boolean previouslyShownVerticalReachabilityEducation) {
+ return !previouslyShownVerticalReachabilityEducation
+ && mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(getLastTaskInfo());
+ }
+
+ private void triggerOnDismissCallback() {
+ mOnDismissCallback.accept(getLastTaskInfo(), getTaskListener());
+ }
+
private void hideReachability() {
if (mLayout == null || !shouldHideEducation()) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
new file mode 100644
index 0000000..5eeb3b6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Layout for the user aspect ratio button which opens the app list page in settings
+ * and allows users to change apps aspect ratio.
+ */
+public class UserAspectRatioSettingsLayout extends LinearLayout {
+
+ private static final float ALPHA_FULL_TRANSPARENT = 0f;
+
+ private static final float ALPHA_FULL_OPAQUE = 1f;
+
+ private static final long VISIBILITY_ANIMATION_DURATION_MS = 50;
+
+ private static final String ALPHA_PROPERTY_NAME = "alpha";
+
+ private UserAspectRatioSettingsWindowManager mWindowManager;
+
+ public UserAspectRatioSettingsLayout(Context context) {
+ this(context, null);
+ }
+
+ public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ void inject(@NonNull UserAspectRatioSettingsWindowManager windowManager) {
+ mWindowManager = windowManager;
+ }
+
+ void setUserAspectRatioSettingsHintVisibility(boolean show) {
+ setViewVisibility(R.id.user_aspect_ratio_settings_hint, show);
+ }
+
+ void setUserAspectRatioButtonVisibility(boolean show) {
+ setViewVisibility(R.id.user_aspect_ratio_settings_button, show);
+ // Hint should never be visible without button.
+ if (!show) {
+ setUserAspectRatioSettingsHintVisibility(/* show= */ false);
+ }
+ }
+
+ private void setViewVisibility(@IdRes int resId, boolean show) {
+ final View view = findViewById(resId);
+ int visibility = show ? View.VISIBLE : View.GONE;
+ if (view.getVisibility() == visibility) {
+ return;
+ }
+ if (show) {
+ showItem(view);
+ } else {
+ view.setVisibility(visibility);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ // Need to relayout after changes like hiding / showing a hint since they affect size.
+ // Doing this directly in setUserAspectRatioButtonVisibility can result in flaky animation.
+ mWindowManager.relayout();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ final ImageButton userAspectRatioButton =
+ findViewById(R.id.user_aspect_ratio_settings_button);
+ userAspectRatioButton.setOnClickListener(
+ view -> mWindowManager.onUserAspectRatioSettingsButtonClicked());
+ userAspectRatioButton.setOnLongClickListener(view -> {
+ mWindowManager.onUserAspectRatioSettingsButtonLongClicked();
+ return true;
+ });
+
+ final LinearLayout sizeCompatHint = findViewById(R.id.user_aspect_ratio_settings_hint);
+ ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text))
+ .setText(R.string.user_aspect_ratio_settings_button_hint);
+ sizeCompatHint.setOnClickListener(
+ view -> setUserAspectRatioSettingsHintVisibility(/* show= */ false));
+ }
+
+ private void showItem(@NonNull View view) {
+ view.setVisibility(View.VISIBLE);
+ final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+ ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE);
+ fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS);
+ fadeIn.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.VISIBLE);
+ }
+ });
+ fadeIn.start();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
new file mode 100644
index 0000000..bd53dc7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Window manager for the user aspect ratio settings button which allows users to go to
+ * app settings and change apps aspect ratio.
+ */
+class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract {
+
+ private static final long SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 500L;
+
+ private static final long HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 4000L;
+
+ private long mNextButtonHideTimeMs = -1L;
+
+ private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnButtonClicked;
+
+ private final ShellExecutor mShellExecutor;
+
+ @VisibleForTesting
+ @NonNull
+ final CompatUIHintsState mCompatUIHintsState;
+
+ @Nullable
+ private UserAspectRatioSettingsLayout mLayout;
+
+ // Remember the last reported states in case visibility changes due to keyguard or IME updates.
+ @VisibleForTesting
+ boolean mHasUserAspectRatioSettingsButton;
+
+ UserAspectRatioSettingsWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo,
+ @NonNull SyncTransactionQueue syncQueue,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener,
+ @NonNull DisplayLayout displayLayout, @NonNull CompatUIHintsState compatUIHintsState,
+ @NonNull BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onButtonClicked,
+ @NonNull ShellExecutor shellExecutor) {
+ super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ mShellExecutor = shellExecutor;
+ mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+ mCompatUIHintsState = compatUIHintsState;
+ mOnButtonClicked = onButtonClicked;
+ }
+
+ @Override
+ protected int getZOrder() {
+ return TASK_CHILD_LAYER_COMPAT_UI + 1;
+ }
+
+ @Override
+ protected @Nullable View getLayout() {
+ return mLayout;
+ }
+
+ @Override
+ protected void removeLayout() {
+ mLayout = null;
+ }
+
+ @Override
+ protected boolean eligibleToShowLayout() {
+ return mHasUserAspectRatioSettingsButton;
+ }
+
+ @Override
+ protected View createLayout() {
+ mLayout = inflateLayout();
+ mLayout.inject(this);
+
+ updateVisibilityOfViews();
+
+ return mLayout;
+ }
+
+ @VisibleForTesting
+ UserAspectRatioSettingsLayout inflateLayout() {
+ return (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.user_aspect_ratio_settings_layout, null);
+ }
+
+ @Override
+ public boolean updateCompatInfo(@NonNull TaskInfo taskInfo,
+ @NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
+ final boolean prevHasUserAspectRatioSettingsButton = mHasUserAspectRatioSettingsButton;
+ mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+
+ if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
+ return false;
+ }
+
+ if (prevHasUserAspectRatioSettingsButton != mHasUserAspectRatioSettingsButton) {
+ updateVisibilityOfViews();
+ }
+ return true;
+ }
+
+ /** Called when the user aspect ratio settings button is clicked. */
+ void onUserAspectRatioSettingsButtonClicked() {
+ mOnButtonClicked.accept(getLastTaskInfo(), getTaskListener());
+ }
+
+ /** Called when the user aspect ratio settings button is long clicked. */
+ void onUserAspectRatioSettingsButtonLongClicked() {
+ if (mLayout == null) {
+ return;
+ }
+ mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+ mShellExecutor.executeDelayed(this::hideUserAspectRatioButton,
+ HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+ }
+
+ @Override
+ @VisibleForTesting
+ public void updateSurfacePosition() {
+ if (mLayout == null) {
+ return;
+ }
+ // Position of the button in the container coordinate.
+ final Rect taskBounds = getTaskBounds();
+ final Rect taskStableBounds = getTaskStableBounds();
+ final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? taskStableBounds.left - taskBounds.left
+ : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
+ final int positionY = taskStableBounds.bottom - taskBounds.top
+ - mLayout.getMeasuredHeight();
+ updateSurfacePosition(positionX, positionY);
+ }
+
+ @VisibleForTesting
+ void updateVisibilityOfViews() {
+ if (mHasUserAspectRatioSettingsButton) {
+ mShellExecutor.executeDelayed(this::showUserAspectRatioButton,
+ SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+ mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+ mShellExecutor.executeDelayed(this::hideUserAspectRatioButton,
+ HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+ } else {
+ mShellExecutor.removeCallbacks(this::showUserAspectRatioButton);
+ mShellExecutor.execute(this::hideUserAspectRatioButton);
+ }
+ }
+
+ private void showUserAspectRatioButton() {
+ if (mLayout == null) {
+ return;
+ }
+ mLayout.setUserAspectRatioButtonVisibility(true);
+ // Only show by default for the first time.
+ if (!mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint) {
+ mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+ }
+ }
+
+ private void hideUserAspectRatioButton() {
+ if (mLayout == null || !isHideDelayReached(mNextButtonHideTimeMs)) {
+ return;
+ }
+ mLayout.setUserAspectRatioButtonVisibility(false);
+ }
+
+ private boolean isHideDelayReached(long nextHideTime) {
+ return SystemClock.uptimeMillis() >= nextHideTime;
+ }
+
+ private long updateHideTime(long hideDelay) {
+ return SystemClock.uptimeMillis() + hideDelay;
+ }
+
+ private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
+ return taskInfo.topActivityEligibleForUserAspectRatioButton
+ && (taskInfo.topActivityBoundsLetterboxed
+ || taskInfo.isUserFullscreenOverrideEnabled);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index a6501f0..efc69ebd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -58,6 +58,8 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,8 +68,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import dagger.Lazy;
-
/**
* Tests for {@link CompatUIController}.
*
@@ -82,21 +82,36 @@
private CompatUIController mController;
private ShellInit mShellInit;
- private @Mock ShellController mMockShellController;
- private @Mock DisplayController mMockDisplayController;
- private @Mock DisplayInsetsController mMockDisplayInsetsController;
- private @Mock DisplayLayout mMockDisplayLayout;
- private @Mock DisplayImeController mMockImeController;
- private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
- private @Mock SyncTransactionQueue mMockSyncQueue;
- private @Mock ShellExecutor mMockExecutor;
- private @Mock Lazy<Transitions> mMockTransitionsLazy;
- private @Mock CompatUIWindowManager mMockCompatLayout;
- private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
- private @Mock RestartDialogWindowManager mMockRestartDialogLayout;
- private @Mock DockStateReader mDockStateReader;
- private @Mock CompatUIConfiguration mCompatUIConfiguration;
- private @Mock CompatUIShellCommandHandler mCompatUIShellCommandHandler;
+ @Mock
+ private ShellController mMockShellController;
+ @Mock
+ private DisplayController mMockDisplayController;
+ @Mock
+ private DisplayInsetsController mMockDisplayInsetsController;
+ @Mock
+ private DisplayLayout mMockDisplayLayout;
+ @Mock
+ private DisplayImeController mMockImeController;
+ @Mock
+ private ShellTaskOrganizer.TaskListener mMockTaskListener;
+ @Mock
+ private SyncTransactionQueue mMockSyncQueue;
+ @Mock
+ private ShellExecutor mMockExecutor;
+ @Mock
+ private Lazy<Transitions> mMockTransitionsLazy;
+ @Mock
+ private CompatUIWindowManager mMockCompatLayout;
+ @Mock
+ private LetterboxEduWindowManager mMockLetterboxEduLayout;
+ @Mock
+ private RestartDialogWindowManager mMockRestartDialogLayout;
+ @Mock
+ private DockStateReader mDockStateReader;
+ @Mock
+ private CompatUIConfiguration mCompatUIConfiguration;
+ @Mock
+ private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 5f294d5..3bce2b8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -44,7 +44,7 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import junit.framework.Assert;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 78c3cbd..4c837e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -53,7 +53,7 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import junit.framework.Assert;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index 973a99c..a802f15a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -40,6 +40,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.BiConsumer;
+
/**
* Tests for {@link ReachabilityEduWindowManager}.
*
@@ -57,6 +59,8 @@
private CompatUIConfiguration mCompatUIConfiguration;
@Mock
private DisplayLayout mDisplayLayout;
+ @Mock
+ private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback;
private TestShellExecutor mExecutor;
private TaskInfo mTaskInfo;
private ReachabilityEduWindowManager mWindowManager;
@@ -104,6 +108,7 @@
private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) {
return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue,
- mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor);
+ mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor,
+ mOnDismissCallback);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
new file mode 100644
index 0000000..1fee153
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
+import android.content.ComponentName;
+import android.testing.AndroidTestingRunner;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.SurfaceControlViewHost;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Tests for {@link UserAspectRatioSettingsLayout}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:UserAspectRatioSettingsLayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
+
+ private static final int TASK_ID = 1;
+
+ @Mock
+ private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock
+ private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener>
+ mOnUserAspectRatioSettingsButtonClicked;
+ @Mock
+ private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock
+ private SurfaceControlViewHost mViewHost;
+ @Captor
+ private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor;
+ @Captor
+ private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor;
+
+ private UserAspectRatioSettingsWindowManager mWindowManager;
+ private UserAspectRatioSettingsLayout mLayout;
+ private TaskInfo mTaskInfo;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
+ mSyncTransactionQueue, mTaskListener, new DisplayLayout(),
+ new CompatUIController.CompatUIHintsState(),
+ mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor());
+
+ mLayout = (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.user_aspect_ratio_settings_layout, null);
+ mLayout.inject(mWindowManager);
+
+ spyOn(mWindowManager);
+ spyOn(mLayout);
+ doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+ doReturn(mLayout).when(mWindowManager).inflateLayout();
+ }
+
+ @Test
+ public void testOnClickForUserAspectRatioSettingsButton() {
+ final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
+ button.performClick();
+
+ verify(mWindowManager).onUserAspectRatioSettingsButtonClicked();
+ verify(mOnUserAspectRatioSettingsButtonClicked).accept(
+ mUserAspectRationTaskInfoCaptor.capture(),
+ mUserAspectRatioTaskListenerCaptor.capture());
+ final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result =
+ new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(),
+ mUserAspectRatioTaskListenerCaptor.getValue());
+ Assert.assertEquals(mTaskInfo, result.first);
+ Assert.assertEquals(mTaskListener, result.second);
+ }
+
+ @Test
+ public void testOnLongClickForUserAspectRatioButton() {
+ doNothing().when(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
+
+ final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
+ button.performLongClick();
+
+ verify(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
+ }
+
+ @Test
+ public void testOnClickForUserAspectRatioSettingsHint() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+ final LinearLayout sizeCompatHint = mLayout.findViewById(
+ R.id.user_aspect_ratio_settings_hint);
+ sizeCompatHint.performClick();
+
+ verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ false);
+ }
+
+ private static TaskInfo createTaskInfo(boolean hasSizeCompat,
+ @CameraCompatControlState int cameraCompatControlState) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = TASK_ID;
+ taskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.cameraCompatControlState = cameraCompatControlState;
+ taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
new file mode 100644
index 0000000..b48538c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.view.WindowInsets.Type.navigationBars;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.util.Pair;
+import android.view.DisplayInfo;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.BiConsumer;
+
+/**
+ * Tests for {@link UserAspectRatioSettingsWindowManager}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:UserAspectRatioSettingsWindowManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
+
+ private static final int TASK_ID = 1;
+
+ @Mock private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock
+ private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener>
+ mOnUserAspectRatioSettingsButtonClicked;
+ @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock private UserAspectRatioSettingsLayout mLayout;
+ @Mock private SurfaceControlViewHost mViewHost;
+ @Captor
+ private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor;
+ @Captor
+ private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor;
+
+ private final Set<String> mPackageNameCache = new HashSet<>();
+
+ private UserAspectRatioSettingsWindowManager mWindowManager;
+ private TaskInfo mTaskInfo;
+
+ private TestShellExecutor mExecutor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mExecutor = new TestShellExecutor();
+ mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ false, /* topActivityBoundsLetterboxed */ true);
+ mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
+ mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mOnUserAspectRatioSettingsButtonClicked, mExecutor);
+ spyOn(mWindowManager);
+ doReturn(mLayout).when(mWindowManager).inflateLayout();
+ doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+ }
+
+ @Test
+ public void testCreateUserAspectRatioButton() {
+ // Doesn't create layout if show is false.
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ false));
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Doesn't create hint popup.
+ mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager).inflateLayout();
+ mExecutor.flushAll();
+ verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true);
+ verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+
+ // Creates hint popup.
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ mWindowManager.release();
+ mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = false;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager).inflateLayout();
+ assertNotNull(mLayout);
+ mExecutor.flushAll();
+ verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true);
+ verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ assertTrue(mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint);
+
+ // Returns false and doesn't create layout if mHasUserAspectRatioSettingsButton is false.
+ clearInvocations(mWindowManager);
+ mWindowManager.release();
+ mWindowManager.mHasUserAspectRatioSettingsButton = false;
+ assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager, never()).inflateLayout();
+ }
+
+ @Test
+ public void testRelease() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+
+ mWindowManager.release();
+
+ verify(mViewHost).release();
+ }
+
+ @Test
+ public void testUpdateCompatInfo() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+
+ // No diff
+ clearInvocations(mWindowManager);
+ TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
+
+ verify(mWindowManager, never()).updateSurfacePosition();
+ verify(mWindowManager, never()).release();
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+
+
+ // Change task listener, recreate button.
+ clearInvocations(mWindowManager);
+ final ShellTaskOrganizer.TaskListener newTaskListener = mock(
+ ShellTaskOrganizer.TaskListener.class);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+ verify(mWindowManager).release();
+ verify(mWindowManager).createLayout(/* canShow= */ true);
+
+ // Change has eligibleForUserAspectRatioButton to false, dispose the component
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ false, /* topActivityBoundsLetterboxed */ true);
+ assertFalse(
+ mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+ verify(mWindowManager).release();
+ }
+
+ @Test
+ public void testUpdateCompatInfoLayoutNotInflatedYet() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ false);
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Change topActivityInSizeCompat to false and pass canShow true, layout shouldn't be
+ // inflated
+ clearInvocations(mWindowManager);
+ TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ false, /* topActivityBoundsLetterboxed */ true);
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated.
+ clearInvocations(mWindowManager);
+ taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true);
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+ }
+
+ @Test
+ public void testUpdateDisplayLayout() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+
+ mWindowManager.updateDisplayLayout(displayLayout1);
+ verify(mWindowManager).updateSurfacePosition();
+
+ // No update if the display bounds is the same.
+ clearInvocations(mWindowManager);
+ final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+ mWindowManager.updateDisplayLayout(displayLayout2);
+ verify(mWindowManager, never()).updateSurfacePosition();
+ }
+
+ @Test
+ public void testUpdateDisplayLayoutInsets() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ final DisplayLayout displayLayout = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+
+ mWindowManager.updateDisplayLayout(displayLayout);
+ verify(mWindowManager).updateSurfacePosition();
+
+ // Update if the insets change on the existing display layout
+ clearInvocations(mWindowManager);
+ InsetsState insetsState = new InsetsState();
+ insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000));
+ InsetsSource insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ insetsSource.setFrame(0, 1800, 1000, 2000);
+ insetsState.addSource(insetsSource);
+ displayLayout.setInsets(mContext.getResources(), insetsState);
+ mWindowManager.updateDisplayLayout(displayLayout);
+ verify(mWindowManager).updateSurfacePosition();
+ }
+
+ @Test
+ public void testUpdateVisibility() {
+ // Create button if it is not created.
+ mWindowManager.removeLayout();
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.updateVisibility(/* canShow= */ true);
+
+ verify(mWindowManager).createLayout(/* canShow= */ true);
+
+ // Hide button.
+ clearInvocations(mWindowManager);
+ doReturn(View.VISIBLE).when(mLayout).getVisibility();
+ mWindowManager.updateVisibility(/* canShow= */ false);
+
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+ verify(mLayout).setVisibility(View.GONE);
+
+ // Show button.
+ doReturn(View.GONE).when(mLayout).getVisibility();
+ mWindowManager.updateVisibility(/* canShow= */ true);
+
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+ verify(mLayout).setVisibility(View.VISIBLE);
+ }
+
+ @Test
+ public void testAttachToParentSurface() {
+ final SurfaceControl.Builder b = new SurfaceControl.Builder();
+ mWindowManager.attachToParentSurface(b);
+
+ verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b);
+ }
+
+ @Test
+ public void testOnUserAspectRatioButtonClicked() {
+ mWindowManager.onUserAspectRatioSettingsButtonClicked();
+
+ verify(mOnUserAspectRatioSettingsButtonClicked).accept(
+ mUserAspectRationTaskInfoCaptor.capture(),
+ mUserAspectRatioTaskListenerCaptor.capture());
+ final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result =
+ new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(),
+ mUserAspectRatioTaskListenerCaptor.getValue());
+ Assert.assertEquals(mTaskInfo, result.first);
+ Assert.assertEquals(mTaskListener, result.second);
+ }
+
+ @Test
+ public void testOnUserAspectRatioButtonLongClicked_showHint() {
+ // Not create hint popup.
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+ verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+
+ mWindowManager.onUserAspectRatioSettingsButtonLongClicked();
+
+ verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ }
+
+ @Test
+ public void testWhenDockedStateHasChanged_needsToBeRecreated() {
+ ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
+ newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
+
+ Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
+ }
+
+ private static TaskInfo createTaskInfo(boolean eligibleForUserAspectRatioButton,
+ boolean topActivityBoundsLetterboxed) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = TASK_ID;
+ taskInfo.topActivityEligibleForUserAspectRatioButton = eligibleForUserAspectRatioButton;
+ taskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed;
+ taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
+ taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
+ return taskInfo;
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index cf7d2c5..3d9645a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -58,9 +58,26 @@
void userActivity();
void getState();
- boolean areCaptionsEnabled();
- void setCaptionsEnabled(boolean isEnabled);
+ /**
+ * Get Captions enabled state
+ *
+ * @param checkForSwitchState set true when we'd like to switch captions enabled state after
+ * getting the latest captions state.
+ */
+ void getCaptionsEnabledState(boolean checkForSwitchState);
+ /**
+ * Set Captions enabled state
+ *
+ * @param enabled the captions enabled state we'd like to update.
+ */
+ void setCaptionsEnabledState(boolean enabled);
+
+ /**
+ * Get Captions component state
+ *
+ * @param fromTooltip if it's triggered from tooltip.
+ */
void getCaptionsComponentState(boolean fromTooltip);
@ProvidesInterface(version = StreamState.VERSION)
@@ -192,7 +209,22 @@
void onScreenOff();
void onShowSafetyWarning(int flags);
void onAccessibilityModeChanged(Boolean showA11yStream);
+
+ /**
+ * Callback function for captions component state changed event
+ *
+ * @param isComponentEnabled the lateset captions component state.
+ * @param fromTooltip if it's triggered from tooltip.
+ */
void onCaptionComponentStateChanged(Boolean isComponentEnabled, Boolean fromTooltip);
+
+ /**
+ * Callback function for captions enabled state changed event
+ *
+ * @param isEnabled the lateset captions enabled state.
+ * @param checkBeforeSwitch intend to switch captions enabled state after the callback.
+ */
+ void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch);
// requires version 2
void onShowCsdWarning(@AudioManager.CsdWarning int csdWarning, int durationMs);
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6164f29f..4aad6e7 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -917,4 +917,7 @@
"$packageName" part that will be replaced by the code with the package name of the target app.
-->
<string name="config_appStoreAppLinkTemplate" translatable="false"></string>
+
+ <!-- Flag controlling whether visual query attention detection has been enabled. -->
+ <bool name="config_enableVisualQueryAttentionDetection">false</bool>
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index b81e081..e3f9de1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -102,6 +102,12 @@
super.onViewAttached();
mView.setKeyDownListener(mKeyDownListener);
mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (shouldLockout(deadline)) {
+ handleAttemptLockout(deadline);
+ }
}
@Override
@@ -278,12 +284,6 @@
@Override
public void onResume(int reason) {
mResumed = true;
- // if the user is currently locked out, enforce it.
- long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
- KeyguardUpdateMonitor.getCurrentUser());
- if (shouldLockout(deadline)) {
- handleAttemptLockout(deadline);
- }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 20e4656..42dbc48 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -30,13 +30,13 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
+import com.android.systemui.bouncer.ui.BouncerMessageView;
+import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
-import com.android.systemui.bouncer.ui.BouncerMessageView;
-import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder;
import com.android.systemui.log.BouncerLogger;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.ViewController;
@@ -95,6 +95,12 @@
@CallSuper
protected void onViewAttached() {
updateMessageAreaVisibility();
+ if (TextUtils.isEmpty(mMessageAreaController.getMessage())
+ && getInitialMessageResId() != 0) {
+ mMessageAreaController.setMessage(
+ mView.getResources().getString(getInitialMessageResId()),
+ /* animate= */ false);
+ }
}
private void updateMessageAreaVisibility() {
@@ -147,12 +153,6 @@
}
public void startAppearAnimation() {
- if (TextUtils.isEmpty(mMessageAreaController.getMessage())
- && getInitialMessageResId() != 0) {
- mMessageAreaController.setMessage(
- mView.getResources().getString(getInitialMessageResId()),
- /* animate= */ false);
- }
mView.startAppearAnimation();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 49f788c..a30b447 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -238,6 +238,12 @@
}
mView.onDevicePostureChanged(mPostureController.getDevicePosture());
mPostureController.addCallback(mPostureCallback);
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ }
}
@Override
@@ -268,12 +274,6 @@
@Override
public void onResume(int reason) {
super.onResume(reason);
- // if the user is currently locked out, enforce it.
- long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
- KeyguardUpdateMonitor.getCurrentUser());
- if (deadline != 0) {
- handleAttemptLockout(deadline);
- }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index b3e08c0..574a059 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -79,6 +79,10 @@
mPasswordEntry.setUserActivityListener(this::onUserInput);
mView.onDevicePostureChanged(mPostureController.getDevicePosture());
mPostureController.addCallback(mPostureCallback);
+ if (mFeatureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)) {
+ mPasswordEntry.setUsePinShapes(true);
+ updateAutoConfirmationState();
+ }
}
protected void onUserInput() {
@@ -100,10 +104,6 @@
@Override
public void startAppearAnimation() {
- if (mFeatureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)) {
- mPasswordEntry.setUsePinShapes(true);
- updateAutoConfirmationState();
- }
super.startAppearAnimation();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index d9a1dc6..94d26bd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -146,8 +146,19 @@
private int mLastOrientation;
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
+ private int mCurrentUser = UserHandle.USER_NULL;
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
- () -> showPrimarySecurityScreen(false);
+ new UserSwitcherController.UserSwitchCallback() {
+ @Override
+ public void onUserSwitched() {
+ if (mCurrentUser == KeyguardUpdateMonitor.getCurrentUser()) {
+ return;
+ }
+ mCurrentUser = KeyguardUpdateMonitor.getCurrentUser();
+ showPrimarySecurityScreen(false);
+ reinflateViewFlipper((l) -> {});
+ }
+ };
@VisibleForTesting
final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
@@ -343,7 +354,6 @@
@Override
public void onThemeChanged() {
reloadColors();
- reset();
}
@Override
@@ -1164,7 +1174,7 @@
}
private void reloadColors() {
- reinflateViewFlipper(controller -> mView.reloadColors());
+ mView.reloadColors();
}
/** Handles density or font scale changes. */
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 2b83e6b..590056f 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -23,6 +23,8 @@
import android.util.Log;
import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -39,10 +41,13 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.settings.SecureSettings;
-import javax.inject.Inject;
-
import dagger.Lazy;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
/**
* Class to manage everything related to assist in SystemUI.
*/
@@ -78,6 +83,18 @@
void hide();
}
+ /**
+ * An interface for a listener that receives notification that visual query attention has
+ * either been gained or lost.
+ */
+ public interface VisualQueryAttentionListener {
+ /** Called when visual query attention has been gained. */
+ void onAttentionGained();
+
+ /** Called when visual query attention has been lost. */
+ void onAttentionLost();
+ }
+
private static final String TAG = "AssistManager";
// Note that VERBOSE logging may leak PII (e.g. transcription contents).
@@ -127,6 +144,23 @@
private final SecureSettings mSecureSettings;
private final DeviceProvisionedController mDeviceProvisionedController;
+
+ private final List<VisualQueryAttentionListener> mVisualQueryAttentionListeners =
+ new ArrayList<>();
+
+ private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener =
+ new IVisualQueryDetectionAttentionListener.Stub() {
+ @Override
+ public void onAttentionGained() {
+ mVisualQueryAttentionListeners.forEach(VisualQueryAttentionListener::onAttentionGained);
+ }
+
+ @Override
+ public void onAttentionLost() {
+ mVisualQueryAttentionListeners.forEach(VisualQueryAttentionListener::onAttentionLost);
+ }
+ };
+
private final CommandQueue mCommandQueue;
protected final AssistUtils mAssistUtils;
@@ -157,6 +191,7 @@
mSecureSettings = secureSettings;
registerVoiceInteractionSessionListener();
+ registerVisualQueryRecognitionStatusListener();
mUiController = defaultUiController;
@@ -266,6 +301,24 @@
mAssistUtils.hideCurrentSession();
}
+ /**
+ * Add the given {@link VisualQueryAttentionListener} to the list of listeners awaiting
+ * notification of gaining/losing visual query attention.
+ */
+ public void addVisualQueryAttentionListener(VisualQueryAttentionListener listener) {
+ if (!mVisualQueryAttentionListeners.contains(listener)) {
+ mVisualQueryAttentionListeners.add(listener);
+ }
+ }
+
+ /**
+ * Remove the given {@link VisualQueryAttentionListener} from the list of listeners awaiting
+ * notification of gaining/losing visual query attention.
+ */
+ public void removeVisualQueryAttentionListener(VisualQueryAttentionListener listener) {
+ mVisualQueryAttentionListeners.remove(listener);
+ }
+
private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
boolean isService) {
if (isService) {
@@ -326,6 +379,27 @@
null, null);
}
+ private void registerVisualQueryRecognitionStatusListener() {
+ if (!mContext.getResources()
+ .getBoolean(R.bool.config_enableVisualQueryAttentionDetection)) {
+ return;
+ }
+
+ mAssistUtils.subscribeVisualQueryRecognitionStatus(
+ new IVisualQueryRecognitionStatusListener.Stub() {
+ @Override
+ public void onStartPerceiving() {
+ mAssistUtils.enableVisualQueryDetection(
+ mVisualQueryDetectionAttentionListener);
+ }
+
+ @Override
+ public void onStopPerceiving() {
+ mAssistUtils.disableVisualQueryDetection();
+ }
+ });
+ }
+
public void launchVoiceAssistFromKeyguard() {
mAssistUtils.launchVoiceAssistFromKeyguard();
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
index 566a74a..d58fab4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
@@ -27,7 +27,8 @@
import com.android.systemui.R;
class IntentCreator {
- private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
+ private static final String EXTRA_EDIT_SOURCE = "edit_source";
+ private static final String EDIT_SOURCE_CLIPBOARD = "clipboard";
private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
static Intent getTextEditorIntent(Context context) {
@@ -74,7 +75,7 @@
editIntent.setDataAndType(uri, "image/*");
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- editIntent.putExtra(EXTRA_EDIT_SOURCE_CLIPBOARD, true);
+ editIntent.putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD);
return editIntent;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index d9665c5b5..484be9c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -53,6 +53,7 @@
import com.android.systemui.statusbar.phone.KeyguardLiftController
import com.android.systemui.statusbar.phone.LockscreenWallpaper
import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
@@ -331,4 +332,11 @@
@IntoMap
@ClassKey(ScrimController::class)
abstract fun bindScrimController(impl: ScrimController): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(StatusBarHeadsUpChangeListener::class)
+ abstract fun bindStatusBarHeadsUpChangeListener(
+ impl: StatusBarHeadsUpChangeListener
+ ): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
index c889ac2..4dd97d5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
@@ -16,10 +16,9 @@
package com.android.systemui.dreams.conditions;
-import com.android.internal.app.AssistUtils;
-import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener;
import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.shared.condition.Condition;
import javax.inject.Inject;
@@ -30,12 +29,10 @@
* {@link AssistantAttentionCondition} provides a signal when assistant has the user's attention.
*/
public class AssistantAttentionCondition extends Condition {
- private final DreamOverlayStateController mDreamOverlayStateController;
- private final AssistUtils mAssistUtils;
- private boolean mEnabled;
+ private final AssistManager mAssistManager;
- private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener =
- new IVisualQueryDetectionAttentionListener.Stub() {
+ private final VisualQueryAttentionListener mVisualQueryAttentionListener =
+ new VisualQueryAttentionListener() {
@Override
public void onAttentionGained() {
updateCondition(true);
@@ -47,59 +44,26 @@
}
};
- private final DreamOverlayStateController.Callback mCallback =
- new DreamOverlayStateController.Callback() {
- @Override
- public void onStateChanged() {
- if (mDreamOverlayStateController.isDreamOverlayStatusBarVisible()) {
- enableVisualQueryDetection();
- } else {
- disableVisualQueryDetection();
- }
- }
- };
-
@Inject
public AssistantAttentionCondition(
@Application CoroutineScope scope,
- DreamOverlayStateController dreamOverlayStateController,
- AssistUtils assistUtils) {
+ AssistManager assistManager) {
super(scope);
- mDreamOverlayStateController = dreamOverlayStateController;
- mAssistUtils = assistUtils;
+ mAssistManager = assistManager;
}
@Override
protected void start() {
- mDreamOverlayStateController.addCallback(mCallback);
+ mAssistManager.addVisualQueryAttentionListener(mVisualQueryAttentionListener);
}
@Override
protected void stop() {
- disableVisualQueryDetection();
- mDreamOverlayStateController.removeCallback(mCallback);
+ mAssistManager.removeVisualQueryAttentionListener(mVisualQueryAttentionListener);
}
@Override
protected int getStartStrategy() {
return START_EAGERLY;
}
-
- private void enableVisualQueryDetection() {
- if (mEnabled) {
- return;
- }
- mEnabled = true;
- mAssistUtils.enableVisualQueryDetection(mVisualQueryDetectionAttentionListener);
- }
-
- private void disableVisualQueryDetection() {
- if (!mEnabled) {
- return;
- }
- mEnabled = false;
- mAssistUtils.disableVisualQueryDetection();
- // Make sure the condition is set to false as well.
- updateCondition(false);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 5b56223..9393754 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -195,7 +195,7 @@
// TODO(b/294110497): Tracking Bug
@JvmField
val ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS =
- unreleasedFlag("enable_wallet_contextual_loyalty_cards", teamfood = true)
+ releasedFlag("enable_wallet_contextual_loyalty_cards")
// TODO(b/242908637): Tracking Bug
@JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag("wallpaper_fullscreen_preview")
@@ -386,8 +386,7 @@
@JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag("new_bluetooth_repository")
// TODO(b/292533677): Tracking Bug
- val WIFI_TRACKER_LIB_FOR_WIFI_ICON =
- unreleasedFlag("wifi_tracker_lib_for_wifi_icon", teamfood = true)
+ val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon")
// TODO(b/293863612): Tracking Bug
@JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
@@ -787,4 +786,8 @@
/** Enable the share wifi button in Quick Settings internet dialog. */
@JvmField
val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button")
+
+ /** Enable haptic slider component in the brightness slider */
+ @JvmField
+ val HAPTIC_BRIGHTNESS_SLIDER = unreleasedFlag("haptic_brightness_slider")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 23b80b0..ed4dd6a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -19,10 +19,10 @@
import android.os.Trace
import android.util.Log
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.launch
@@ -31,19 +31,19 @@
companion object {
private const val TAG = "KeyguardBlueprintViewBinder"
- fun bind(keyguardRootView: KeyguardRootView, viewModel: KeyguardBlueprintViewModel) {
- keyguardRootView.repeatWhenAttached {
+ fun bind(constraintLayout: ConstraintLayout, viewModel: KeyguardBlueprintViewModel) {
+ constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.blueprint.collect { blueprint ->
Trace.beginSection("KeyguardBlueprintController#applyBlueprint")
Log.d(TAG, "applying blueprint: $blueprint")
ConstraintSet().apply {
- clone(keyguardRootView)
+ clone(constraintLayout)
val emptyLayout = ConstraintSet.Layout()
knownIds.forEach { getConstraint(it).layout.copyFrom(emptyLayout) }
blueprint?.apply(this)
- applyTo(keyguardRootView)
+ applyTo(constraintLayout)
}
Trace.endSection()
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index e134f7c..ae0ab84 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -25,6 +25,7 @@
import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
import static android.view.InsetsSource.FLAG_SUPPRESS_SCRIM;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -1714,10 +1715,12 @@
private InsetsFrameProvider[] getInsetsFrameProvider(int insetsHeight, Context userContext) {
final InsetsFrameProvider navBarProvider =
- new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars())
- .setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] {
- new InsetsFrameProvider.InsetsSizeOverride(
- TYPE_INPUT_METHOD, null)});
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars());
+ if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+ navBarProvider.setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] {
+ new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
+ });
+ }
if (insetsHeight != -1 && !mEdgeBackGestureHandler.isButtonForcedVisible()) {
navBarProvider.setInsetsSize(Insets.of(0, 0, 0, insetsHeight));
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 76d9b03..a7434c6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -21,16 +21,13 @@
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.RemoteUserInput
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -109,10 +106,6 @@
/** Whether the scene container is visible. */
val isVisible: StateFlow<Boolean> = repository.isVisible
- private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null)
- /** A flow of motion events originating from outside of the scene framework. */
- val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow()
-
/**
* Returns the keys of all scenes in the container.
*
@@ -160,11 +153,6 @@
repository.setTransitionState(transitionState)
}
- /** Handles a remote user input. */
- fun onRemoteUserInput(input: RemoteUserInput) {
- _remoteUserInput.value = input
- }
-
/**
* Notifies that the UI has transitioned sufficiently to the given scene.
*
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/RemoteUserInput.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/RemoteUserInput.kt
deleted file mode 100644
index 680de59..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/RemoteUserInput.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.android.systemui.scene.shared.model
-
-import android.view.MotionEvent
-
-/** A representation of user input that is used by the scene framework. */
-data class RemoteUserInput(
- val x: Float,
- val y: Float,
- val action: RemoteUserInputAction,
-) {
- companion object {
- fun translateMotionEvent(event: MotionEvent): RemoteUserInput {
- return RemoteUserInput(
- x = event.x,
- y = event.y,
- action =
- when (event.actionMasked) {
- MotionEvent.ACTION_DOWN -> RemoteUserInputAction.DOWN
- MotionEvent.ACTION_MOVE -> RemoteUserInputAction.MOVE
- MotionEvent.ACTION_UP -> RemoteUserInputAction.UP
- MotionEvent.ACTION_CANCEL -> RemoteUserInputAction.CANCEL
- else -> RemoteUserInputAction.UNKNOWN
- }
- )
- }
- }
-}
-
-enum class RemoteUserInputAction {
- DOWN,
- MOVE,
- UP,
- CANCEL,
- UNKNOWN,
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 8601b3d..cdf50ba 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -2,7 +2,6 @@
import android.content.Context
import android.util.AttributeSet
-import android.view.MotionEvent
import android.view.View
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
@@ -39,14 +38,6 @@
)
}
- override fun onTouchEvent(event: MotionEvent?): Boolean {
- return event?.let {
- viewModel.onRemoteUserInput(event)
- true
- }
- ?: false
- }
-
override fun setVisibility(visibility: Int) {
// Do nothing. We don't want external callers to invoke this. Instead, we drive our own
// visibility from our view-binder.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 3e9bbe4..5c16fb5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -16,11 +16,9 @@
package com.android.systemui.scene.ui.viewmodel
-import android.view.MotionEvent
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.RemoteUserInput
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import javax.inject.Inject
@@ -34,9 +32,6 @@
constructor(
private val interactor: SceneInteractor,
) {
- /** A flow of motion events originating from outside of the scene framework. */
- val remoteUserInput: StateFlow<RemoteUserInput?> = interactor.remoteUserInput
-
/**
* Keys of all scenes in the container.
*
@@ -68,11 +63,6 @@
interactor.setTransitionState(transitionState)
}
- /** Handles a [MotionEvent] representing remote user input. */
- fun onRemoteUserInput(event: MotionEvent) {
- interactor.onRemoteUserInput(RemoteUserInput.translateMotionEvent(event))
- }
-
companion object {
private const val SCENE_TRANSITION_LOGGING_REASON = "user input"
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 05a0416..ab2a8d9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -82,11 +82,15 @@
return editIntent
.setDataAndType(uri, "image/png")
+ .putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_SCREENSHOT)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
+
+ private const val EXTRA_EDIT_SOURCE = "edit_source"
+ private const val EDIT_SOURCE_SCREENSHOT = "screenshot"
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 5c2f9a8..62a0d13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -39,10 +39,7 @@
override fun attach(pipeline: NotifPipeline) {
pipeline.addOnAfterRenderListListener(::onAfterRenderList)
- // TODO(b/282865576): This has an issue where it makes changes to some groups without
- // notifying listeners. To be fixed in QPR, but for now let's comment it out to avoid the
- // group expansion bug.
- // groupExpansionManagerImpl.attach(pipeline)
+ groupExpansionManagerImpl.attach(pipeline)
}
fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 46af03a..5d33804 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -67,18 +67,29 @@
* Cleanup entries from mExpandedGroups that no longer exist in the pipeline.
*/
private final OnBeforeRenderListListener mNotifTracker = (entries) -> {
+ if (mExpandedGroups.isEmpty()) {
+ return; // nothing to do
+ }
+
final Set<NotificationEntry> renderingSummaries = new HashSet<>();
for (ListEntry entry : entries) {
if (entry instanceof GroupEntry) {
renderingSummaries.add(entry.getRepresentativeEntry());
}
}
- mExpandedGroups.removeIf(expandedGroup -> !renderingSummaries.contains(expandedGroup));
+
+ // Create a copy of mExpandedGroups so we can modify it in a thread-safe way.
+ final var currentExpandedGroups = new HashSet<>(mExpandedGroups);
+ for (NotificationEntry entry : currentExpandedGroups) {
+ setExpanded(entry, renderingSummaries.contains(entry));
+ }
};
public void attach(NotifPipeline pipeline) {
- mDumpManager.registerDumpable(this);
- pipeline.addOnBeforeRenderListListener(mNotifTracker);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
+ mDumpManager.registerDumpable(this);
+ pipeline.addOnBeforeRenderListListener(mNotifTracker);
+ }
}
@Override
@@ -94,11 +105,24 @@
@Override
public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
final NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
+ setExpanded(groupSummary, expanded);
+ }
+
+ /**
+ * Add or remove {@code entry} to/from {@code mExpandedGroups} and notify listeners if
+ * something changed. This assumes that {@code entry} is a group summary.
+ * <p>
+ * TODO(b/293434635): Currently, in spite of its docs,
+ * {@code mGroupMembershipManager.getGroupSummary(entry)} returns null if {@code entry} is
+ * already a summary. Instead of needing this helper method to bypass that, we probably want to
+ * move this code back to {@code setGroupExpanded} and use that everywhere.
+ */
+ private void setExpanded(NotificationEntry entry, boolean expanded) {
boolean changed;
if (expanded) {
- changed = mExpandedGroups.add(groupSummary);
+ changed = mExpandedGroups.add(entry);
} else {
- changed = mExpandedGroups.remove(groupSummary);
+ changed = mExpandedGroups.remove(entry);
}
// Only notify listeners if something changed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 127569d..8ffd43a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1612,8 +1612,6 @@
mBackActionInteractor.setup(mQsController, mShadeSurface);
mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
- mHeadsUpManager.addListener(mCentralSurfacesComponent.getStatusBarHeadsUpChangeListener());
-
// Listen for demo mode changes
mDemoModeController.addCallback(mDemoModeCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 931aedd..4de669c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -28,8 +28,7 @@
import com.android.systemui.R
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.RemoteUserInput
+import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
@@ -56,7 +55,7 @@
private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
- private val sceneInteractor: Provider<SceneInteractor>,
+ private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
private val userChipViewModel: StatusBarUserChipViewModel,
@@ -80,7 +79,8 @@
statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer))
if (moveFromCenterAnimationController == null) return
- val statusBarLeftSide: View = mView.requireViewById(R.id.status_bar_start_side_except_heads_up)
+ val statusBarLeftSide: View =
+ mView.requireViewById(R.id.status_bar_start_side_except_heads_up)
val systemIconArea: ViewGroup = mView.requireViewById(R.id.status_bar_end_side_content)
val viewsToAnimate = arrayOf(
@@ -179,11 +179,8 @@
// If scene framework is enabled, route the touch to it and
// ignore the rest of the gesture.
if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
- sceneInteractor.get()
- .onRemoteUserInput(RemoteUserInput.translateMotionEvent(event))
- // TODO(b/291965119): remove once view is expanded to cover the status bar
- sceneInteractor.get().setVisible(true, "swipe down from status bar")
- return false
+ windowRootView.get().dispatchTouchEvent(event)
+ return true
}
if (event.action == MotionEvent.ACTION_DOWN) {
@@ -247,7 +244,7 @@
private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
- private val sceneInteractor: Provider<SceneInteractor>,
+ private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
private val configurationController: ConfigurationController,
@@ -269,7 +266,7 @@
centralSurfaces,
shadeController,
shadeViewController,
- sceneInteractor,
+ windowRootView,
shadeLogger,
statusBarMoveFromCenterAnimationController,
userChipViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 9a295e6..4b39854 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -16,23 +16,24 @@
package com.android.systemui.statusbar.phone;
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import javax.inject.Inject;
/**
- * Ties the {@link CentralSurfaces} to {@link com.android.systemui.statusbar.policy.HeadsUpManager}.
+ * Ties the status bar to {@link com.android.systemui.statusbar.policy.HeadsUpManager}.
*/
-@CentralSurfacesComponent.CentralSurfacesScope
-public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener {
+@SysUISingleton
+public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, CoreStartable {
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarWindowController mStatusBarWindowController;
private final ShadeViewController mShadeViewController;
@@ -63,6 +64,11 @@
}
@Override
+ public void start() {
+ mHeadsUpManager.addListener(this);
+ }
+
+ @Override
public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
if (inPinnedMode) {
mNotificationShadeWindowController.setHeadsUpShowing(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index 3a3663d..bbbe16f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -25,7 +25,6 @@
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarterModule;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
@@ -72,11 +71,6 @@
WindowRootView getWindowRootView();
/**
- * Creates a StatusBarHeadsUpChangeListener.
- */
- StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener();
-
- /**
* Creates a CentralSurfacesCommandQueueCallbacks.
*/
CentralSurfacesCommandQueueCallbacks getCentralSurfacesCommandQueueCallbacks();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index fe24815..275cfc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -221,36 +221,15 @@
override val activityInVisible: Flow<Boolean> =
activity
.map { it?.hasActivityIn ?: false }
- .distinctUntilChanged()
- .logDiffsForTable(
- iconInteractor.tableLogBuffer,
- columnPrefix = "",
- columnName = "activityInVisible",
- initialValue = false,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val activityOutVisible: Flow<Boolean> =
activity
.map { it?.hasActivityOut ?: false }
- .distinctUntilChanged()
- .logDiffsForTable(
- iconInteractor.tableLogBuffer,
- columnPrefix = "",
- columnName = "activityOutVisible",
- initialValue = false,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val activityContainerVisible: Flow<Boolean> =
activity
.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
- .distinctUntilChanged()
- .logDiffsForTable(
- iconInteractor.tableLogBuffer,
- columnPrefix = "",
- columnName = "activityContainerVisible",
- initialValue = false,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index d39a53d..9cc3cdb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -44,6 +44,7 @@
import android.media.session.MediaSession.Token;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -57,6 +58,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;
+import androidx.annotation.NonNull;
import androidx.lifecycle.Observer;
import com.android.internal.annotations.GuardedBy;
@@ -81,6 +83,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
@@ -131,7 +134,7 @@
private final Receiver mReceiver = new Receiver();
private final RingerModeObservers mRingerModeObservers;
private final MediaSessions mMediaSessions;
- private final CaptioningManager mCaptioningManager;
+ private final AtomicReference<CaptioningManager> mCaptioningManager = new AtomicReference<>();
private final KeyguardManager mKeyguardManager;
private final ActivityManager mActivityManager;
private final UserTracker mUserTracker;
@@ -155,16 +158,16 @@
private final WakefulnessLifecycle.Observer mWakefullnessLifecycleObserver =
new WakefulnessLifecycle.Observer() {
- @Override
- public void onStartedWakingUp() {
- mDeviceInteractive = true;
- }
+ @Override
+ public void onStartedWakingUp() {
+ mDeviceInteractive = true;
+ }
- @Override
- public void onFinishedGoingToSleep() {
- mDeviceInteractive = false;
- }
- };
+ @Override
+ public void onFinishedGoingToSleep() {
+ mDeviceInteractive = false;
+ }
+ };
@Inject
public VolumeDialogControllerImpl(
@@ -179,7 +182,6 @@
AccessibilityManager accessibilityManager,
PackageManager packageManager,
WakefulnessLifecycle wakefulnessLifecycle,
- CaptioningManager captioningManager,
KeyguardManager keyguardManager,
ActivityManager activityManager,
UserTracker userTracker,
@@ -209,17 +211,19 @@
mVibrator = vibrator;
mHasVibrator = mVibrator.hasVibrator();
mAudioService = iAudioService;
- mCaptioningManager = captioningManager;
mKeyguardManager = keyguardManager;
mActivityManager = activityManager;
mUserTracker = userTracker;
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mWorker));
+ createCaptioningManagerServiceByUserContext(mUserTracker.getUserContext());
+
dumpManager.registerDumpable("VolumeDialogControllerImpl", this);
boolean accessibilityVolumeStreamActive = accessibilityManager
.isAccessibilityVolumeStreamActive();
mVolumeController.setA11yMode(accessibilityVolumeStreamActive ?
- VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
- VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
+ VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
+ VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
mWakefulnessLifecycle.addObserver(mWakefullnessLifecycleObserver);
}
@@ -316,12 +320,31 @@
mWorker.sendEmptyMessage(W.GET_STATE);
}
- public boolean areCaptionsEnabled() {
- return mCaptioningManager.isSystemAudioCaptioningEnabled();
+ /**
+ * We met issues about the wrong state of System Caption in multi-user mode.
+ * It happened in the usage of CaptioningManager Service from SysUI process
+ * that is a global system process of User 0.
+ * Therefore, we have to add callback on UserTracker that allows us to get the Context of
+ * active User and then get the corresponding CaptioningManager Service for further usages.
+ */
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ createCaptioningManagerServiceByUserContext(userContext);
+ }
+ };
+
+ private void createCaptioningManagerServiceByUserContext(@NonNull Context userContext) {
+ mCaptioningManager.set(userContext.getSystemService(CaptioningManager.class));
}
- public void setCaptionsEnabled(boolean isEnabled) {
- mCaptioningManager.setSystemAudioCaptioningEnabled(isEnabled);
+ public void getCaptionsEnabledState(boolean checkForSwitchState) {
+ mWorker.obtainMessage(W.GET_CAPTIONS_ENABLED_STATE, checkForSwitchState).sendToTarget();
+ }
+
+ public void setCaptionsEnabledState(boolean enabled) {
+ mWorker.obtainMessage(W.SET_CAPTIONS_ENABLED_STATE, enabled).sendToTarget();
}
public void getCaptionsComponentState(boolean fromTooltip) {
@@ -362,8 +385,8 @@
}
public void setEnableDialogs(boolean volumeUi, boolean safetyWarning) {
- mShowVolumeDialog = volumeUi;
- mShowSafetyWarning = safetyWarning;
+ mShowVolumeDialog = volumeUi;
+ mShowSafetyWarning = safetyWarning;
}
@Override
@@ -414,12 +437,38 @@
}
private void onShowCsdWarningW(@AudioManager.CsdWarning int csdWarning, int durationMs) {
- mCallbacks.onShowCsdWarning(csdWarning, durationMs);
+ mCallbacks.onShowCsdWarning(csdWarning, durationMs);
}
private void onGetCaptionsComponentStateW(boolean fromTooltip) {
- mCallbacks.onCaptionComponentStateChanged(
- mCaptioningManager.isSystemAudioCaptioningUiEnabled(), fromTooltip);
+ CaptioningManager captioningManager = mCaptioningManager.get();
+ if (null != captioningManager) {
+ mCallbacks.onCaptionComponentStateChanged(
+ captioningManager.isSystemAudioCaptioningUiEnabled(), fromTooltip);
+ } else {
+ Log.e(TAG, "onGetCaptionsComponentStateW(), null captioningManager");
+ }
+ }
+
+ private void onGetCaptionsEnabledStateW(boolean checkForSwitchState) {
+ CaptioningManager captioningManager = mCaptioningManager.get();
+ if (null != captioningManager) {
+ mCallbacks.onCaptionEnabledStateChanged(
+ captioningManager.isSystemAudioCaptioningEnabled(), checkForSwitchState);
+ } else {
+ Log.e(TAG, "onGetCaptionsEnabledStateW(), null captioningManager");
+ }
+ }
+
+ private void onSetCaptionsEnabledStateW(boolean enabled) {
+ CaptioningManager captioningManager = mCaptioningManager.get();
+ if (null != captioningManager) {
+ captioningManager.setSystemAudioCaptioningEnabled(enabled);
+ mCallbacks.onCaptionEnabledStateChanged(
+ captioningManager.isSystemAudioCaptioningEnabled(), false);
+ } else {
+ Log.e(TAG, "onGetCaptionsEnabledStateW(), null captioningManager");
+ }
}
private void onAccessibilityModeChanged(Boolean showA11yStream) {
@@ -719,7 +768,7 @@
* This method will never be called if the CSD (Computed Sound Dose) feature is
* not enabled. See com.android.android.server.audio.SoundDoseHelper for the state of
* the feature.
- * @param warning the type of warning to display, values are one of
+ * @param csdWarning the type of warning to display, values are one of
* {@link android.media.AudioManager#CSD_WARNING_DOSE_REACHED_1X},
* {@link android.media.AudioManager#CSD_WARNING_DOSE_REPEATED_5X},
* {@link android.media.AudioManager#CSD_WARNING_MOMENTARY_EXPOSURE},
@@ -798,6 +847,8 @@
private static final int ACCESSIBILITY_MODE_CHANGED = 15;
private static final int GET_CAPTIONS_COMPONENT_STATE = 16;
private static final int SHOW_CSD_WARNING = 17;
+ private static final int GET_CAPTIONS_ENABLED_STATE = 18;
+ private static final int SET_CAPTIONS_ENABLED_STATE = 19;
W(Looper looper) {
super(looper);
@@ -825,6 +876,10 @@
case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
break;
case SHOW_CSD_WARNING: onShowCsdWarningW(msg.arg1, msg.arg2); break;
+ case GET_CAPTIONS_ENABLED_STATE:
+ onGetCaptionsEnabledStateW((Boolean) msg.obj); break;
+ case SET_CAPTIONS_ENABLED_STATE:
+ onSetCaptionsEnabledStateW((Boolean) msg.obj); break;
}
}
}
@@ -993,6 +1048,17 @@
componentEnabled, fromTooltip));
}
}
+
+ @Override
+ public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch) {
+ boolean captionsEnabled = isEnabled != null && isEnabled;
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(
+ () -> entry.getKey().onCaptionEnabledStateChanged(
+ captionsEnabled, checkBeforeSwitch));
+ }
+ }
+
}
private final class RingerModeObservers {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 6219e4d..aafa16f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1333,21 +1333,30 @@
if (!isServiceComponentEnabled) return;
- updateCaptionsIcon();
+ checkEnabledStateForCaptionsIconUpdate();
if (fromTooltip) showCaptionsTooltip();
}
- private void updateCaptionsIcon() {
- boolean captionsEnabled = mController.areCaptionsEnabled();
- if (mODICaptionsIcon.getCaptionsEnabled() != captionsEnabled) {
- mHandler.post(mODICaptionsIcon.setCaptionsEnabled(captionsEnabled));
+ private void updateCaptionsEnabledH(boolean isCaptionsEnabled, boolean checkForSwitchState) {
+ if (checkForSwitchState) {
+ mController.setCaptionsEnabledState(!isCaptionsEnabled);
+ } else {
+ updateCaptionsIcon(isCaptionsEnabled);
+ }
+ }
+
+ private void checkEnabledStateForCaptionsIconUpdate() {
+ mController.getCaptionsEnabledState(false);
+ }
+
+ private void updateCaptionsIcon(boolean isCaptionsEnabled) {
+ if (mODICaptionsIcon.getCaptionsEnabled() != isCaptionsEnabled) {
+ mHandler.post(mODICaptionsIcon.setCaptionsEnabled(isCaptionsEnabled));
}
}
private void onCaptionIconClicked() {
- boolean isEnabled = mController.areCaptionsEnabled();
- mController.setCaptionsEnabled(!isEnabled);
- updateCaptionsIcon();
+ mController.getCaptionsEnabledState(true);
}
private void incrementManualToggleCount() {
@@ -2363,7 +2372,6 @@
} else {
updateRowsH(activeRow);
}
-
}
@Override
@@ -2371,6 +2379,11 @@
Boolean isComponentEnabled, Boolean fromTooltip) {
updateODICaptionsH(isComponentEnabled, fromTooltip);
}
+
+ @Override
+ public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkForSwitchState) {
+ updateCaptionsEnabledH(isEnabled, checkForSwitchState);
+ }
};
@VisibleForTesting void onPostureChanged(int posture) {
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 316b54e..091a54f 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -109,6 +109,7 @@
private WallpaperManager mWallpaperManager;
private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
private SurfaceHolder mSurfaceHolder;
+ private boolean mDrawn = false;
@VisibleForTesting
static final int MIN_SURFACE_WIDTH = 128;
@VisibleForTesting
@@ -238,6 +239,7 @@
private void drawFrameSynchronized() {
synchronized (mLock) {
+ if (mDrawn) return;
drawFrameInternal();
}
}
@@ -275,6 +277,7 @@
Rect dest = mSurfaceHolder.getSurfaceFrame();
try {
canvas.drawBitmap(bitmap, null, dest, null);
+ mDrawn = true;
} finally {
surface.unlockCanvasAndPost(canvas);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 677d3ff..c894d91 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -180,8 +180,9 @@
}
@Test
- public void testResume() {
- mKeyguardAbsKeyInputViewController.onResume(KeyguardSecurityView.VIEW_REVEALED);
+ public void testOnViewAttached() {
+ reset(mLockPatternUtils);
+ mKeyguardAbsKeyInputViewController.onViewAttached();
verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 1a9260c..3a94730 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -20,6 +20,7 @@
import android.testing.TestableLooper
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
+import android.widget.ImageView
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
@@ -29,6 +30,7 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.whenever
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,6 +38,7 @@
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
@@ -45,104 +48,109 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPasswordViewControllerTest : SysuiTestCase() {
- @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
- @Mock private lateinit var passwordEntry: EditText
- @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
- @Mock lateinit var lockPatternUtils: LockPatternUtils
- @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
- @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
- @Mock lateinit var latencyTracker: LatencyTracker
- @Mock lateinit var inputMethodManager: InputMethodManager
- @Mock lateinit var emergencyButtonController: EmergencyButtonController
- @Mock lateinit var mainExecutor: DelayableExecutor
- @Mock lateinit var falsingCollector: FalsingCollector
- @Mock lateinit var keyguardViewController: KeyguardViewController
- @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
- @Mock
- private lateinit var mKeyguardMessageAreaController:
- KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
+ @Mock private lateinit var passwordEntry: EditText
+ @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+ @Mock lateinit var lockPatternUtils: LockPatternUtils
+ @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock lateinit var latencyTracker: LatencyTracker
+ @Mock lateinit var inputMethodManager: InputMethodManager
+ @Mock lateinit var emergencyButtonController: EmergencyButtonController
+ @Mock lateinit var mainExecutor: DelayableExecutor
+ @Mock lateinit var falsingCollector: FalsingCollector
+ @Mock lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+ @Mock
+ private lateinit var mKeyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+ private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- Mockito.`when`(
- keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>(
- R.id.bouncer_message_area))
- .thenReturn(mKeyguardMessageArea)
- Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
- .thenReturn(mKeyguardMessageAreaController)
- Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
- Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
- .thenReturn(passwordEntry)
- `when`(keyguardPasswordView.resources).thenReturn(context.resources)
- val fakeFeatureFlags = FakeFeatureFlags()
- fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
- keyguardPasswordViewController =
- KeyguardPasswordViewController(
- keyguardPasswordView,
- keyguardUpdateMonitor,
- securityMode,
- lockPatternUtils,
- keyguardSecurityCallback,
- messageAreaControllerFactory,
- latencyTracker,
- inputMethodManager,
- emergencyButtonController,
- mainExecutor,
- mContext.resources,
- falsingCollector,
- keyguardViewController,
- fakeFeatureFlags)
- }
-
- @Test
- fun testFocusWhenBouncerIsShown() {
- Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
- Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
- keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- keyguardPasswordView.post {
- verify(keyguardPasswordView).requestFocus()
- verify(keyguardPasswordView).showKeyboard()
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ Mockito.`when`(
+ keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>(
+ R.id.bouncer_message_area
+ )
+ )
+ .thenReturn(mKeyguardMessageArea)
+ Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
+ .thenReturn(mKeyguardMessageAreaController)
+ Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+ Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
+ .thenReturn(passwordEntry)
+ whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
+ .thenReturn(mock(ImageView::class.java))
+ `when`(keyguardPasswordView.resources).thenReturn(context.resources)
+ val fakeFeatureFlags = FakeFeatureFlags()
+ fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ keyguardPasswordViewController =
+ KeyguardPasswordViewController(
+ keyguardPasswordView,
+ keyguardUpdateMonitor,
+ securityMode,
+ lockPatternUtils,
+ keyguardSecurityCallback,
+ messageAreaControllerFactory,
+ latencyTracker,
+ inputMethodManager,
+ emergencyButtonController,
+ mainExecutor,
+ mContext.resources,
+ falsingCollector,
+ keyguardViewController,
+ fakeFeatureFlags
+ )
}
- }
- @Test
- fun testDoNotFocusWhenBouncerIsHidden() {
- Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
- Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
- keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- verify(keyguardPasswordView, never()).requestFocus()
- }
-
- @Test
- fun testHideKeyboardWhenOnPause() {
- keyguardPasswordViewController.onPause()
- keyguardPasswordView.post {
- verify(keyguardPasswordView).clearFocus()
- verify(keyguardPasswordView).hideKeyboard()
+ @Test
+ fun testFocusWhenBouncerIsShown() {
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
+ Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+ keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).requestFocus()
+ verify(keyguardPasswordView).showKeyboard()
+ }
}
- }
- @Test
- fun startAppearAnimation() {
- keyguardPasswordViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController)
- .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false)
- }
+ @Test
+ fun testDoNotFocusWhenBouncerIsHidden() {
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
+ Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+ keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ verify(keyguardPasswordView, never()).requestFocus()
+ }
- @Test
- fun startAppearAnimation_withExistingMessage() {
- `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- keyguardPasswordViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
- }
+ @Test
+ fun testHideKeyboardWhenOnPause() {
+ keyguardPasswordViewController.onPause()
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).clearFocus()
+ verify(keyguardPasswordView).hideKeyboard()
+ }
+ }
- @Test
- fun testMessageIsSetWhenReset() {
- keyguardPasswordViewController.resetState()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
- }
+ @Test
+ fun testOnViewAttached() {
+ keyguardPasswordViewController.onViewAttached()
+ verify(mKeyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false)
+ }
+
+ @Test
+ fun testOnViewAttached_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ keyguardPasswordViewController.onViewAttached()
+ verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+ }
+
+ @Test
+ fun testMessageIsSetWhenReset() {
+ keyguardPasswordViewController.resetState()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 9f7ab7b..1acd676 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -46,6 +46,7 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -54,35 +55,35 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPatternViewControllerTest : SysuiTestCase() {
- private lateinit var mKeyguardPatternView: KeyguardPatternView
+ private lateinit var mKeyguardPatternView: KeyguardPatternView
- @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
+ @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
- @Mock private lateinit var mLockPatternUtils: LockPatternUtils
+ @Mock private lateinit var mLockPatternUtils: LockPatternUtils
- @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
- @Mock private lateinit var mLatencyTracker: LatencyTracker
- private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
+ @Mock private lateinit var mLatencyTracker: LatencyTracker
+ private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
- @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController
+ @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController
- @Mock
- private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock
+ private lateinit var mKeyguardMessageAreaControllerFactory:
+ KeyguardMessageAreaController.Factory
- @Mock
- private lateinit var mKeyguardMessageAreaController:
- KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock
+ private lateinit var mKeyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- @Mock private lateinit var mPostureController: DevicePostureController
+ @Mock private lateinit var mPostureController: DevicePostureController
- private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
- private lateinit var fakeFeatureFlags: FakeFeatureFlags
+ private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
+ private lateinit var fakeFeatureFlags: FakeFeatureFlags
- @Captor
- lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
+ @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
@Before
fun setup() {
@@ -91,9 +92,8 @@
.thenReturn(mKeyguardMessageAreaController)
fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
- mKeyguardPatternView = View.inflate(mContext, R.layout.keyguard_pattern_view, null)
- as KeyguardPatternView
-
+ mKeyguardPatternView =
+ View.inflate(mContext, R.layout.keyguard_pattern_view, null) as KeyguardPatternView
mKeyguardPatternViewController =
KeyguardPatternViewController(
@@ -125,8 +125,7 @@
@Test
fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
- whenever(mPostureController.devicePosture)
- .thenReturn(DEVICE_POSTURE_HALF_OPENED)
+ whenever(mPostureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
mKeyguardPatternViewController.onViewAttached()
@@ -159,39 +158,37 @@
return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
}
- @Test
- fun withFeatureFlagOn_oldMessage_isHidden() {
- fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ @Test
+ fun withFeatureFlagOn_oldMessage_isHidden() {
+ fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
- mKeyguardPatternViewController.onViewAttached()
+ mKeyguardPatternViewController.onViewAttached()
- verify<KeyguardMessageAreaController<*>>(mKeyguardMessageAreaController).disable()
- }
+ verify<KeyguardMessageAreaController<*>>(mKeyguardMessageAreaController).disable()
+ }
- @Test
- fun onPause_resetsText() {
- mKeyguardPatternViewController.init()
- mKeyguardPatternViewController.onPause()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
- }
+ @Test
+ fun onPause_resetsText() {
+ mKeyguardPatternViewController.init()
+ mKeyguardPatternViewController.onPause()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
+ }
- @Test
- fun startAppearAnimation() {
- mKeyguardPatternViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController)
- .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false)
- }
+ @Test
+ fun testOnViewAttached() {
+ reset(mKeyguardMessageAreaController)
+ reset(mLockPatternUtils)
+ mKeyguardPatternViewController.onViewAttached()
+ verify(mKeyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false)
+ verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
+ }
- @Test
- fun startAppearAnimation_withExistingMessage() {
- `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- mKeyguardPatternViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
- }
-
- @Test
- fun resume() {
- mKeyguardPatternViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
- }
+ @Test
+ fun testOnViewAttached_withExistingMessage() {
+ reset(mKeyguardMessageAreaController)
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ mKeyguardPatternViewController.onViewAttached()
+ verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index cf86c21..efe1955 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -100,6 +100,8 @@
.thenReturn(mDeleteButton);
when(mPinBasedInputView.findViewById(R.id.key_enter))
.thenReturn(mOkButton);
+
+ when(mPinBasedInputView.getResources()).thenReturn(getContext().getResources());
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 309d9e0..80fd721 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -185,27 +185,27 @@
}
@Test
- fun startAppearAnimation() {
+ fun testOnViewAttached() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
- pinViewController.startAppearAnimation()
+ pinViewController.onViewAttached()
verify(keyguardMessageAreaController)
.setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
- fun startAppearAnimation_withExistingMessage() {
+ fun testOnViewAttached_withExistingMessage() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- pinViewController.startAppearAnimation()
+ pinViewController.onViewAttached()
verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
}
@Test
- fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
+ fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
@@ -213,7 +213,7 @@
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
`when`(passwordTextView.text).thenReturn("")
- pinViewController.startAppearAnimation()
+ pinViewController.onViewAttached()
verify(deleteButton).visibility = View.INVISIBLE
verify(enterButton).visibility = View.INVISIBLE
@@ -222,7 +222,7 @@
}
@Test
- fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+ fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
@@ -230,7 +230,7 @@
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
`when`(passwordTextView.text).thenReturn("")
- pinViewController.startAppearAnimation()
+ pinViewController.onViewAttached()
verify(deleteButton).visibility = View.VISIBLE
verify(enterButton).visibility = View.VISIBLE
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 17bb3d5..63c51e4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -64,6 +64,8 @@
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.GlobalSettings
@@ -577,18 +579,7 @@
ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
underTest.onViewAttached()
verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
- clearInvocations(viewFlipperController)
configurationListenerArgumentCaptor.value.onThemeChanged()
- verify(viewFlipperController).clearViews()
- verify(viewFlipperController)
- .asynchronouslyInflateView(
- eq(SecurityMode.PIN),
- any(),
- onViewInflatedCallbackArgumentCaptor.capture()
- )
- onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
- verify(view).reset()
- verify(viewFlipperController).reset()
verify(view).reloadColors()
}
@@ -598,16 +589,7 @@
ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
underTest.onViewAttached()
verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
- clearInvocations(viewFlipperController)
configurationListenerArgumentCaptor.value.onUiModeChanged()
- verify(viewFlipperController).clearViews()
- verify(viewFlipperController)
- .asynchronouslyInflateView(
- eq(SecurityMode.PIN),
- any(),
- onViewInflatedCallbackArgumentCaptor.capture()
- )
- onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
verify(view).reloadColors()
}
@@ -876,6 +858,17 @@
verify(userSwitcher).setAlpha(0f)
}
+ @Test
+ fun testOnUserSwitched() {
+ val userSwitchCallbackArgumentCaptor =
+ argumentCaptor<UserSwitcherController.UserSwitchCallback>()
+ underTest.onViewAttached()
+ verify(userSwitcherController)
+ .addUserSwitchCallback(capture(userSwitchCallbackArgumentCaptor))
+ userSwitchCallbackArgumentCaptor.value.onUserSwitched()
+ verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any())
+ }
+
private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
get() {
underTest.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index a3acc78..291dda25 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -97,6 +97,8 @@
@Test
fun onViewAttached() {
underTest.onViewAttached()
+ verify(keyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
@@ -120,8 +122,6 @@
@Test
fun startAppearAnimation() {
underTest.startAppearAnimation()
- verify(keyguardMessageAreaController)
- .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index efcf4dd..626faa6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -98,6 +98,8 @@
underTest.onViewAttached()
Mockito.verify(keyguardUpdateMonitor)
.registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
+ Mockito.verify(keyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
@@ -120,8 +122,6 @@
@Test
fun startAppearAnimation() {
underTest.startAppearAnimation()
- Mockito.verify(keyguardMessageAreaController)
- .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
index 7628be4..662c89c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
@@ -80,6 +80,7 @@
assertEquals(Intent.ACTION_EDIT, intent.getAction());
assertEquals("image/*", intent.getType());
assertEquals(null, intent.getComponent());
+ assertEquals("clipboard", intent.getStringExtra("edit_source"));
assertFlags(intent, EXTERNAL_INTENT_FLAGS);
// try again with an editor component
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
index 07cb5d8..6a17889 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -22,17 +22,14 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
-import com.android.internal.app.AssistUtils;
-import com.android.internal.app.IVisualQueryDetectionAttentionListener;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener;
import com.android.systemui.shared.condition.Condition;
import org.junit.Before;
@@ -50,9 +47,7 @@
@Mock
Condition.Callback mCallback;
@Mock
- AssistUtils mAssistUtils;
- @Mock
- DreamOverlayStateController mDreamOverlayStateController;
+ AssistManager mAssistManager;
@Mock
CoroutineScope mScope;
@@ -62,55 +57,34 @@
public void setup() {
MockitoAnnotations.initMocks(this);
- mAssistantAttentionCondition =
- new AssistantAttentionCondition(mScope, mDreamOverlayStateController, mAssistUtils);
+ mAssistantAttentionCondition = new AssistantAttentionCondition(mScope, mAssistManager);
// Adding a callback also starts the condition.
mAssistantAttentionCondition.addCallback(mCallback);
}
@Test
public void testEnableVisualQueryDetection() {
- final ArgumentCaptor<DreamOverlayStateController.Callback> argumentCaptor =
- ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
- verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
-
- when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
- argumentCaptor.getValue().onStateChanged();
-
- verify(mAssistUtils).enableVisualQueryDetection(any());
+ verify(mAssistManager).addVisualQueryAttentionListener(
+ any(VisualQueryAttentionListener.class));
}
@Test
public void testDisableVisualQueryDetection() {
- final ArgumentCaptor<DreamOverlayStateController.Callback> argumentCaptor =
- ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
- verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
-
- when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
- argumentCaptor.getValue().onStateChanged();
- when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(false);
- argumentCaptor.getValue().onStateChanged();
-
- verify(mAssistUtils).disableVisualQueryDetection();
+ mAssistantAttentionCondition.stop();
+ verify(mAssistManager).removeVisualQueryAttentionListener(
+ any(VisualQueryAttentionListener.class));
}
@Test
- public void testAttentionChangedTriggersCondition() throws RemoteException {
- final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
- ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
- verify(mDreamOverlayStateController).addCallback(callbackCaptor.capture());
+ public void testAttentionChangedTriggersCondition() {
+ final ArgumentCaptor<VisualQueryAttentionListener> argumentCaptor =
+ ArgumentCaptor.forClass(VisualQueryAttentionListener.class);
+ verify(mAssistManager).addVisualQueryAttentionListener(argumentCaptor.capture());
- when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
- callbackCaptor.getValue().onStateChanged();
-
- final ArgumentCaptor<IVisualQueryDetectionAttentionListener> listenerCaptor =
- ArgumentCaptor.forClass(IVisualQueryDetectionAttentionListener.class);
- verify(mAssistUtils).enableVisualQueryDetection(listenerCaptor.capture());
-
- listenerCaptor.getValue().onAttentionGained();
+ argumentCaptor.getValue().onAttentionGained();
assertThat(mAssistantAttentionCondition.isConditionMet()).isTrue();
- listenerCaptor.getValue().onAttentionLost();
+ argumentCaptor.getValue().onAttentionLost();
assertThat(mAssistantAttentionCondition.isConditionMet()).isFalse();
verify(mCallback, times(2)).onConditionChanged(eq(mAssistantAttentionCondition));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 16cc924..713c602 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -147,16 +147,4 @@
underTest.setVisible(true, "reason")
assertThat(isVisible).isTrue()
}
-
- @Test
- fun remoteUserInput() =
- testScope.runTest {
- val remoteUserInput by collectLastValue(underTest.remoteUserInput)
- assertThat(remoteUserInput).isNull()
-
- for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) {
- underTest.onRemoteUserInput(input)
- assertThat(remoteUserInput).isEqualTo(input)
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index da6c4269..88abb642 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -18,19 +18,14 @@
package com.android.systemui.scene.ui.viewmodel
-import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.shared.model.RemoteUserInput
-import com.android.systemui.scene.shared.model.RemoteUserInputAction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.currentTime
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -73,35 +68,4 @@
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
-
- @Test
- fun onRemoteUserInput() = runTest {
- val remoteUserInput by collectLastValue(underTest.remoteUserInput)
- assertThat(remoteUserInput).isNull()
-
- val inputs =
- SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE.map { remoteUserInputToMotionEvent(it) }
-
- inputs.forEachIndexed { index, input ->
- underTest.onRemoteUserInput(input)
- assertThat(remoteUserInput).isEqualTo(SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE[index])
- }
- }
-
- private fun TestScope.remoteUserInputToMotionEvent(input: RemoteUserInput): MotionEvent {
- return MotionEvent.obtain(
- currentTime,
- currentTime,
- when (input.action) {
- RemoteUserInputAction.DOWN -> MotionEvent.ACTION_DOWN
- RemoteUserInputAction.MOVE -> MotionEvent.ACTION_MOVE
- RemoteUserInputAction.UP -> MotionEvent.ACTION_UP
- RemoteUserInputAction.CANCEL -> MotionEvent.ACTION_CANCEL
- RemoteUserInputAction.UNKNOWN -> MotionEvent.ACTION_OUTSIDE
- },
- input.x,
- input.y,
- 0
- )
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
index 2d3ee0e..ca4486b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -20,12 +20,13 @@
import android.content.Context
import android.content.Intent
import android.net.Uri
-import androidx.test.ext.truth.content.IntentSubject.assertThat
+import androidx.test.ext.truth.content.IntentSubject.assertThat as assertThatIntent
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
import org.mockito.Mockito.`when` as whenever
@@ -39,23 +40,23 @@
val output = ActionIntentCreator.createShare(uri)
- assertThat(output).hasAction(Intent.ACTION_CHOOSER)
- assertThat(output)
+ assertThatIntent(output).hasAction(Intent.ACTION_CHOOSER)
+ assertThatIntent(output)
.hasFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK or
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
- assertThat(output).extras().parcelable<Intent>(Intent.EXTRA_INTENT).isNotNull()
+ assertThatIntent(output).extras().parcelable<Intent>(Intent.EXTRA_INTENT).isNotNull()
val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
- assertThat(wrappedIntent).hasAction(Intent.ACTION_SEND)
- assertThat(wrappedIntent).hasData(uri)
- assertThat(wrappedIntent).hasType("image/png")
- assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT)
- assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT)
- assertThat(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
+ assertThatIntent(wrappedIntent).hasAction(Intent.ACTION_SEND)
+ assertThatIntent(wrappedIntent).hasData(uri)
+ assertThatIntent(wrappedIntent).hasType("image/png")
+ assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT)
+ assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT)
+ assertThatIntent(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
}
@Test
@@ -64,7 +65,7 @@
val output = ActionIntentCreator.createShare(uri)
- assertThat(output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java))
+ assertThatIntent(output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java))
.hasData(Uri.parse("content://fake"))
}
@@ -75,8 +76,8 @@
val output = ActionIntentCreator.createShareWithSubject(uri, subject)
- assertThat(output).hasAction(Intent.ACTION_CHOOSER)
- assertThat(output)
+ assertThatIntent(output).hasAction(Intent.ACTION_CHOOSER)
+ assertThatIntent(output)
.hasFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK or
@@ -84,12 +85,12 @@
)
val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
- assertThat(wrappedIntent).hasAction(Intent.ACTION_SEND)
- assertThat(wrappedIntent).hasData(uri)
- assertThat(wrappedIntent).hasType("image/png")
- assertThat(wrappedIntent).extras().string(Intent.EXTRA_SUBJECT).isEqualTo(subject)
- assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT)
- assertThat(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
+ assertThatIntent(wrappedIntent).hasAction(Intent.ACTION_SEND)
+ assertThatIntent(wrappedIntent).hasData(uri)
+ assertThatIntent(wrappedIntent).hasType("image/png")
+ assertThatIntent(wrappedIntent).extras().string(Intent.EXTRA_SUBJECT).isEqualTo(subject)
+ assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT)
+ assertThatIntent(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
}
@Test
@@ -99,8 +100,8 @@
val output = ActionIntentCreator.createShareWithText(uri, extraText)
- assertThat(output).hasAction(Intent.ACTION_CHOOSER)
- assertThat(output)
+ assertThatIntent(output).hasAction(Intent.ACTION_CHOOSER)
+ assertThatIntent(output)
.hasFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK or
@@ -108,12 +109,12 @@
)
val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
- assertThat(wrappedIntent).hasAction(Intent.ACTION_SEND)
- assertThat(wrappedIntent).hasData(uri)
- assertThat(wrappedIntent).hasType("image/png")
- assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT)
- assertThat(wrappedIntent).extras().string(Intent.EXTRA_TEXT).isEqualTo(extraText)
- assertThat(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
+ assertThatIntent(wrappedIntent).hasAction(Intent.ACTION_SEND)
+ assertThatIntent(wrappedIntent).hasData(uri)
+ assertThatIntent(wrappedIntent).hasType("image/png")
+ assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT)
+ assertThatIntent(wrappedIntent).extras().string(Intent.EXTRA_TEXT).isEqualTo(extraText)
+ assertThatIntent(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
}
@Test
@@ -125,11 +126,12 @@
val output = ActionIntentCreator.createEdit(uri, context)
- assertThat(output).hasAction(Intent.ACTION_EDIT)
- assertThat(output).hasData(uri)
- assertThat(output).hasType("image/png")
+ assertThatIntent(output).hasAction(Intent.ACTION_EDIT)
+ assertThatIntent(output).hasData(uri)
+ assertThatIntent(output).hasType("image/png")
assertWithMessage("getComponent()").that(output.component).isNull()
- assertThat(output)
+ assertThat(output.getStringExtra("edit_source")).isEqualTo("screenshot")
+ assertThatIntent(output)
.hasFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
@@ -146,7 +148,7 @@
val output = ActionIntentCreator.createEdit(uri, context)
- assertThat(output).hasData(Uri.parse("content://fake"))
+ assertThatIntent(output).hasData(Uri.parse("content://fake"))
}
@Test
@@ -160,6 +162,6 @@
val output = ActionIntentCreator.createEdit(uri, context)
- assertThat(output).hasComponent(component)
+ assertThatIntent(output).hasComponent(component)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 4a94dc8..38a8f414 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -21,11 +21,21 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
import org.junit.Assert
import org.junit.Before
import org.junit.Test
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
@SmallTest
@@ -36,13 +46,43 @@
private val groupMembershipManager: GroupMembershipManager = mock()
private val featureFlags = FakeFeatureFlags()
- private val entry1 = NotificationEntryBuilder().build()
- private val entry2 = NotificationEntryBuilder().build()
+ private val pipeline: NotifPipeline = mock()
+ private lateinit var beforeRenderListListener: OnBeforeRenderListListener
+
+ private val summary1 = notificationEntry("foo", 1)
+ private val summary2 = notificationEntry("bar", 1)
+ private val entries =
+ listOf<ListEntry>(
+ GroupEntryBuilder()
+ .setSummary(summary1)
+ .setChildren(
+ listOf(
+ notificationEntry("foo", 2),
+ notificationEntry("foo", 3),
+ notificationEntry("foo", 4)
+ )
+ )
+ .build(),
+ GroupEntryBuilder()
+ .setSummary(summary2)
+ .setChildren(
+ listOf(
+ notificationEntry("bar", 2),
+ notificationEntry("bar", 3),
+ notificationEntry("bar", 4)
+ )
+ )
+ .build(),
+ notificationEntry("baz", 1)
+ )
+
+ private fun notificationEntry(pkg: String, id: Int) =
+ NotificationEntryBuilder().setPkg(pkg).setId(id).build().apply { row = mock() }
@Before
fun setUp() {
- whenever(groupMembershipManager.getGroupSummary(entry1)).thenReturn(entry1)
- whenever(groupMembershipManager.getGroupSummary(entry2)).thenReturn(entry2)
+ whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
+ whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags)
}
@@ -54,15 +94,15 @@
var listenerCalledCount = 0
gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
- gem.setGroupExpanded(entry1, false)
+ gem.setGroupExpanded(summary1, false)
Assert.assertEquals(0, listenerCalledCount)
- gem.setGroupExpanded(entry1, true)
+ gem.setGroupExpanded(summary1, true)
Assert.assertEquals(1, listenerCalledCount)
- gem.setGroupExpanded(entry2, true)
+ gem.setGroupExpanded(summary2, true)
Assert.assertEquals(2, listenerCalledCount)
- gem.setGroupExpanded(entry1, true)
+ gem.setGroupExpanded(summary1, true)
Assert.assertEquals(2, listenerCalledCount)
- gem.setGroupExpanded(entry2, false)
+ gem.setGroupExpanded(summary2, false)
Assert.assertEquals(3, listenerCalledCount)
}
@@ -73,15 +113,39 @@
var listenerCalledCount = 0
gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
- gem.setGroupExpanded(entry1, false)
+ gem.setGroupExpanded(summary1, false)
Assert.assertEquals(1, listenerCalledCount)
- gem.setGroupExpanded(entry1, true)
+ gem.setGroupExpanded(summary1, true)
Assert.assertEquals(2, listenerCalledCount)
- gem.setGroupExpanded(entry2, true)
+ gem.setGroupExpanded(summary2, true)
Assert.assertEquals(3, listenerCalledCount)
- gem.setGroupExpanded(entry1, true)
+ gem.setGroupExpanded(summary1, true)
Assert.assertEquals(4, listenerCalledCount)
- gem.setGroupExpanded(entry2, false)
+ gem.setGroupExpanded(summary2, false)
Assert.assertEquals(5, listenerCalledCount)
}
+
+ @Test
+ fun testSyncWithPipeline() {
+ featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+ gem.attach(pipeline)
+ beforeRenderListListener = withArgCaptor {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ val listener: OnGroupExpansionChangeListener = mock()
+ gem.registerGroupExpansionChangeListener(listener)
+
+ beforeRenderListListener.onBeforeRenderList(entries)
+ verify(listener, never()).onGroupExpansionChange(any(), any())
+
+ // Expand one of the groups.
+ gem.setGroupExpanded(summary1, true)
+ verify(listener).onGroupExpansionChange(summary1.row, true)
+
+ // Empty the pipeline list and verify that the group is no longer expanded.
+ beforeRenderListListener.onBeforeRenderList(emptyList())
+ verify(listener).onGroupExpansionChange(summary1.row, false)
+ verifyNoMoreInteractions(listener)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 2e92bb9..0b31523 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -27,7 +27,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
@@ -77,7 +77,7 @@
@Mock
private lateinit var shadeControllerImpl: ShadeControllerImpl
@Mock
- private lateinit var sceneInteractor: Provider<SceneInteractor>
+ private lateinit var windowRootView: Provider<WindowRootView>
@Mock
private lateinit var shadeLogger: ShadeLogger
@Mock
@@ -203,7 +203,7 @@
centralSurfacesImpl,
shadeControllerImpl,
shadeViewController,
- sceneInteractor,
+ windowRootView,
shadeLogger,
viewUtil,
configurationController,
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 823155b..b8f2cab 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
@@ -81,7 +81,7 @@
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
-import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -136,8 +136,8 @@
private StatusBarWindowStateController mStatusBarWindowStateController;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @ClassRule
- public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index ef39ff8..79feb41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -82,7 +82,7 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -111,8 +111,8 @@
private BlockingQueueIntentReceiver mReceiver;
private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
- @ClassRule
- public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
@Before
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 0663004..69d7586 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -40,7 +40,6 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.CaptioningManager;
import androidx.test.filters.SmallTest;
@@ -64,6 +63,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.Executor;
+
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper
@@ -96,8 +97,6 @@
@Mock
private WakefulnessLifecycle mWakefullnessLifcycle;
@Mock
- private CaptioningManager mCaptioningManager;
- @Mock
private KeyguardManager mKeyguardManager;
@Mock
private ActivityManager mActivityManager;
@@ -117,6 +116,7 @@
when(mRingerModeLiveData.getValue()).thenReturn(-1);
when(mRingerModeInternalLiveData.getValue()).thenReturn(-1);
when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+ when(mUserTracker.getUserContext()).thenReturn(mContext);
// Enable group volume adjustments
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions,
@@ -127,7 +127,7 @@
mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
- mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mKeyguardManager,
+ mPackageManager, mWakefullnessLifcycle, mKeyguardManager,
mActivityManager, mUserTracker, mDumpManager, mCallback);
mVolumeController.setEnableDialogs(true, true);
}
@@ -219,6 +219,11 @@
verify(mRingerModeInternalLiveData).observeForever(any());
}
+ @Test
+ public void testAddCallbackWithUserTracker() {
+ verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class));
+ }
+
static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl {
private final WakefulnessLifecycle.Observer mWakefullessLifecycleObserver;
@@ -234,7 +239,6 @@
AccessibilityManager accessibilityManager,
PackageManager packageManager,
WakefulnessLifecycle wakefulnessLifecycle,
- CaptioningManager captioningManager,
KeyguardManager keyguardManager,
ActivityManager activityManager,
UserTracker userTracker,
@@ -242,7 +246,7 @@
C callback) {
super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
notificationManager, optionalVibrator, iAudioService, accessibilityManager,
- packageManager, wakefulnessLifecycle, captioningManager, keyguardManager,
+ packageManager, wakefulnessLifecycle, keyguardManager,
activityManager, userTracker, dumpManager);
mCallbacks = callback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index fa18e57..21e4f5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -31,6 +31,7 @@
import static org.junit.Assume.assumeNotNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -38,7 +39,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.animation.AnimatorTestRule;
import android.app.KeyguardManager;
import android.content.res.Configuration;
import android.media.AudioManager;
@@ -85,14 +85,14 @@
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class VolumeDialogImplTest extends SysuiTestCase {
- private static final AnimatorTestRule sAnimatorTestRule = new AnimatorTestRule();
-
VolumeDialogImpl mDialog;
View mActiveRinger;
View mDrawerContainer;
View mDrawerVibrate;
View mDrawerMute;
View mDrawerNormal;
+ CaptionsToggleImageButton mODICaptionsIcon;
+
private TestableLooper mTestableLooper;
private ConfigurationController mConfigurationController;
private int mOriginalOrientation;
@@ -180,6 +180,7 @@
mDrawerVibrate = mDrawerContainer.findViewById(R.id.volume_drawer_vibrate);
mDrawerMute = mDrawerContainer.findViewById(R.id.volume_drawer_mute);
mDrawerNormal = mDrawerContainer.findViewById(R.id.volume_drawer_normal);
+ mODICaptionsIcon = mDialog.getDialogView().findViewById(R.id.odi_captions_icon);
Prefs.putInt(mContext,
Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT,
@@ -688,6 +689,28 @@
assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.CLOSE);
}
+ @Test
+ public void testOnCaptionEnabledStateChanged_checkBeforeSwitchTrue_setCaptionsEnabledState() {
+ ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture =
+ ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
+ verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any());
+ VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();
+
+ callbacks.onCaptionEnabledStateChanged(true, true);
+ verify(mVolumeDialogController).setCaptionsEnabledState(eq(false));
+ }
+
+ @Test
+ public void testOnCaptionEnabledStateChanged_checkBeforeSwitchFalse_getCaptionsEnabledTrue() {
+ ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture =
+ ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
+ verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any());
+ VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();
+
+ callbacks.onCaptionEnabledStateChanged(true, false);
+ assertTrue(mODICaptionsIcon.getCaptionsEnabled());
+ }
+
/**
* The content description should include ringer state, and the correct one.
*/
@@ -727,7 +750,6 @@
public void teardown() {
cleanUp(mDialog);
setOrientation(mOriginalOrientation);
- sAnimatorTestRule.advanceTimeBy(mLongestHideShowAnimationDuration);
mTestableLooper.moveTimeForward(mLongestHideShowAnimationDuration);
mTestableLooper.processAllMessages();
reset(mPostureController);
diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
index 19c68e8..41dbc14 100644
--- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
+++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
@@ -49,7 +49,7 @@
* public class SampleAnimatorTest {
*
* {@literal @}Rule
- * public AnimatorTestRule sAnimatorTestRule = new AnimatorTestRule();
+ * public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
*
* {@literal @}UiThreadTest
* {@literal @}Test
@@ -58,7 +58,7 @@
* animator.setDuration(1000L);
* assertThat(animator.getAnimatedValue(), is(0));
* animator.start();
- * sAnimatorTestRule.advanceTimeBy(500L);
+ * mAnimatorTestRule.advanceTimeBy(500L);
* assertThat(animator.getAnimatedValue(), is(500));
* }
* }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index dd45331..f0e1111 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -39,8 +39,6 @@
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.RemoteUserInput
-import com.android.systemui.scene.shared.model.RemoteUserInputAction
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -196,15 +194,6 @@
}
companion object {
- val REMOTE_INPUT_DOWN_GESTURE =
- listOf(
- RemoteUserInput(10f, 10f, RemoteUserInputAction.DOWN),
- RemoteUserInput(10f, 20f, RemoteUserInputAction.MOVE),
- RemoteUserInput(10f, 30f, RemoteUserInputAction.MOVE),
- RemoteUserInput(10f, 40f, RemoteUserInputAction.MOVE),
- RemoteUserInput(10f, 40f, RemoteUserInputAction.UP),
- )
-
fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel {
return when (this) {
DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
index 137a418..e109cc8 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStats.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
@@ -22,6 +22,8 @@
*/
public class AuthenticationStats {
+ private static final float FRR_NOT_ENOUGH_ATTEMPTS = -1.0f;
+
private final int mUserId;
private int mTotalAttempts;
private int mRejectedAttempts;
@@ -70,7 +72,7 @@
if (mTotalAttempts > 0) {
return mRejectedAttempts / (float) mTotalAttempts;
} else {
- return -1.0f;
+ return FRR_NOT_ENOUGH_ATTEMPTS;
}
}
@@ -87,4 +89,32 @@
mTotalAttempts = 0;
mRejectedAttempts = 0;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof AuthenticationStats)) {
+ return false;
+ }
+
+ AuthenticationStats target = (AuthenticationStats) obj;
+ return this.getUserId() == target.getUserId()
+ && this.getTotalAttempts()
+ == target.getTotalAttempts()
+ && this.getRejectedAttempts()
+ == target.getRejectedAttempts()
+ && this.getEnrollmentNotifications()
+ == target.getEnrollmentNotifications()
+ && this.getModality() == target.getModality();
+ }
+
+ @Override
+ public int hashCode() {
+ return String.format("userId: %d, totalAttempts: %d, rejectedAttempts: %d, "
+ + "enrollmentNotifications: %d, modality: %d", mUserId, mTotalAttempts,
+ mRejectedAttempts, mEnrollmentNotifications, mModality).hashCode();
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index c9cd814..85125d2 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -37,23 +37,42 @@
// The minimum number of attempts that will calculate the FRR and trigger the notification.
private static final int MINIMUM_ATTEMPTS = 500;
+ // Upload the data every 50 attempts (average number of daily authentications).
+ private static final int AUTHENTICATION_UPLOAD_INTERVAL = 50;
// The maximum number of eligible biometric enrollment notification can be sent.
private static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
+ @NonNull private final Context mContext;
+
private final float mThreshold;
private final int mModality;
@NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
+ @NonNull private AuthenticationStatsPersister mAuthenticationStatsPersister;
+
public AuthenticationStatsCollector(@NonNull Context context, int modality) {
+ mContext = context;
mThreshold = context.getResources()
.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1);
mUserAuthenticationStatsMap = new HashMap<>();
mModality = modality;
}
+ private void initializeUserAuthenticationStatsMap() {
+ mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext);
+ for (AuthenticationStats stats : mAuthenticationStatsPersister.getAllFrrStats(mModality)) {
+ mUserAuthenticationStatsMap.put(stats.getUserId(), stats);
+ }
+ }
+
/** Update total authentication and rejected attempts. */
public void authenticate(int userId, boolean authenticated) {
+ // Initialize mUserAuthenticationStatsMap when authenticate to ensure SharedPreferences
+ // are ready for application use and avoid ramdump issue.
+ if (mUserAuthenticationStatsMap.isEmpty()) {
+ initializeUserAuthenticationStatsMap();
+ }
// Check if this is a new user.
if (!mUserAuthenticationStatsMap.containsKey(userId)) {
mUserAuthenticationStatsMap.put(userId, new AuthenticationStats(userId, mModality));
@@ -82,8 +101,12 @@
private void persistDataIfNeeded(int userId) {
AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
- if (authenticationStats.getTotalAttempts() % 50 == 0) {
- // TODO(wenhuiy): Persist data.
+ if (authenticationStats.getTotalAttempts() % AUTHENTICATION_UPLOAD_INTERVAL == 0) {
+ mAuthenticationStatsPersister.persistFrrStats(authenticationStats.getUserId(),
+ authenticationStats.getTotalAttempts(),
+ authenticationStats.getRejectedAttempts(),
+ authenticationStats.getEnrollmentNotifications(),
+ authenticationStats.getModality());
}
}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
new file mode 100644
index 0000000..96150a6
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.os.Environment;
+import android.util.Slog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Persists and retrieves stats for Biometric Authentication.
+ * Authentication stats include userId, total attempts, rejected attempts,
+ * and the number of sent enrollment notifications.
+ * Data are stored in SharedPreferences in a form of a set of JSON objects,
+ * where it's one element per user.
+ */
+public class AuthenticationStatsPersister {
+
+ private static final String TAG = "AuthenticationStatsPersister";
+ private static final String FILE_NAME = "authentication_stats";
+ private static final String USER_ID = "user_id";
+ private static final String FACE_ATTEMPTS = "face_attempts";
+ private static final String FACE_REJECTIONS = "face_rejections";
+ private static final String FINGERPRINT_ATTEMPTS = "fingerprint_attempts";
+ private static final String FINGERPRINT_REJECTIONS = "fingerprint_rejections";
+ private static final String ENROLLMENT_NOTIFICATIONS = "enrollment_notifications";
+ private static final String KEY = "frr_stats";
+
+ @NonNull private final SharedPreferences mSharedPreferences;
+
+ AuthenticationStatsPersister(@NonNull Context context) {
+ // The package info in the context isn't initialized in the way it is for normal apps,
+ // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+ // build the path manually below using the same policy that appears in ContextImpl.
+ final File prefsFile = new File(Environment.getDataSystemDeDirectory(), FILE_NAME);
+ mSharedPreferences = context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Get all frr data from SharedPreference.
+ */
+ public List<AuthenticationStats> getAllFrrStats(int modality) {
+ List<AuthenticationStats> authenticationStatsList = new ArrayList<>();
+ for (String frrStats : readFrrStats()) {
+ try {
+ JSONObject frrStatsJson = new JSONObject(frrStats);
+ if (modality == BiometricsProtoEnums.MODALITY_FACE) {
+ authenticationStatsList.add(new AuthenticationStats(
+ getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */),
+ getIntValue(frrStatsJson, FACE_ATTEMPTS),
+ getIntValue(frrStatsJson, FACE_REJECTIONS),
+ getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS),
+ modality));
+ } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+ authenticationStatsList.add(new AuthenticationStats(
+ getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */),
+ getIntValue(frrStatsJson, FINGERPRINT_ATTEMPTS),
+ getIntValue(frrStatsJson, FINGERPRINT_REJECTIONS),
+ getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS),
+ modality));
+ }
+ } catch (JSONException e) {
+ Slog.w(TAG, String.format("Unable to resolve authentication stats JSON: %s",
+ frrStats));
+ }
+ }
+ return authenticationStatsList;
+ }
+
+ /**
+ * Persist frr data for a specific user.
+ */
+ public void persistFrrStats(int userId, int totalAttempts, int rejectedAttempts,
+ int enrollmentNotifications, int modality) {
+ try {
+ // Copy into a new HashSet to avoid iterator exception.
+ Set<String> frrStatsSet = new HashSet<>(readFrrStats());
+
+ // Remove the old authentication stat for the user if it exists.
+ JSONObject frrStatJson = null;
+ for (Iterator<String> iterator = frrStatsSet.iterator(); iterator.hasNext();) {
+ String frrStats = iterator.next();
+ frrStatJson = new JSONObject(frrStats);
+ if (getValue(frrStatJson, USER_ID).equals(String.valueOf(userId))) {
+ iterator.remove();
+ break;
+ }
+ }
+
+ // If there's existing frr stats in the file, we want to update the stats for the given
+ // modality and keep the stats for other modalities.
+ if (frrStatJson != null) {
+ frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts,
+ enrollmentNotifications, modality));
+ } else {
+ frrStatsSet.add(buildFrrStats(userId, totalAttempts, rejectedAttempts,
+ enrollmentNotifications, modality));
+ }
+
+ mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
+
+ } catch (JSONException e) {
+ Slog.e(TAG, "Unable to persist authentication stats");
+ }
+ }
+
+ private Set<String> readFrrStats() {
+ return mSharedPreferences.getStringSet(KEY, Set.of());
+ }
+
+ // Update frr stats for existing frrStats JSONObject and build the new string.
+ private String buildFrrStats(JSONObject frrStats, int totalAttempts, int rejectedAttempts,
+ int enrollmentNotifications, int modality) throws JSONException {
+ if (modality == BiometricsProtoEnums.MODALITY_FACE) {
+ return frrStats
+ .put(FACE_ATTEMPTS, totalAttempts)
+ .put(FACE_REJECTIONS, rejectedAttempts)
+ .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+ .toString();
+ } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+ return frrStats
+ .put(FINGERPRINT_ATTEMPTS, totalAttempts)
+ .put(FINGERPRINT_REJECTIONS, rejectedAttempts)
+ .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+ .toString();
+ } else {
+ return frrStats.toString();
+ }
+ }
+
+ // Build string for new user and new authentication stats.
+ private String buildFrrStats(int userId, int totalAttempts, int rejectedAttempts,
+ int enrollmentNotifications, int modality)
+ throws JSONException {
+ if (modality == BiometricsProtoEnums.MODALITY_FACE) {
+ return new JSONObject()
+ .put(USER_ID, userId)
+ .put(FACE_ATTEMPTS, totalAttempts)
+ .put(FACE_REJECTIONS, rejectedAttempts)
+ .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+ .toString();
+ } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+ return new JSONObject()
+ .put(USER_ID, userId)
+ .put(FINGERPRINT_ATTEMPTS, totalAttempts)
+ .put(FINGERPRINT_REJECTIONS, rejectedAttempts)
+ .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+ .toString();
+ } else {
+ return "";
+ }
+ }
+
+ private String getValue(JSONObject jsonObject, String key) throws JSONException {
+ return jsonObject.has(key) ? jsonObject.getString(key) : "";
+ }
+
+ private int getIntValue(JSONObject jsonObject, String key) throws JSONException {
+ return getIntValue(jsonObject, key, 0 /* defaultValue */);
+ }
+
+ private int getIntValue(JSONObject jsonObject, String key, int defaultValue)
+ throws JSONException {
+ return jsonObject.has(key) ? jsonObject.getInt(key) : defaultValue;
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2a617c5..6509126 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -6132,6 +6132,8 @@
mVisibilityStateComputer.dump(pw);
p.println(" mInFullscreenMode=" + mInFullscreenMode);
p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
+ p.println(" ENABLE_HIDE_IME_CAPTION_BAR="
+ + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR);
p.println(" mSettingsObserver=" + mSettingsObserver);
p.println(" mStylusIds=" + (mStylusIds != null
? Arrays.toString(mStylusIds.toArray()) : ""));
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 20595ea..73fdfe0 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -25,6 +25,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.gui.StalledTransactionInfo;
import android.os.Debug;
import android.os.IBinder;
import android.util.Slog;
@@ -96,7 +97,7 @@
@Override
public void notifyNoFocusedWindowAnr(@NonNull InputApplicationHandle applicationHandle) {
TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(
- timeoutMessage("Application does not have a focused window"));
+ timeoutMessage(OptionalInt.empty(), "Application does not have a focused window"));
mService.mAnrController.notifyAppUnresponsive(applicationHandle, timeoutRecord);
}
@@ -104,7 +105,7 @@
public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
String reason) {
TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
- timeoutMessage(reason));
+ timeoutMessage(pid, reason));
mService.mAnrController.notifyWindowUnresponsive(token, pid, timeoutRecord);
}
@@ -354,11 +355,21 @@
mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
}
- private String timeoutMessage(String reason) {
- if (reason == null) {
- return "Input dispatching timed out";
+ private String timeoutMessage(OptionalInt pid, String reason) {
+ String message = (reason == null) ? "Input dispatching timed out."
+ : String.format("Input dispatching timed out (%s).", reason);
+ if (pid.isEmpty()) {
+ return message;
}
- return "Input dispatching timed out (" + reason + ")";
+ StalledTransactionInfo stalledTransactionInfo =
+ SurfaceControl.getStalledTransactionInfo(pid.getAsInt());
+ if (stalledTransactionInfo == null) {
+ return message;
+ }
+ return String.format("%s Buffer processing for the associated surface is stuck due to an "
+ + "unsignaled fence (window=%s, bufferId=0x%016X, frameNumber=%s). This "
+ + "potentially indicates a GPU hang.", message, stalledTransactionInfo.layerName,
+ stalledTransactionInfo.bufferId, stalledTransactionInfo.frameNumber);
}
void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index ba242ec..01786be 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -969,8 +969,10 @@
final Rect innerFrame = hasInheritedLetterboxBehavior()
? mActivityRecord.getBounds() : w.getFrame();
mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
- // We need to notify Shell that letterbox position has changed.
- mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
+ if (mDoubleTapEvent) {
+ // We need to notify Shell that letterbox position has changed.
+ mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
+ }
} else if (mLetterbox != null) {
mLetterbox.hide();
}
@@ -1242,6 +1244,7 @@
? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
: LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
logLetterboxPositionChange(changeToLog);
+ 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(
@@ -1252,8 +1255,8 @@
? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
: LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
logLetterboxPositionChange(changeToLog);
+ mDoubleTapEvent = true;
}
- mDoubleTapEvent = true;
// TODO(197549949): Add animation for transition.
mActivityRecord.recomputeConfiguration();
}
@@ -1281,6 +1284,7 @@
? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
: LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
logLetterboxPositionChange(changeToLog);
+ 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(
@@ -1291,8 +1295,8 @@
? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
: LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
logLetterboxPositionChange(changeToLog);
+ mDoubleTapEvent = true;
}
- mDoubleTapEvent = true;
// TODO(197549949): Add animation for transition.
mActivityRecord.recomputeConfiguration();
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8fbaac2..0f0189e61 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3459,6 +3459,8 @@
info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.isUserFullscreenOverrideEnabled = top != null
+ && top.mLetterboxUiController.shouldApplyUserFullscreenOverride();
info.isFromLetterboxDoubleTap = top != null && top.mLetterboxUiController.isFromDoubleTap();
if (info.isLetterboxDoubleTapEnabled) {
info.topActivityLetterboxWidth = top.getBounds().width();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 57ce368..f340c26 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1184,7 +1184,7 @@
}
final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
- boolean deferPause) {
+ boolean skipPause) {
ActivityRecord next = topRunningActivity(true /* focusableOnly */);
if (next == null || !next.canResumeByCompat()) {
return false;
@@ -1192,11 +1192,9 @@
next.delayedResume = false;
- // If we are currently pausing an activity, then don't do anything until that is done.
- final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
- if (!allPausedComplete) {
- ProtoLog.v(WM_DEBUG_STATES,
- "resumeTopActivity: Skip resume: some activity pausing.");
+ if (!skipPause && !mRootWindowContainer.allPausedActivitiesComplete()) {
+ // If we aren't skipping pause, then we have to wait for currently pausing activities.
+ ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: some activity pausing.");
return false;
}
@@ -1260,7 +1258,7 @@
lastResumed = lastFocusedRootTask.getTopResumedActivity();
}
- boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
+ boolean pausing = !skipPause && taskDisplayArea.pauseBackTasks(next);
if (mResumedActivity != null) {
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Pausing %s", mResumedActivity);
pausing |= startPausing(mTaskSupervisor.mUserLeaving, false /* uiSleeping */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index 99d66c5..a578f9a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -16,11 +16,18 @@
package com.android.server.biometrics;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
+import static java.util.Collections.emptySet;
+
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.res.Resources;
import com.android.internal.R;
@@ -30,6 +37,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.File;
+
public class AuthenticationStatsCollectorTest {
private AuthenticationStatsCollector mAuthenticationStatsCollector;
@@ -40,6 +49,8 @@
private Context mContext;
@Mock
private Resources mResources;
+ @Mock
+ private SharedPreferences mSharedPreferences;
@Before
public void setUp() {
@@ -48,6 +59,9 @@
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
.thenReturn(FRR_THRESHOLD);
+ when(mContext.getSharedPreferences(any(File.class), anyInt()))
+ .thenReturn(mSharedPreferences);
+ when(mSharedPreferences.getStringSet(anyString(), anySet())).thenReturn(emptySet());
mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
0 /* modality */);
@@ -57,30 +71,31 @@
@Test
public void authenticate_authenticationSucceeded_mapShouldBeUpdated() {
// Assert that the user doesn't exist in the map initially.
- assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+ assertThat(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1)).isNull();
mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated*/);
AuthenticationStats authenticationStats =
mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
- assertEquals(USER_ID_1, authenticationStats.getUserId());
- assertEquals(1, authenticationStats.getTotalAttempts());
- assertEquals(0, authenticationStats.getRejectedAttempts());
- assertEquals(0, authenticationStats.getEnrollmentNotifications());
+ assertThat(authenticationStats.getUserId()).isEqualTo(USER_ID_1);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(1);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
}
@Test
public void authenticate_authenticationFailed_mapShouldBeUpdated() {
// Assert that the user doesn't exist in the map initially.
- assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+ assertThat(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1)).isNull();
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated*/);
AuthenticationStats authenticationStats =
mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
- assertEquals(USER_ID_1, authenticationStats.getUserId());
- assertEquals(1, authenticationStats.getTotalAttempts());
- assertEquals(1, authenticationStats.getRejectedAttempts());
- assertEquals(0, authenticationStats.getEnrollmentNotifications());
+
+ assertThat(authenticationStats.getUserId()).isEqualTo(USER_ID_1);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(1);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(1);
+ assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
new file mode 100644
index 0000000..455625c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.biometrics.BiometricsProtoEnums;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+public class AuthenticationStatsPersisterTest {
+
+ @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ private static final int USER_ID_1 = 1;
+ private static final int USER_ID_2 = 2;
+ private static final String USER_ID = "user_id";
+ private static final String FACE_ATTEMPTS = "face_attempts";
+ private static final String FACE_REJECTIONS = "face_rejections";
+ private static final String FINGERPRINT_ATTEMPTS = "fingerprint_attempts";
+ private static final String FINGERPRINT_REJECTIONS = "fingerprint_rejections";
+ private static final String ENROLLMENT_NOTIFICATIONS = "enrollment_notifications";
+ private static final String KEY = "frr_stats";
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private SharedPreferences mSharedPreferences;
+ @Mock
+ private SharedPreferences.Editor mEditor;
+ private AuthenticationStatsPersister mAuthenticationStatsPersister;
+
+ @Captor
+ private ArgumentCaptor<Set<String>> mStringSetArgumentCaptor;
+
+ @Before
+ public void setUp() {
+ when(mContext.getSharedPreferences(any(File.class), anyInt()))
+ .thenReturn(mSharedPreferences);
+ when(mSharedPreferences.edit()).thenReturn(mEditor);
+ when(mEditor.putStringSet(anyString(), anySet())).thenReturn(mEditor);
+
+ mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext);
+ }
+
+ @Test
+ public void getAllFrrStats_face_shouldListAllFrrStats() throws JSONException {
+ AuthenticationStats stats1 = new AuthenticationStats(USER_ID_1,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ AuthenticationStats stats2 = new AuthenticationStats(USER_ID_2,
+ 200 /* totalAttempts */, 20 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+ Set.of(buildFrrStats(stats1), buildFrrStats(stats2)));
+
+ List<AuthenticationStats> authenticationStatsList =
+ mAuthenticationStatsPersister.getAllFrrStats(BiometricsProtoEnums.MODALITY_FACE);
+
+ assertThat(authenticationStatsList.size()).isEqualTo(2);
+ AuthenticationStats expectedStats2 = new AuthenticationStats(USER_ID_2,
+ 0 /* totalAttempts */, 0 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ assertThat(authenticationStatsList).contains(stats1);
+ assertThat(authenticationStatsList).contains(expectedStats2);
+ }
+
+ @Test
+ public void getAllFrrStats_fingerprint_shouldListAllFrrStats() throws JSONException {
+ // User 1 with fingerprint authentication stats.
+ AuthenticationStats stats1 = new AuthenticationStats(USER_ID_1,
+ 200 /* totalAttempts */, 20 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ // User 2 without fingerprint authentication stats.
+ AuthenticationStats stats2 = new AuthenticationStats(USER_ID_2,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+ Set.of(buildFrrStats(stats1), buildFrrStats(stats2)));
+
+ List<AuthenticationStats> authenticationStatsList =
+ mAuthenticationStatsPersister
+ .getAllFrrStats(BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
+ assertThat(authenticationStatsList.size()).isEqualTo(2);
+ AuthenticationStats expectedStats2 = new AuthenticationStats(USER_ID_2,
+ 0 /* totalAttempts */, 0 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ assertThat(authenticationStatsList).contains(stats1);
+ assertThat(authenticationStatsList).contains(expectedStats2);
+ }
+
+ @Test
+ public void persistFrrStats_newUser_face_shouldSuccess() throws JSONException {
+ AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+
+ mAuthenticationStatsPersister.persistFrrStats(authenticationStats.getUserId(),
+ authenticationStats.getTotalAttempts(),
+ authenticationStats.getRejectedAttempts(),
+ authenticationStats.getEnrollmentNotifications(),
+ authenticationStats.getModality());
+
+ verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+ assertThat(mStringSetArgumentCaptor.getValue())
+ .contains(buildFrrStats(authenticationStats));
+ }
+
+ @Test
+ public void persistFrrStats_newUser_fingerprint_shouldSuccess() throws JSONException {
+ AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
+ mAuthenticationStatsPersister.persistFrrStats(authenticationStats.getUserId(),
+ authenticationStats.getTotalAttempts(),
+ authenticationStats.getRejectedAttempts(),
+ authenticationStats.getEnrollmentNotifications(),
+ authenticationStats.getModality());
+
+ verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+ assertThat(mStringSetArgumentCaptor.getValue())
+ .contains(buildFrrStats(authenticationStats));
+ }
+
+ @Test
+ public void persistFrrStats_existingUser_shouldUpdateRecord() throws JSONException {
+ AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ AuthenticationStats newAuthenticationStats = new AuthenticationStats(USER_ID_1,
+ 500 /* totalAttempts */, 30 /* rejectedAttempts */,
+ 1 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+ Set.of(buildFrrStats(authenticationStats)));
+
+ mAuthenticationStatsPersister.persistFrrStats(newAuthenticationStats.getUserId(),
+ newAuthenticationStats.getTotalAttempts(),
+ newAuthenticationStats.getRejectedAttempts(),
+ newAuthenticationStats.getEnrollmentNotifications(),
+ newAuthenticationStats.getModality());
+
+ verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+ assertThat(mStringSetArgumentCaptor.getValue())
+ .contains(buildFrrStats(newAuthenticationStats));
+ }
+
+ @Test
+ public void persistFrrStats_existingUserWithFingerprint_faceAuthenticate_shouldUpdateRecord()
+ throws JSONException {
+ // User with fingerprint authentication stats.
+ AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+ 200 /* totalAttempts */, 20 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ // The same user with face authentication stats.
+ AuthenticationStats newAuthenticationStats = new AuthenticationStats(USER_ID_1,
+ 500 /* totalAttempts */, 30 /* rejectedAttempts */,
+ 1 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+ Set.of(buildFrrStats(authenticationStats)));
+
+ mAuthenticationStatsPersister.persistFrrStats(newAuthenticationStats.getUserId(),
+ newAuthenticationStats.getTotalAttempts(),
+ newAuthenticationStats.getRejectedAttempts(),
+ newAuthenticationStats.getEnrollmentNotifications(),
+ newAuthenticationStats.getModality());
+
+ String expectedFrrStats = new JSONObject(buildFrrStats(authenticationStats))
+ .put(ENROLLMENT_NOTIFICATIONS, newAuthenticationStats.getEnrollmentNotifications())
+ .put(FACE_ATTEMPTS, newAuthenticationStats.getTotalAttempts())
+ .put(FACE_REJECTIONS, newAuthenticationStats.getRejectedAttempts()).toString();
+ verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+ assertThat(mStringSetArgumentCaptor.getValue()).contains(expectedFrrStats);
+ }
+
+ private String buildFrrStats(AuthenticationStats authenticationStats)
+ throws JSONException {
+ if (authenticationStats.getModality() == BiometricsProtoEnums.MODALITY_FACE) {
+ return new JSONObject()
+ .put(USER_ID, authenticationStats.getUserId())
+ .put(FACE_ATTEMPTS, authenticationStats.getTotalAttempts())
+ .put(FACE_REJECTIONS, authenticationStats.getRejectedAttempts())
+ .put(ENROLLMENT_NOTIFICATIONS, authenticationStats.getEnrollmentNotifications())
+ .toString();
+ } else if (authenticationStats.getModality() == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+ return new JSONObject()
+ .put(USER_ID, authenticationStats.getUserId())
+ .put(FINGERPRINT_ATTEMPTS, authenticationStats.getTotalAttempts())
+ .put(FINGERPRINT_REJECTIONS, authenticationStats.getRejectedAttempts())
+ .put(ENROLLMENT_NOTIFICATIONS, authenticationStats.getEnrollmentNotifications())
+ .toString();
+ }
+ return "";
+ }
+}
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 72b5159..1d423ca 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -39,7 +39,11 @@
"src/**/activityembedding/*.kt",
"src/**/activityembedding/open/*.kt",
"src/**/activityembedding/close/*.kt",
+ "src/**/activityembedding/layoutchange/*.kt",
+ "src/**/activityembedding/pip/*.kt",
"src/**/activityembedding/rotation/*.kt",
+ "src/**/activityembedding/rtl/*.kt",
+ "src/**/activityembedding/splitscreen/*.kt",
],
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
index 4530ef3..0c36c59 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
@@ -38,7 +38,7 @@
* Setup: Launch A|B in split with B being the secondary activity. Transitions: Finish B and expect
* A to become fullscreen.
*
- * To run this test: `atest FlickerTests:CloseSecondaryActivityInSplitTest`
+ * To run this test: `atest FlickerTestsOther:CloseSecondaryActivityInSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
index badd876..cfc0d8b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.layoutchange
import android.platform.test.annotations.Presubmit
import android.tools.common.datatypes.Rect
@@ -22,6 +22,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import androidx.test.filters.RequiresDevice
import org.junit.FixMethodOrder
@@ -38,7 +39,7 @@
* Transitions:
* Change the split ratio to A:B=0.7:0.3, expect bounds change for both A and B.
*
- * To run this test: `atest FlickerTests:HorizontalSplitChangeRatioTest`
+ * To run this test: `atest FlickerTestsOther:HorizontalSplitChangeRatioTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
index 0f406fd..b0ae738 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -39,7 +39,7 @@
* Setup: Launch A|B in split with B being the secondary activity. Transitions: A start C with
* alwaysExpand=true, expect C to launch in fullscreen and cover split A|B.
*
- * To run this test: `atest FlickerTests:MainActivityStartsSecondaryWithAlwaysExpandTest`
+ * To run this test: `atest FlickerTestsOther:MainActivityStartsSecondaryWithAlwaysExpandTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
index 8a997dd..48edf6d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
@@ -34,7 +34,7 @@
* Test opening an activity that will launch another activity as ActivityEmbedding placeholder in
* split.
*
- * To run this test: `atest FlickerTests:OpenActivityEmbeddingPlaceholderSplitTest`
+ * To run this test: `atest FlickerTestsOther:OpenActivityEmbeddingPlaceholderSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
index 49aa84b..3657820 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
@@ -34,7 +34,7 @@
/**
* Test opening a secondary activity that will split with the main activity.
*
- * To run this test: `atest FlickerTests:OpenActivityEmbeddingSecondaryToSplitTest`
+ * To run this test: `atest FlickerTestsOther:OpenActivityEmbeddingSecondaryToSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
index 404f329..5551aa6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
@@ -40,7 +40,7 @@
*
* Transitions: Let B start C, expect C to cover B and end up in split A|C.
*
- * To run this test: `atest FlickerTests:OpenThirdActivityOverSplitTest`
+ * To run this test: `atest FlickerTestsOther:OpenThirdActivityOverSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index d3001d8..6432f1f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.open
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
@@ -27,6 +27,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -42,7 +43,7 @@
* Transitions: From A launch a trampoline Activity T, T launches secondary Activity B and
* finishes itself, end up in split A|B.
*
- * To run this test: `atest FlickerTests:OpenTrampolineActivityTest`
+ * To run this test: `atest FlickerTestsOther:OpenTrampolineActivityTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index 0417f9d..9ad3edd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.pip
import android.platform.test.annotations.Presubmit
import android.tools.common.datatypes.Rect
@@ -25,6 +25,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -38,7 +39,7 @@
* Setup: Start from a split A|B.
* Transition: B enters PIP, observe the window shrink to the bottom right corner on screen.
*
- * To run this test: `atest FlickerTests:SecondaryActivityEnterPipTest`
+ * To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest`
*
*/
@RequiresDevice
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
index da56500..4f7d8a4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
@@ -23,6 +23,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import com.android.server.wm.flicker.rotation.RotationTransition
import org.junit.FixMethodOrder
@@ -37,7 +38,7 @@
* Setup: Launch A|B in split with B being the secondary activity. Transitions: Rotate display, and
* expect A and B to split evenly in new rotation.
*
- * To run this test: `atest FlickerTests:RotateSplitNoChangeTest`
+ * To run this test: `atest FlickerTestsOther:RotateSplitNoChangeTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/RTLStartSecondaryWithPlaceholderTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/RTLStartSecondaryWithPlaceholderTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt
index 4bc17ed..6be78f8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/RTLStartSecondaryWithPlaceholderTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.rtl
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -22,6 +22,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -36,7 +37,7 @@
* PlaceholderPrimary, which is configured to launch with PlaceholderSecondary in RTL. Expect split
* PlaceholderSecondary|PlaceholderPrimary covering split B|A.
*
- * To run this test: `atest FlickerTests:RTLStartSecondaryWithPlaceholderTest`
+ * To run this test: `atest FlickerTestsOther:RTLStartSecondaryWithPlaceholderTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
index 5ad3ede..c828de9f 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
@@ -196,7 +196,7 @@
public String toString() {
return new StringBuilder("SharedConnectivitySettingsState[")
.append("instantTetherEnabled=").append(mInstantTetherEnabled)
- .append("PendingIntent=").append(mInstantTetherSettingsPendingIntent.toString())
+ .append("PendingIntent=").append(mInstantTetherSettingsPendingIntent)
.append("extras=").append(mExtras.toString())
.append("]").toString();
}