Merge "Enable user of UserWakeupStore only on devices that support multiuser." into main
diff --git a/BAL_OWNERS b/BAL_OWNERS
index d56a1d4..ec779e7 100644
--- a/BAL_OWNERS
+++ b/BAL_OWNERS
@@ -2,4 +2,6 @@
achim@google.com
topjohnwu@google.com
lus@google.com
+haok@google.com
+wnan@google.com
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index bf67187..0a3ae4f 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -16,9 +16,9 @@
package android.platform.coverage
+import com.android.tools.metalava.model.CallableItem
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.Item
-import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.text.ApiFile
import java.io.File
import java.io.FileWriter
@@ -40,24 +40,24 @@
fun extractFlaggedApisFromClass(
classItem: ClassItem,
- methods: List<MethodItem>,
+ callables: List<CallableItem>,
packageName: String,
builder: FlagApiMap.Builder
) {
- if (methods.isEmpty()) return
+ if (callables.isEmpty()) return
val classFlag = getClassFlag(classItem)
- for (method in methods) {
- val methodFlag = getFlagAnnotation(method) ?: classFlag
+ for (callable in callables) {
+ val callableFlag = getFlagAnnotation(callable) ?: classFlag
val api =
JavaMethod.newBuilder()
.setPackageName(packageName)
.setClassName(classItem.fullName())
- .setMethodName(method.name())
- for (param in method.parameters()) {
+ .setMethodName(callable.name())
+ for (param in callable.parameters()) {
api.addParameters(param.type().toTypeString())
}
- if (methodFlag != null) {
- addFlaggedApi(builder, api, methodFlag)
+ if (callableFlag != null) {
+ addFlaggedApi(builder, api, callableFlag)
}
}
}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 978a8f9..e3dbb2b 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -747,6 +747,10 @@
* {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList}.
* </p>
*
+ * <p>This function returns an empty array if
+ * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}
+ * is not supported.</p>
+ *
* @return an array of supported high speed video recording sizes
* @see #getHighSpeedVideoFpsRangesFor(Size)
* @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
@@ -836,6 +840,10 @@
* supported for the same recording rate.</li>
* </p>
*
+ * <p>This function returns an empty array if
+ * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}
+ * is not supported.</p>
+ *
* @return an array of supported high speed video recording FPS ranges The upper bound of
* returned ranges is guaranteed to be larger or equal to 120.
* @see #getHighSpeedVideoSizesFor
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 0321e1df..97f6899 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1669,6 +1669,46 @@
}
/**
+ * Gets the mapping between the doze brightness sensor values and brightness values. The doze
+ * brightness sensor is a light sensor used to determine the brightness while the device is
+ * dozing. Light sensor values are typically integers in the rage of 0-4. The returned values
+ * are between {@link PowerManager#BRIGHTNESS_MIN} and {@link PowerManager#BRIGHTNESS_MAX}, or
+ * -1 meaning that the current brightness should be kept.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId The ID of the display
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ return mGlobal.getDozeBrightnessSensorValueToBrightness(displayId);
+ }
+
+ /**
+ * Gets the default doze brightness.
+ * The returned values are between {@link PowerManager#BRIGHTNESS_MIN} and
+ * {@link PowerManager#BRIGHTNESS_MAX}.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId The ID of the display
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @FloatRange(from = 0f, to = 1f)
+ public float getDefaultDozeBrightness(int displayId) {
+ return mGlobal.getDefaultDozeBrightness(displayId);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index e9cd37a..cae33d0 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -21,6 +21,7 @@
import static android.view.Display.HdrCapabilities.HdrType;
import android.Manifest;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1226,6 +1227,32 @@
}
}
+ /**
+ * @see DisplayManager#getDozeBrightnessSensorValueToBrightness
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ try {
+ return mDm.getDozeBrightnessSensorValueToBrightness(displayId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see DisplayManager#getDefaultDozeBrightness
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @FloatRange(from = 0f, to = 1f)
+ public float getDefaultDozeBrightness(int displayId) {
+ try {
+ return mDm.getDefaultDozeBrightness(displayId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, @DisplayEvent int event) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 77277ee..f3c21e9f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -246,4 +246,12 @@
// Restricts display modes to specified modeIds.
@EnforcePermission("RESTRICT_DISPLAY_MODES")
void requestDisplayModes(in IBinder token, int displayId, in @nullable int[] modeIds);
+
+ // Get the mapping between the doze brightness sensor values and brightness values
+ @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
+ float[] getDozeBrightnessSensorValueToBrightness(int displayId);
+
+ // Get the default doze brightness
+ @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
+ float getDefaultDozeBrightness(int displayId);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 23e262c..d7952eb 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1251,8 +1251,22 @@
* canceled. Only the pilfering window will continue to receive events for the affected pointers
* until the pointer is lifted.
*
- * This method should be used with caution as unexpected pilfering can break fundamental user
- * interactions.
+ * Furthermore, if any new pointers go down within the touchable region of the pilfering window
+ * and are part of the same gesture, those new pointers will be pilfered as well, and will not
+ * be sent to any other windows.
+ *
+ * Pilfering is designed to be used only once per gesture. Once the gesture is complete
+ * (i.e. on {@link MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_CANCEL},
+ * or {@link MotionEvent#ACTION_HOVER_EXIT}), the system will resume dispatching pointers
+ * to the appropriately touched windows.
+ *
+ * NOTE: This method should be used with caution as unexpected pilfering can break fundamental
+ * user interactions.
+ *
+ * NOTE: Since this method pilfers pointers based on gesture stream that is
+ * currently active for the window, the behavior will depend on the state of the system, and
+ * is inherently racy. For example, a pilfer request on a quick tap may not be successful if
+ * the tap is already complete by the time the pilfer request is received by the system.
*
* @see android.os.InputConfig#SPY
* @hide
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index e2d215e..4bc5bd2 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -29,6 +29,7 @@
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
import android.window.WindowProviderService;
@@ -186,6 +187,10 @@
if (callback != null) {
callback.finishedEvent(seq, handled);
}
+ if (Flags.imeSwitcherRevamp() && !handled && event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getUnicodeChar() > 0 && mInputMethodServiceInternal != null) {
+ mInputMethodServiceInternal.notifyUserActionIfNecessary();
+ }
}
/**
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 943b04f..855c309 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -16,7 +16,6 @@
package android.inputmethodservice;
-import static android.view.inputmethod.Flags.predictiveBackIme;
import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VIEW_STARTED;
import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VISIBILITY;
import static android.inputmethodservice.InputMethodServiceProto.CONFIGURATION;
@@ -57,6 +56,7 @@
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
import static android.view.inputmethod.Flags.ctrlShiftShortcut;
+import static android.view.inputmethod.Flags.predictiveBackIme;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -4341,6 +4341,16 @@
}
/**
+ * Called when the IME switch button was clicked from the client. This will show the input
+ * method picker dialog.
+ *
+ * @hide
+ */
+ final void onImeSwitchButtonClickFromClient() {
+ mPrivOps.onImeSwitchButtonClickFromClient(getDisplayId());
+ }
+
+ /**
* Used to inject custom {@link InputMethodServiceInternal}.
*
* @return the {@link InputMethodServiceInternal} to be used.
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index de67e06..3ce67b0 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -42,6 +42,7 @@
import android.view.WindowInsetsController.Appearance;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
@@ -145,7 +146,8 @@
return mImpl.toDebugString();
}
- private static final class Impl implements Callback, Window.DecorCallback {
+ private static final class Impl implements Callback, Window.DecorCallback,
+ NavigationBarView.ButtonClickListener {
private static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700;
// Copied from com.android.systemui.animation.Interpolators#LEGACY_DECELERATE
@@ -241,6 +243,7 @@
? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN
: 0);
navigationBarView.setNavigationIconHints(hints);
+ navigationBarView.prepareNavButtons(this);
}
} else {
mNavigationBarFrame.setLayoutParams(new FrameLayout.LayoutParams(
@@ -592,6 +595,17 @@
return drawLegacyNavigationBarBackground;
}
+ @Override
+ public void onImeSwitchButtonClick(View v) {
+ mService.onImeSwitchButtonClickFromClient();
+ }
+
+ @Override
+ public boolean onImeSwitchButtonLongClick(View v) {
+ v.getContext().getSystemService(InputMethodManager.class).showInputMethodPicker();
+ return true;
+ }
+
/**
* Returns the height of the IME caption bar if this should be shown, or {@code 0} instead.
*/
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
index f423672..540243c 100644
--- a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
@@ -41,6 +41,7 @@
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputConnection;
import android.widget.ImageView;
@@ -58,12 +59,30 @@
private int mTouchDownY;
private AudioManager mAudioManager;
private boolean mGestureAborted;
+ /**
+ * Whether the long click action has been invoked. The short click action is invoked on the up
+ * event while a long click is invoked as soon as the long press duration is reached, so a long
+ * click could be performed before the short click is checked, in which case the short click's
+ * action should not be invoked.
+ *
+ * @see View#mHasPerformedLongPress
+ */
+ private boolean mLongClicked;
private OnClickListener mOnClickListener;
private final KeyButtonRipple mRipple;
private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private float mDarkIntensity;
private boolean mHasOvalBg = false;
+ /** Runnable for checking whether the long click action should be performed. */
+ private final Runnable mCheckLongPress = new Runnable() {
+ public void run() {
+ if (isPressed() && performLongClick()) {
+ mLongClicked = true;
+ }
+ }
+ };
+
public KeyButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -159,6 +178,7 @@
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownTime = SystemClock.uptimeMillis();
+ mLongClicked = false;
setPressed(true);
// Use raw X and Y to detect gestures in case a parent changes the x and y values
@@ -173,6 +193,10 @@
if (!showSwipeUI) {
playSoundEffect(SoundEffectConstants.CLICK);
}
+ if (Flags.imeSwitcherRevamp() && isLongClickable()) {
+ removeCallbacks(mCheckLongPress);
+ postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
+ }
break;
case MotionEvent.ACTION_MOVE:
x = (int) ev.getRawX();
@@ -183,6 +207,9 @@
// When quick step is enabled, prevent animating the ripple triggered by
// setPressed and decide to run it on touch up
setPressed(false);
+ if (isLongClickable()) {
+ removeCallbacks(mCheckLongPress);
+ }
}
break;
case MotionEvent.ACTION_CANCEL:
@@ -190,9 +217,12 @@
if (mCode != KEYCODE_UNKNOWN) {
sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
}
+ if (isLongClickable()) {
+ removeCallbacks(mCheckLongPress);
+ }
break;
case MotionEvent.ACTION_UP:
- final boolean doIt = isPressed();
+ final boolean doIt = isPressed() && !mLongClicked;
setPressed(false);
final boolean doHapticFeedback = (SystemClock.uptimeMillis() - mDownTime) > 150;
if (showSwipeUI) {
@@ -201,7 +231,7 @@
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
playSoundEffect(SoundEffectConstants.CLICK);
}
- } else if (doHapticFeedback) {
+ } else if (doHapticFeedback && !mLongClicked) {
// Always send a release ourselves because it doesn't seem to be sent elsewhere
// and it feels weird to sometimes get a release haptic and other times not.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
@@ -221,6 +251,9 @@
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
}
}
+ if (isLongClickable()) {
+ removeCallbacks(mCheckLongPress);
+ }
break;
}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
index e28f345..a3beaf4 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -26,6 +26,7 @@
import android.animation.PropertyValuesHolder;
import android.annotation.DrawableRes;
import android.annotation.FloatRange;
+import android.annotation.NonNull;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
@@ -39,6 +40,7 @@
import android.view.View;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
@@ -79,6 +81,28 @@
private NavigationBarInflaterView mNavigationInflaterView;
+ /**
+ * Interface definition for callbacks to be invoked when navigation bar buttons are clicked.
+ */
+ public interface ButtonClickListener {
+
+ /**
+ * Called when the IME switch button is clicked.
+ *
+ * @param v The view that was clicked.
+ */
+ void onImeSwitchButtonClick(View v);
+
+ /**
+ * Called when the IME switch button has been clicked and held.
+ *
+ * @param v The view that was clicked and held.
+ *
+ * @return true if the callback consumed the long click, false otherwise.
+ */
+ boolean onImeSwitchButtonLongClick(View v);
+ }
+
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -98,13 +122,27 @@
new ButtonDispatcher(com.android.internal.R.id.input_method_nav_home_handle));
mDeadZone = new android.inputmethodservice.navigationbar.DeadZone(this);
+ }
+ /**
+ * Prepares the navigation bar buttons to be used and sets the on click listeners.
+ *
+ * @param listener The listener used to handle the clicks on the navigation bar buttons.
+ */
+ public void prepareNavButtons(@NonNull ButtonClickListener listener) {
getBackButton().setLongClickable(false);
- final ButtonDispatcher imeSwitchButton = getImeSwitchButton();
- imeSwitchButton.setLongClickable(false);
- imeSwitchButton.setOnClickListener(view -> view.getContext()
- .getSystemService(InputMethodManager.class).showInputMethodPicker());
+ if (Flags.imeSwitcherRevamp()) {
+ final var imeSwitchButton = getImeSwitchButton();
+ imeSwitchButton.setLongClickable(true);
+ imeSwitchButton.setOnClickListener(listener::onImeSwitchButtonClick);
+ imeSwitchButton.setOnLongClickListener(listener::onImeSwitchButtonLongClick);
+ } else {
+ final ButtonDispatcher imeSwitchButton = getImeSwitchButton();
+ imeSwitchButton.setLongClickable(false);
+ imeSwitchButton.setOnClickListener(view -> view.getContext()
+ .getSystemService(InputMethodManager.class).showInputMethodPicker());
+ }
}
@Override
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 8b1577c..97993b6 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -41,5 +41,5 @@
// There is no order guarantee with respect to the two-way APIs above like
// vibrate/isVibrating/cancel.
oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
- boolean always, String reason, boolean fromIme);
+ String reason, int flags, int privFlags);
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 406a1a6..026013c 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -564,8 +564,7 @@
BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM,
BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM,
BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT,
- BRIGHTNESS_CONSTRAINT_TYPE_DIM,
- BRIGHTNESS_CONSTRAINT_TYPE_DOZE
+ BRIGHTNESS_CONSTRAINT_TYPE_DIM
})
@Retention(RetentionPolicy.SOURCE)
public @interface BrightnessConstraint{}
@@ -594,12 +593,6 @@
public static final int BRIGHTNESS_CONSTRAINT_TYPE_DIM = 3;
/**
- * Brightness constraint type: minimum allowed value.
- * @hide
- */
- public static final int BRIGHTNESS_CONSTRAINT_TYPE_DOZE = 4;
-
- /**
* @hide
*/
@IntDef(prefix = { "WAKE_REASON_" }, value = {
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 2a62c24..5339d73 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -206,13 +206,12 @@
}
@Override
- public void performHapticFeedback(
- int constant, boolean always, String reason, boolean fromIme) {
+ public void performHapticFeedback(int constant, String reason, int flags, int privFlags) {
if (mVibratorManager == null) {
Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager.");
return;
}
- mVibratorManager.performHapticFeedback(constant, always, reason, fromIme);
+ mVibratorManager.performHapticFeedback(constant, reason, flags, privFlags);
}
@Override
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index c80bcac..a9846ba 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -147,15 +147,14 @@
}
@Override
- public void performHapticFeedback(int constant, boolean always, String reason,
- boolean fromIme) {
+ public void performHapticFeedback(int constant, String reason, int flags, int privFlags) {
if (mService == null) {
Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service.");
return;
}
try {
- mService.performHapticFeedback(
- mUid, mContext.getDeviceId(), mPackageName, constant, always, reason, fromIme);
+ mService.performHapticFeedback(mUid, mContext.getDeviceId(), mPackageName, constant,
+ reason, flags, privFlags);
} catch (RemoteException e) {
Log.w(TAG, "Failed to perform haptic feedback.", e);
}
@@ -245,9 +244,8 @@
}
@Override
- public void performHapticFeedback(int effectId, boolean always, String reason,
- boolean fromIme) {
- SystemVibratorManager.this.performHapticFeedback(effectId, always, reason, fromIme);
+ public void performHapticFeedback(int effectId, String reason, int flags, int privFlags) {
+ SystemVibratorManager.this.performHapticFeedback(effectId, reason, flags, privFlags);
}
@Override
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 8af371c..71c83f2 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -33,6 +33,7 @@
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibratorFrequencyProfile;
import android.util.Log;
+import android.view.HapticFeedbackConstants;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -519,17 +520,15 @@
*
* @param constant the ID for the haptic feedback. This should be one of the constants defined
* in {@link HapticFeedbackConstants}.
- * @param always {@code true} if the haptic feedback should be played regardless of the user
- * vibration intensity settings applicable to the corresponding vibration.
- * {@code false} if the vibration for the haptic feedback should respect the applicable
- * vibration intensity settings.
* @param reason the reason for this haptic feedback.
- * @param fromIme the haptic feedback is performed from an IME.
+ * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+ * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
*
* @hide
*/
- public void performHapticFeedback(int constant, boolean always, String reason,
- boolean fromIme) {
+ public void performHapticFeedback(int constant, String reason,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
Log.w(TAG, "performHapticFeedback is not supported");
}
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 513c4bd..2c7a852 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -23,6 +23,7 @@
import android.app.ActivityThread;
import android.content.Context;
import android.util.Log;
+import android.view.HapticFeedbackConstants;
/**
* Provides access to all vibrators from the device, as well as the ability to run them
@@ -142,15 +143,14 @@
*
* @param constant the ID of the requested haptic feedback. Should be one of the constants
* defined in {@link HapticFeedbackConstants}.
- * @param always {@code true} if the haptic feedback should be played regardless of the user
- * vibration intensity settings applicable to the corresponding vibration.
- * {@code false} otherwise.
* @param reason the reason for this haptic feedback.
- * @param fromIme the haptic feedback is performed from an IME.
+ * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+ * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
* @hide
*/
- public void performHapticFeedback(int constant, boolean always, String reason,
- boolean fromIme) {
+ public void performHapticFeedback(int constant, String reason,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
Log.w(TAG, "performHapticFeedback is not supported");
}
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index c73a422..ad2f59d 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -46,6 +46,7 @@
flag {
namespace: "haptics"
name: "vibration_xml_apis"
+ is_exported: true
description: "Enabled System APIs for vibration effect XML parser and serializer"
bug: "347273158"
metadata {
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 3954bc2..f6f0eff 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -92,3 +92,10 @@
description: "Add a dump capability for attestation_verification service"
bug: "335498868"
}
+
+flag {
+ name: "should_trust_manager_listen_for_primary_auth"
+ namespace: "biometrics"
+ description: "Causes TrustManagerService to listen for credential attempts and ignore reports from upstream"
+ bug: "323086607"
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 0714285..d8a88b8 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -138,12 +138,6 @@
"settings_show_stylus_preferences";
/**
- * Flag to enable/disable biometrics enrollment v2
- * @hide
- */
- public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
-
- /**
* Flag to enable/disable FingerprintSettings v2
* @hide
*/
@@ -223,7 +217,6 @@
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "true");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
- DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 69228ca..1fe06d4 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -16,11 +16,30 @@
package android.view;
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Constants to be used to perform haptic feedback effects via
* {@link View#performHapticFeedback(int)}
*/
public class HapticFeedbackConstants {
+ /** @hide **/
+ @IntDef(flag = true, prefix = "FLAG_", value = {
+ FLAG_IGNORE_VIEW_SETTING,
+ FLAG_IGNORE_GLOBAL_SETTING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flags {}
+
+ /** @hide **/
+ @IntDef(flag = true, prefix = "PRIVATE_FLAG_", value = {
+ PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PrivateFlags {}
private HapticFeedbackConstants() {}
@@ -258,4 +277,14 @@
*/
@Deprecated
public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002;
+
+ /**
+ * Flag for {@link android.os.Vibrator#performHapticFeedback(int, boolean, String, int, int)} or
+ * {@link ViewRootImpl#performHapticFeedback(int, boolean, int, int)}: Perform the haptic
+ * feedback with the input method vibration settings, e.g. applying the keyboard vibration
+ * user settings to the KEYBOARD_* constants.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS = 0x0001;
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 070d33b..14407ca 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -140,13 +140,13 @@
int seqId);
@UnsupportedAppUsage
- boolean performHapticFeedback(int effectId, boolean always, boolean fromIme);
+ boolean performHapticFeedback(int effectId, int flags, int privFlags);
/**
* Called by attached views to perform predefined haptic feedback without requiring VIBRATE
* permission.
*/
- oneway void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme);
+ oneway void performHapticFeedbackAsync(int effectId, int flags, int privFlags);
/**
* Initiate the drag operation itself
diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java
index 2302dc7..ea4abc1 100644
--- a/core/java/android/view/InputMonitor.java
+++ b/core/java/android/view/InputMonitor.java
@@ -50,13 +50,12 @@
private final SurfaceControl mSurface;
/**
- * Takes all of the current pointer events streams that are currently being sent to this
- * monitor and generates appropriate cancellations for the windows that would normally get
- * them.
+ * Pilfer pointers from this input monitor.
*
- * This method should be used with caution as unexpected pilfering can break fundamental user
- * interactions.
+ * @see android.hardware.input.InputManager#pilferPointers(IBinder)
+ * @deprecated
*/
+ @Deprecated
public void pilferPointers() {
try {
mHost.pilferPointers();
@@ -197,10 +196,10 @@
};
@DataClass.Generated(
- time = 1679692514588L,
+ time = 1720819824835L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
- inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\nprivate final @android.annotation.NonNull android.view.SurfaceControl mSurface\npublic void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
+ inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\nprivate final @android.annotation.NonNull android.view.SurfaceControl mSurface\npublic @java.lang.Deprecated void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 88d7a83..2edbcc2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28636,20 +28636,20 @@
return false;
}
- final boolean always = (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
- boolean fromIme = false;
- if (mAttachInfo.mViewRootImpl != null) {
- fromIme = mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD;
+ int privFlags = 0;
+ if (mAttachInfo.mViewRootImpl != null
+ && mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD) {
+ privFlags = HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS;
}
if (Flags.useVibratorHapticFeedback()) {
if (!mAttachInfo.canPerformHapticFeedback()) {
return false;
}
- getSystemVibrator().performHapticFeedback(
- feedbackConstant, always, "View#performHapticFeedback", fromIme);
+ getSystemVibrator().performHapticFeedback(feedbackConstant,
+ "View#performHapticFeedback", flags, privFlags);
return true;
}
- return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, always, fromIme);
+ return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, flags, privFlags);
}
private Vibrator getSystemVibrator() {
@@ -31684,7 +31684,10 @@
interface Callbacks {
void playSoundEffect(int effectId);
- boolean performHapticFeedback(int effectId, boolean always, boolean fromIme);
+
+ boolean performHapticFeedback(int effectId,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags);
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e5b17c8..596726f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9666,18 +9666,18 @@
* {@inheritDoc}
*/
@Override
- public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
+ public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
if ((mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
return false;
}
try {
if (USE_ASYNC_PERFORM_HAPTIC_FEEDBACK) {
- mWindowSession.performHapticFeedbackAsync(effectId, always, fromIme);
+ mWindowSession.performHapticFeedbackAsync(effectId, flags, privFlags);
return true;
} else {
// Original blocking binder call path.
- return mWindowSession.performHapticFeedback(effectId, always, fromIme);
+ return mWindowSession.performHapticFeedback(effectId, flags, privFlags);
}
} catch (RemoteException e) {
return false;
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 55f22a6..7871858 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -503,13 +503,13 @@
}
@Override
- public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
+ public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
return false;
}
@Override
- public void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme) {
- performHapticFeedback(effectId, always, fromIme);
+ public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
+ performHapticFeedback(effectId, flags, privFlags);
}
@Override
diff --git a/core/java/android/view/accessibility/a11ychecker/Android.bp b/core/java/android/view/accessibility/a11ychecker/Android.bp
deleted file mode 100644
index e5a577c..0000000
--- a/core/java/android/view/accessibility/a11ychecker/Android.bp
+++ /dev/null
@@ -1,7 +0,0 @@
-java_library_static {
- name: "A11yChecker",
- srcs: [
- "*.java",
- ],
- visibility: ["//visibility:public"],
-}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index e7a2fb9..07a9794 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -465,6 +465,20 @@
}
@AnyThread
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ static void onImeSwitchButtonClickFromSystem(int displayId) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.onImeSwitchButtonClickFromSystem(displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
@Nullable
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0c63e58..dbbfff0 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -4354,6 +4354,19 @@
}
/**
+ * Called when the IME switch button was clicked from the system. This will show the input
+ * method picker dialog.
+ *
+ * @param displayId The ID of the display where the input method picker dialog should be shown.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void onImeSwitchButtonClickFromSystem(int displayId) {
+ IInputMethodManagerGlobalInvoker.onImeSwitchButtonClickFromSystem(displayId);
+ }
+
+ /**
* A test API for CTS to check whether there are any pending IME visibility requests.
*
* @return {@code true} iff there are pending IME visibility requests.
diff --git a/core/java/android/window/ActivityWindowInfo.java b/core/java/android/window/ActivityWindowInfo.java
index 946bb82..71c500c 100644
--- a/core/java/android/window/ActivityWindowInfo.java
+++ b/core/java/android/window/ActivityWindowInfo.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityThread;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
@@ -144,4 +146,15 @@
+ ", taskFragmentBounds=" + mTaskFragmentBounds
+ "}";
}
+
+ /** Gets the {@link ActivityWindowInfo} of the given activity. */
+ @Nullable
+ public static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
+ if (activity.isFinishing()) {
+ return null;
+ }
+ final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
+ .getActivityClient(activity.getActivityToken());
+ return record != null ? record.getActivityWindowInfo() : null;
+ }
}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 2c64b8e..ac57c00 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -39,12 +39,6 @@
void unregisterOrganizer(in ITaskFragmentOrganizer organizer);
/**
- * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
- * only occupies a portion of Task bounds.
- */
- boolean isActivityEmbedded(in IBinder activityToken);
-
- /**
* Notifies the server that the organizer has finished handling the given transaction. The
* server should apply the given {@link WindowContainerTransaction} for the necessary changes.
*/
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 15f1258..8e429cb 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -21,6 +21,7 @@
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.ActivityWindowInfo.getActivityWindowInfo;
import android.annotation.CallSuper;
import android.annotation.FlaggedApi;
@@ -29,6 +30,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.app.Activity;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -38,6 +40,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -324,16 +327,15 @@
}
/**
- * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+ * Checks if an activity is organized by a {@link android.window.TaskFragmentOrganizer} and
* only occupies a portion of Task bounds.
+ *
+ * @see ActivityWindowInfo for additional window info.
* @hide
*/
- // TODO(b/287582673): cleanup
- public boolean isActivityEmbedded(@NonNull IBinder activityToken) {
- try {
- return getController().isActivityEmbedded(activityToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ public static boolean isActivityEmbedded(@NonNull Activity activity) {
+ Objects.requireNonNull(activity);
+ final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+ return activityWindowInfo != null && activityWindowInfo.isEmbedded();
}
}
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 739cf0e..0d5e37e 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -75,8 +75,8 @@
/**
* The core implementation to obtain {@link WindowMetrics}
*
- * @param isMaximum {@code true} to obtain {@link WindowManager#getCurrentWindowMetrics()}.
- * {@code false} to obtain {@link WindowManager#getMaximumWindowMetrics()}.
+ * @param isMaximum {@code false} to obtain {@link WindowManager#getCurrentWindowMetrics()}.
+ * {@code true} to obtain {@link WindowManager#getMaximumWindowMetrics()}.
*/
private WindowMetrics getWindowMetricsInternal(boolean isMaximum) {
final Rect bounds;
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index cc880e1..48fb2b3 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -91,6 +91,13 @@
}
flag {
+ name: "camera_compat_fullscreen_pick_same_task_activity"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Limit undo of camera compat treatment to the same task that started the treatment."
+ bug: "350495350"
+}
+
+flag {
name: "app_compat_refactoring"
namespace: "large_screen_experiences_app_compat"
description: "Whether the changes about app compat refactoring are enabled./n"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 1362f7b..3f1c06a 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -176,3 +176,10 @@
description: "Enables desktop windowing app handle education"
bug: "348208342"
}
+
+flag {
+ name: "enable_compat_ui_visibility_status"
+ namespace: "lse_desktop_experience"
+ description: "Enables the tracking of the status for compat ui elements."
+ bug: "350953004"
+}
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 63623c7..ac4c066 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -43,6 +43,7 @@
void switchToPreviousInputMethod(in AndroidFuture future /* T=Boolean */);
void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */);
void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */);
+ void onImeSwitchButtonClickFromClient(int displayId);
void notifyUserActionAsync();
void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
in ImeTracker.Token statsToken);
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 72c41be..2daf0fd 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -378,6 +378,22 @@
}
/**
+ * Calls {@link IInputMethodPrivilegedOperations#onImeSwitchButtonClickFromClient(int)}
+ */
+ @AnyThread
+ public void onImeSwitchButtonClickFromClient(int displayId) {
+ final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
+ if (ops == null) {
+ return;
+ }
+ try {
+ ops.onImeSwitchButtonClickFromClient(displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Calls {@link IInputMethodPrivilegedOperations#notifyUserActionAsync()}
*/
@AnyThread
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 2b3ffeb2..cba27ce 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -135,6 +135,19 @@
+ "android.Manifest.permission.TEST_INPUT_METHOD)")
boolean isInputMethodPickerShownForTest();
+ /**
+ * Called when the IME switch button was clicked from the system. Depending on the number of
+ * enabled IME subtypes, this will either switch to the next IME/subtype, or show the input
+ * method picker dialog.
+ *
+ * @param displayId The ID of the display where the input method picker dialog should be shown.
+ * @param userId The ID of the user that triggered the click.
+ */
+ @EnforcePermission("WRITE_SECURE_SETTINGS")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ oneway void onImeSwitchButtonClickFromSystem(int displayId);
+
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
@nullable InputMethodSubtype getCurrentInputMethodSubtype(int userId);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index f4ad487..19c6f51 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -22,6 +22,8 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.security.Flags.reportPrimaryAuthAttempts;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -414,7 +416,9 @@
return;
}
getDevicePolicyManager().reportFailedPasswordAttempt(userId);
- getTrustManager().reportUnlockAttempt(false /* authenticated */, userId);
+ if (!reportPrimaryAuthAttempts() || !shouldTrustManagerListenForPrimaryAuth()) {
+ getTrustManager().reportUnlockAttempt(/* authenticated= */ false, userId);
+ }
}
@UnsupportedAppUsage
@@ -423,7 +427,9 @@
return;
}
getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
- getTrustManager().reportUnlockAttempt(true /* authenticated */, userId);
+ if (!reportPrimaryAuthAttempts() || !shouldTrustManagerListenForPrimaryAuth()) {
+ getTrustManager().reportUnlockAttempt(/* authenticated= */ true, userId);
+ }
}
public void reportPasswordLockout(int timeoutMs, int userId) {
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 982189e..1a1d83c 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1063,8 +1063,8 @@
}
env->ReleaseStringUTFChars(file, file8);
- // Most proc files we read are small, so we only go through the
- // loop once and use the stack buffer. We allocate a buffer big
+ // Most proc files we read are small, so we go through the loop
+ // with the stack buffer firstly. We allocate a buffer big
// enough for the whole file.
char readBufferStack[kProcReadStackBufferSize];
@@ -1072,37 +1072,47 @@
char* readBuffer = &readBufferStack[0];
ssize_t readBufferSize = kProcReadStackBufferSize;
ssize_t numberBytesRead;
+ off_t offset = 0;
for (;;) {
+ ssize_t requestedBufferSize = readBufferSize - offset;
// By using pread, we can avoid an lseek to rewind the FD
// before retry, saving a system call.
- numberBytesRead = pread(fd, readBuffer, readBufferSize, 0);
- if (numberBytesRead < 0 && errno == EINTR) {
- continue;
- }
+ numberBytesRead =
+ TEMP_FAILURE_RETRY(pread(fd, readBuffer + offset, requestedBufferSize, offset));
if (numberBytesRead < 0) {
if (kDebugProc) {
- ALOGW("Unable to open process file: %s fd=%d\n", file8, fd.get());
+ ALOGW("Unable to read process file err: %s file: %s fd=%d\n",
+ strerror_r(errno, &readBufferStack[0], sizeof(readBufferStack)), file8,
+ fd.get());
}
return JNI_FALSE;
}
- if (numberBytesRead < readBufferSize) {
+ if (numberBytesRead == 0) {
+ // End of file.
+ numberBytesRead = offset;
break;
}
- if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
- if (kDebugProc) {
- ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+ if (numberBytesRead < requestedBufferSize) {
+ // Read less bytes than requested, it's not an error per pread(2).
+ offset += numberBytesRead;
+ } else {
+ // Buffer is fully used, try to grow it.
+ if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
+ if (kDebugProc) {
+ ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+ }
+ return JNI_FALSE;
}
- return JNI_FALSE;
+ readBufferSize = std::max(readBufferSize * 2, kProcReadMinHeapBufferSize);
+ readBufferHeap.reset(); // Free address space before getting more.
+ readBufferHeap = std::make_unique<char[]>(readBufferSize);
+ if (!readBufferHeap) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return JNI_FALSE;
+ }
+ readBuffer = readBufferHeap.get();
+ offset = 0;
}
- readBufferSize = std::max(readBufferSize * 2,
- kProcReadMinHeapBufferSize);
- readBufferHeap.reset(); // Free address space before getting more.
- readBufferHeap = std::make_unique<char[]>(readBufferSize);
- if (!readBufferHeap) {
- jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
- return JNI_FALSE;
- }
- readBuffer = readBufferHeap.get();
}
// parseProcLineArray below modifies the buffer while parsing!
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index d11166f..e874163 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -16,18 +16,22 @@
#define LOG_TAG "InputChannel-JNI"
-#include "android-base/stringprintf.h"
-#include <nativehelper/JNIHelp.h>
-#include "nativehelper/scoped_utf_chars.h"
+#include "android_view_InputChannel.h"
+
#include <android_runtime/AndroidRuntime.h>
#include <binder/Parcel.h>
-#include <utils/Log.h>
+#include <com_android_input_flags.h>
#include <input/InputTransport.h>
-#include "android_view_InputChannel.h"
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+
+#include "android-base/stringprintf.h"
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
-
#include "core_jni_helpers.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+namespace input_flags = com::android::input::flags;
namespace android {
@@ -69,6 +73,9 @@
}
void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callback, void* data) {
+ if (input_flags::remove_input_channel_from_windowstate()) {
+ return;
+ }
mDisposeCallback = callback;
mDisposeData = data;
}
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 59d18b8..30c926c 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -104,6 +104,7 @@
extern int register_android_view_KeyEvent(JNIEnv* env);
extern int register_android_view_InputDevice(JNIEnv* env);
extern int register_android_view_MotionEvent(JNIEnv* env);
+extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
@@ -151,6 +152,7 @@
{"android.view.KeyEvent", REG_JNI(register_android_view_KeyEvent)},
{"android.view.InputDevice", REG_JNI(register_android_view_InputDevice)},
{"android.view.MotionEvent", REG_JNI(register_android_view_MotionEvent)},
+ {"android.view.Surface", REG_JNI(register_android_view_Surface)},
{"android.view.VelocityTracker", REG_JNI(register_android_view_VelocityTracker)},
{"com.android.internal.util.VirtualRefBasePtr",
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index cdd8557..61c7a8c 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -393,12 +393,6 @@
<bool name="config_wait_for_device_alignment_in_demo_datagram">false</bool>
<java-symbol type="bool" name="config_wait_for_device_alignment_in_demo_datagram" />
- <!-- Boolean indicating whether to enable MMS to be attempted on IWLAN if possible, even if
- existing cellular networks already supports IWLAN.
- -->
- <bool name="force_iwlan_mms_feature_enabled">false</bool>
- <java-symbol type="bool" name="force_iwlan_mms_feature_enabled" />
-
<!-- The time duration in millis after which Telephony will abort the last message datagram
sending requests. Telephony starts a timer when receiving a last message datagram sending
request in either OFF, IDLE, or NOT_CONNECTED state. In NOT_CONNECTED, the duration of the
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index d277169..41696df 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -63,7 +63,6 @@
"-c fa",
],
static_libs: [
- "A11yChecker",
"collector-device-lib-platform",
"frameworks-base-testutils",
"core-test-rules", // for libcore.dalvik.system.CloseGuardSupport
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 033ac7c..c5b75ff 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -17,6 +17,7 @@
package android.view;
import static android.util.SequenceUtils.getInitSeq;
+import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
@@ -494,8 +495,8 @@
0, displayInfo, new DisplayAdjustments());
ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, display);
- boolean result = viewRootImpl.performHapticFeedback(
- HapticFeedbackConstants.CONTEXT_CLICK, true, false /* fromIme */);
+ boolean result = viewRootImpl.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK,
+ FLAG_IGNORE_GLOBAL_SETTING, 0 /* privFlags */);
assertThat(result).isFalse();
}
diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS b/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS
deleted file mode 100644
index 872a180..0000000
--- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Android Accessibility Framework owners
-include /core/java/android/view/accessibility/a11ychecker/OWNERS
-include /services/accessibility/OWNERS
-
-yaraabdullatif@google.com
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index b120723..8e1fde0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.window.ActivityWindowInfo.getActivityWindowInfo;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -80,6 +81,7 @@
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
@@ -2553,9 +2555,9 @@
return ActivityThread.currentActivityThread().getActivity(activityToken);
}
- @VisibleForTesting
@Nullable
- ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
+ private ActivityThread.ActivityClientRecord getActivityClientRecord(
+ @NonNull Activity activity) {
return ActivityThread.currentActivityThread()
.getActivityClient(activity.getActivityToken());
}
@@ -3092,10 +3094,8 @@
*/
@Override
public boolean isActivityEmbedded(@NonNull Activity activity) {
- Objects.requireNonNull(activity);
synchronized (mLock) {
- final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
- return activityWindowInfo != null && activityWindowInfo.isEmbedded();
+ return TaskFragmentOrganizer.isActivityEmbedded(activity);
}
}
@@ -3165,15 +3165,6 @@
}
}
- @Nullable
- private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
- if (activity.isFinishing()) {
- return null;
- }
- final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
- return record != null ? record.getActivityWindowInfo() : null;
- }
-
@NonNull
private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
@NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 99c0ee2..d852204 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -165,6 +165,7 @@
private Consumer<List<SplitInfo>> mEmbeddingCallback;
private List<SplitInfo> mSplitInfos;
private TransactionManager mTransactionManager;
+ private ActivityThread mCurrentActivityThread;
@Before
public void setUp() {
@@ -181,10 +182,12 @@
};
mSplitController.setSplitInfoCallback(mEmbeddingCallback);
mTransactionManager = mSplitController.mTransactionManager;
+ mCurrentActivityThread = ActivityThread.currentActivityThread();
spyOn(mSplitController);
spyOn(mSplitPresenter);
spyOn(mEmbeddingCallback);
spyOn(mTransactionManager);
+ spyOn(mCurrentActivityThread);
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
@@ -1668,7 +1671,8 @@
final IBinder activityToken = new Binder();
doReturn(activityToken).when(activity).getActivityToken();
doReturn(activity).when(mSplitController).getActivity(activityToken);
- doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity);
+ doReturn(activityClientRecord).when(mCurrentActivityThread).getActivityClient(
+ activityToken);
doReturn(taskId).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 196f89d..df80946 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -107,4 +107,24 @@
* @param pilferCallback the callback to pilfer pointers.
*/
void setPilferPointerCallback(Runnable pilferCallback);
+
+ /**
+ * Set a callback to requestTopUi.
+ * @param topUiRequest the callback to requestTopUi.
+ */
+ void setTopUiRequestCallback(TopUiRequest topUiRequest);
+
+ /**
+ * Callback to request SysUi to call
+ * {@link android.app.IActivityManager#setHasTopUi(boolean)}.
+ */
+ interface TopUiRequest {
+
+ /**
+ * Request {@link android.app.IActivityManager#setHasTopUi(boolean)} to be called.
+ * @param requestTopUi whether topUi should be requested or not
+ * @param tag tag of the request-source
+ */
+ void requestTopUi(boolean requestTopUi, String tag);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 8467e97..bb239ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -179,6 +179,7 @@
@BackNavigationInfo.BackTargetType
private int mPreviousNavigationType;
private Runnable mPilferPointerCallback;
+ private BackAnimation.TopUiRequest mRequestTopUiCallback;
public BackAnimationController(
@NonNull ShellInit shellInit,
@@ -357,6 +358,11 @@
mPilferPointerCallback = callback;
});
}
+
+ @Override
+ public void setTopUiRequestCallback(TopUiRequest topUiRequest) {
+ mShellExecutor.execute(() -> mRequestTopUiCallback = topUiRequest);
+ }
}
private static class IBackAnimationImpl extends IBackAnimation.Stub
@@ -557,6 +563,7 @@
if (!mShellBackAnimationRegistry.startGesture(backType)) {
mActiveCallback = null;
}
+ requestTopUi(true, backType);
tryPilferPointers();
} else {
mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
@@ -906,6 +913,7 @@
mPreviousNavigationType = mBackNavigationInfo.getType();
mBackNavigationInfo.onBackNavigationFinished(triggerBack);
mBackNavigationInfo = null;
+ requestTopUi(false, mPreviousNavigationType);
}
}
@@ -969,6 +977,13 @@
}
}
+ private void requestTopUi(boolean hasTopUi, int backType) {
+ if (mRequestTopUiCallback != null && (backType == BackNavigationInfo.TYPE_CROSS_TASK
+ || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
+ mRequestTopUiCallback.requestTopUi(hasTopUi, TAG);
+ }
+ }
+
/**
* Validate animation targets.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 4f04c5c..4e0c82b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -61,8 +61,7 @@
private val context: Context,
private val background: BackAnimationBackground,
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
- protected val transaction: SurfaceControl.Transaction,
- private val choreographer: Choreographer
+ protected val transaction: SurfaceControl.Transaction
) : ShellBackAnimation() {
protected val startClosingRect = RectF()
@@ -269,7 +268,9 @@
.setSpring(postCommitFlingSpring)
flingAnimation.start()
// do an animation-frame immediately to prevent idle frame
- flingAnimation.doAnimationFrame(choreographer.lastFrameTimeNanos / TimeUtils.NANOS_PER_MS)
+ flingAnimation.doAnimationFrame(
+ Choreographer.getInstance().lastFrameTimeNanos / TimeUtils.NANOS_PER_MS
+ )
val valueAnimator =
ValueAnimator.ofFloat(1f, 0f).setDuration(getPostCommitAnimationDuration())
@@ -362,7 +363,7 @@
}
protected fun applyTransaction() {
- transaction.setFrameTimelineVsync(choreographer.vsyncId)
+ transaction.setFrameTimelineVsync(Choreographer.getInstance().vsyncId)
transaction.apply()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 103a654..e2b0513 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -52,7 +52,6 @@
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
import javax.inject.Inject;
@@ -69,7 +68,6 @@
* IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back navigation to
* launcher starts.
*/
-@ShellMainThread
public class CrossTaskBackAnimation extends ShellBackAnimation {
private static final int BACKGROUNDCOLOR = 0x43433A;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
index e266e2c..b02f97b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -19,7 +19,6 @@
import android.graphics.Rect
import android.graphics.RectF
import android.util.MathUtils
-import android.view.Choreographer
import android.view.SurfaceControl
import android.view.animation.Animation
import android.view.animation.Transformation
@@ -31,27 +30,23 @@
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.shared.annotations.ShellMainThread
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
/** Class that handles customized predictive cross activity back animations. */
-@ShellMainThread
class CustomCrossActivityBackAnimation(
context: Context,
background: BackAnimationBackground,
rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
transaction: SurfaceControl.Transaction,
- choreographer: Choreographer,
private val customAnimationLoader: CustomAnimationLoader
) :
CrossActivityBackAnimation(
context,
background,
rootTaskDisplayAreaOrganizer,
- transaction,
- choreographer
+ transaction
) {
private var enterAnimation: Animation? = null
@@ -70,7 +65,6 @@
background,
rootTaskDisplayAreaOrganizer,
SurfaceControl.Transaction(),
- Choreographer.getInstance(),
CustomAnimationLoader(
TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation")
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
index 3b5eb36..c747e1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -16,18 +16,15 @@
package com.android.wm.shell.back
import android.content.Context
-import android.view.Choreographer
import android.view.SurfaceControl
import android.window.BackEvent
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.animation.Interpolators
-import com.android.wm.shell.shared.annotations.ShellMainThread
import javax.inject.Inject
import kotlin.math.max
/** Class that defines cross-activity animation. */
-@ShellMainThread
class DefaultCrossActivityBackAnimation
@Inject
constructor(
@@ -39,8 +36,7 @@
context,
background,
rootTaskDisplayAreaOrganizer,
- SurfaceControl.Transaction(),
- Choreographer.getInstance()
+ SurfaceControl.Transaction()
) {
private val postCommitInterpolator = Interpolators.EMPHASIZED
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt
new file mode 100644
index 0000000..cb54d89
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.api
+
+/**
+ * Abstraction for the repository of all the available CompatUISpec
+ */
+interface CompatUIRepository {
+ /**
+ * Adds a {@link CompatUISpec} to the repository
+ * @throws IllegalStateException in case of illegal spec
+ */
+ fun addSpec(spec: CompatUISpec)
+
+ /**
+ * Iterates on the list of available {@link CompatUISpec} invoking
+ * fn for each of them.
+ */
+ fun iterateOn(fn: (CompatUISpec) -> Unit)
+
+ /**
+ * Returns the {@link CompatUISpec} for a given key
+ */
+ fun findSpec(name: String): CompatUISpec?
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
new file mode 100644
index 0000000..24c2c8c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.api
+
+/**
+ * Describes each compat ui component to the framework.
+ */
+data class CompatUISpec(
+ // Unique name for the component. It's used for debug and for generating the
+ // unique component identifier in the system.
+ val name: String
+)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
index a181eaf..8408ea6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
@@ -19,12 +19,15 @@
import com.android.wm.shell.compatui.api.CompatUIEvent
import com.android.wm.shell.compatui.api.CompatUIHandler
import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUIRepository
import java.util.function.Consumer
/**
* Default implementation of {@link CompatUIHandler} to handle CompatUI components
*/
-class DefaultCompatUIHandler : CompatUIHandler {
+class DefaultCompatUIHandler(
+ private val compatUIRepository: CompatUIRepository
+) : CompatUIHandler {
private var compatUIEventSender: Consumer<CompatUIEvent>? = null
override fun onCompatInfoChanged(compatUIInfo: CompatUIInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt
new file mode 100644
index 0000000..10d9425
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+
+/**
+ * Default {@link CompatUIRepository} implementation
+ */
+class DefaultCompatUIRepository : CompatUIRepository {
+
+ private val allSpecs = mutableMapOf<String, CompatUISpec>()
+
+ override fun addSpec(spec: CompatUISpec) {
+ if (allSpecs[spec.name] != null) {
+ throw IllegalStateException("Spec with id:${spec.name} already present")
+ }
+ allSpecs[spec.name] = spec
+ }
+
+ override fun iterateOn(fn: (CompatUISpec) -> Unit) =
+ allSpecs.values.forEach(fn)
+
+ override fun findSpec(name: String): CompatUISpec? =
+ allSpecs[name]
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 9bdc0b2..4b548cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -72,7 +72,9 @@
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.api.CompatUIRepository;
import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
+import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -245,12 +247,13 @@
Lazy<DockStateReader> dockStateReader,
Lazy<CompatUIConfiguration> compatUIConfiguration,
Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
- Lazy<AccessibilityManager> accessibilityManager) {
+ Lazy<AccessibilityManager> accessibilityManager,
+ CompatUIRepository compatUIRepository) {
if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
return Optional.empty();
}
if (Flags.appCompatUiFramework()) {
- return Optional.of(new DefaultCompatUIHandler());
+ return Optional.of(new DefaultCompatUIHandler(compatUIRepository));
}
return Optional.of(
new CompatUIController(
@@ -271,6 +274,12 @@
@WMSingleton
@Provides
+ static CompatUIRepository provideCompatUIRepository() {
+ return new DefaultCompatUIRepository();
+ }
+
+ @WMSingleton
+ @Provides
static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
@ShellMainThread ShellExecutor mainExecutor) {
return new SyncTransactionQueue(pool, mainExecutor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 037fbb2..1a9c304 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -206,13 +206,12 @@
@WMSingleton
@Provides
static PipMotionHelper providePipMotionHelper(Context context,
- @ShellMainThread ShellExecutor mainExecutor,
PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
- return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer,
+ return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
menuController, pipSnapAlgorithm, pipTransitionController,
floatingContentCoordinator, pipPerfHintControllerOptional);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index ca05864..247cc42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -88,14 +88,17 @@
/** Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not. */
fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
visibleTasksListeners[visibleTasksListener] = executor
- displayData.keyIterator().forEach { displayId ->
- val visibleTasksCount = getVisibleTaskCount(displayId)
+ displayData.keyIterator().forEach {
executor.execute {
- visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
+ visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount(it))
}
}
}
+ /** Returns a list of all [DisplayData]. */
+ private fun displayDataList(): Sequence<DisplayData> =
+ displayData.valueIterator().asSequence()
+
/**
* Add a Consumer which will inform other classes of changes to exclusion regions for all
* Desktop tasks.
@@ -208,37 +211,17 @@
return removed
}
- /** Check if a task with the given [taskId] was marked as an active task */
- fun isActiveTask(taskId: Int): Boolean {
- return displayData.valueIterator().asSequence().any { data ->
- data.activeTasks.contains(taskId)
- }
- }
-
- /** Check if a task with the given [taskId] was marked as a closing task */
- fun isClosingTask(taskId: Int): Boolean =
- displayData.valueIterator().asSequence().any { data -> taskId in data.closingTasks }
-
- /** Whether a task is visible. */
- fun isVisibleTask(taskId: Int): Boolean {
- return displayData.valueIterator().asSequence().any { data ->
- data.visibleTasks.contains(taskId)
- }
- }
-
- /** Return whether the given Task is minimized. */
- fun isMinimizedTask(taskId: Int): Boolean {
- return displayData.valueIterator().asSequence().any { data ->
- data.minimizedTasks.contains(taskId)
- }
- }
+ fun isActiveTask(taskId: Int) = displayDataList().any { taskId in it.activeTasks }
+ fun isClosingTask(taskId: Int) = displayDataList().any { taskId in it.closingTasks }
+ fun isVisibleTask(taskId: Int) = displayDataList().any { taskId in it.visibleTasks }
+ fun isMinimizedTask(taskId: Int) = displayDataList().any { taskId in it.minimizedTasks }
/**
* Check if a task with the given [taskId] is the only visible, non-closing, not-minimized task
* on its display
*/
fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean =
- displayData.valueIterator().asSequence().any { data ->
+ displayDataList().any { data ->
data.visibleTasks
.subtract(data.closingTasks)
.subtract(data.minimizedTasks)
@@ -255,12 +238,6 @@
ArraySet(displayData[displayId]?.minimizedTasks)
/**
- * Returns whether Desktop Mode is currently showing any tasks, i.e. whether any Desktop Tasks
- * are visible.
- */
- fun isDesktopModeShowing(displayId: Int): Boolean = getVisibleTaskCount(displayId) > 0
-
- /**
* Returns a list of Tasks IDs representing all active non-minimized Tasks on the given display,
* ordered from front to back.
*/
@@ -305,14 +282,14 @@
return
}
- val prevCount = getVisibleTaskCount(displayId)
+ val prevCount = visibleTaskCount(displayId)
if (visible) {
displayData.getOrCreate(displayId).visibleTasks.add(taskId)
unminimizeTask(displayId, taskId)
} else {
displayData[displayId]?.visibleTasks?.remove(taskId)
}
- val newCount = getVisibleTaskCount(displayId)
+ val newCount = visibleTaskCount(displayId)
// Check if count changed
if (prevCount != newCount) {
@@ -340,7 +317,7 @@
}
/** Get number of tasks that are marked as visible on given [displayId] */
- fun getVisibleTaskCount(displayId: Int): Int {
+ fun visibleTaskCount(displayId: Int): Int {
ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: visibleTaskCount= %d",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 5807246..9a1a8a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -246,10 +246,12 @@
}
}
- /** Get number of tasks that are marked as visible */
- fun getVisibleTaskCount(displayId: Int): Int {
- return desktopModeTaskRepository.getVisibleTaskCount(displayId)
- }
+ /** Gets number of visible tasks in [displayId]. */
+ fun visibleTaskCount(displayId: Int): Int =
+ desktopModeTaskRepository.visibleTaskCount(displayId)
+
+ /** Returns true if any tasks are visible in Desktop Mode. */
+ fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
/** Enter desktop by using the focused task in given `displayId` */
fun moveFocusedTaskToDesktop(displayId: Int, transitionSource: DesktopModeTransitionSource) {
@@ -981,7 +983,7 @@
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
return null
}
- if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
+ if (!isDesktopModeShowing(task.displayId)) {
ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: bring desktop tasks to front on transition" +
@@ -1012,7 +1014,7 @@
transition: IBinder
): WindowContainerTransaction? {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
- if (desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
+ if (isDesktopModeShowing(task.displayId)) {
ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: switch fullscreen task to freeform on transition" +
@@ -1045,14 +1047,12 @@
/** Handle task closing by removing wallpaper activity if it's the last active task */
private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
- val wct = if (
- desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId) &&
- desktopModeTaskRepository.wallpaperActivityToken != null
- ) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleTaskClosing")
+ val wct = WindowContainerTransaction()
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId)
+ && desktopModeTaskRepository.wallpaperActivityToken != null) {
// Remove wallpaper activity when the last active task is removed
- WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) }
- } else {
- null
+ removeWallpaperActivity(wct)
}
if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) {
// Could happen if the task hasn't been removed from closing list after it disappeared
@@ -1062,7 +1062,12 @@
task.taskId
)
}
- return wct
+ // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
+ if (Flags.enableDesktopWindowingBackNavigation() &&
+ desktopModeTaskRepository.isVisibleTask(task.taskId)) {
+ wct.removeTask(task.token)
+ }
+ return if (wct.isEmpty) null else wct
}
private fun addMoveToDesktopChanges(
@@ -1393,8 +1398,7 @@
onFinishCallback: Consumer<Boolean>
): Boolean {
// TODO(b/320797628): Pass through which display we are dropping onto
- val activeTasks = desktopModeTaskRepository.getActiveTasks(DEFAULT_DISPLAY)
- if (!activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+ if (!isDesktopModeShowing(DEFAULT_DISPLAY)) {
// Not currently in desktop mode, ignore the drop
return false
}
@@ -1553,8 +1557,8 @@
val result = IntArray(1)
executeRemoteCallWithTaskPermission(
controller,
- "getVisibleTaskCount",
- { controller -> result[0] = controller.getVisibleTaskCount(displayId) },
+ "visibleTaskCount",
+ { controller -> result[0] = controller.visibleTaskCount(displayId) },
true /* blocking */
)
return result[0]
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index b27c428..a749019 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,12 +16,10 @@
package com.android.wm.shell.pip;
-import android.annotation.NonNull;
import android.graphics.Rect;
import com.android.wm.shell.shared.annotations.ExternalThread;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -71,10 +69,9 @@
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
/**
- * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition
- * started / finished callbacks.
+ * @return {@link PipTransitionController} instance.
*/
- default void registerPipTransitionCallback(
- @NonNull PipTransitionController.PipTransitionCallback callback,
- @NonNull Executor executor) { }
+ default PipTransitionController getPipTransitionController() {
+ return null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 6e1e44b..8d63ff2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -423,8 +423,7 @@
});
mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
- pipTransitionController.registerPipTransitionCallback(
- mPipTransitionCallback, mMainExecutor);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
}
}
@@ -496,9 +495,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
mPipTransitionState.setInSwipePipToHomeTransition(true);
- if (!ENABLE_SHELL_TRANSITIONS) {
- sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
- }
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
return mPipBoundsAlgorithm.getEntryDestinationBounds();
}
@@ -1983,12 +1980,6 @@
}
clearContentOverlay();
}
- if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
- // Avoid double removal, which is fatal.
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: trying to remove overlay (%s) while in UNDEFINED state", TAG, surface);
- return;
- }
if (surface == null || !surface.isValid()) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: trying to remove invalid content overlay (%s)", TAG, surface);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 87692ac..e5633de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1174,7 +1174,6 @@
}
final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
- sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 8d36db9..b1dd4f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -53,9 +53,8 @@
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
+import java.util.ArrayList;
+import java.util.List;
/**
* Responsible supplying PiP Transitions.
@@ -67,7 +66,7 @@
protected final ShellTaskOrganizer mShellTaskOrganizer;
protected final PipMenuController mPipMenuController;
protected final Transitions mTransitions;
- private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>();
+ private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
protected PipTaskOrganizer mPipOrganizer;
protected DefaultMixedHandler mMixedHandler;
@@ -184,18 +183,16 @@
/**
* Registers {@link PipTransitionCallback} to receive transition callbacks.
*/
- public void registerPipTransitionCallback(
- @NonNull PipTransitionCallback callback, @NonNull Executor executor) {
- mPipTransitionCallbacks.put(callback, executor);
+ public void registerPipTransitionCallback(PipTransitionCallback callback) {
+ mPipTransitionCallbacks.add(callback);
}
protected void sendOnPipTransitionStarted(
@PipAnimationController.TransitionDirection int direction) {
final Rect pipBounds = mPipBoundsState.getBounds();
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionStarted(direction, pipBounds));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionStarted(direction, pipBounds);
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -212,10 +209,9 @@
protected void sendOnPipTransitionFinished(
@PipAnimationController.TransitionDirection int direction) {
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionFinished(direction));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionFinished(direction);
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -232,10 +228,9 @@
protected void sendOnPipTransitionCancelled(
@PipAnimationController.TransitionDirection int direction) {
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionCanceled(direction));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionCanceled(direction);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 0cb7e17..26b7e58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -106,7 +106,6 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -479,7 +478,7 @@
mShellCommandHandler.addDumpCallback(this::dump, this);
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mMainExecutor);
- mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
+ mPipTransitionController.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
mPipDisplayLayoutState.setDisplayId(displayId);
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -1221,11 +1220,8 @@
}
@Override
- public void registerPipTransitionCallback(
- PipTransitionController.PipTransitionCallback callback,
- Executor executor) {
- mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback(
- callback, executor));
+ public PipTransitionController getPipTransitionController() {
+ return mPipTransitionController;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index df3803d..e8d6576 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -38,7 +38,6 @@
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -48,7 +47,6 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
@@ -173,9 +171,7 @@
public void onPipTransitionCanceled(int direction) {}
};
- public PipMotionHelper(Context context,
- @ShellMainThread ShellExecutor mainExecutor,
- @NonNull PipBoundsState pipBoundsState,
+ public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
@@ -187,7 +183,7 @@
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
- pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
mResizePipUpdateListener = (target, values) -> {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 0ed5079..62c0944 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -257,7 +257,7 @@
}
private void onInit() {
- mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
+ mPipTransitionController.registerPipTransitionCallback(this);
reloadResources();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index c12219c..b939b16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -64,7 +64,6 @@
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -413,11 +412,6 @@
@Override
public void showPictureInPictureMenu() {}
-
- @Override
- public void registerPipTransitionCallback(
- PipTransitionController.PipTransitionCallback callback,
- Executor executor) {}
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 4104234..9bcd9b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -3573,7 +3573,8 @@
pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
pw.println(innerPrefix + "isSplitActive=" + isSplitActive());
pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible());
- pw.println(innerPrefix + "isLeftRightSplit=" + mSplitLayout.isLeftRightSplit());
+ pw.println(innerPrefix + "isLeftRightSplit="
+ + (mSplitLayout != null ? mSplitLayout.isLeftRightSplit() : "null"));
pw.println(innerPrefix + "MainStage");
pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
pw.println(childPrefix + "isActive=" + mMainStage.isActive());
@@ -3585,7 +3586,9 @@
mSideStage.dump(pw, childPrefix);
pw.println(innerPrefix + "SideStageListener");
mSideStageListener.dump(pw, childPrefix);
- mSplitLayout.dump(pw, childPrefix);
+ if (mSplitLayout != null) {
+ mSplitLayout.dump(pw, childPrefix);
+ }
if (!mPausingTasks.isEmpty()) {
pw.println(childPrefix + "mPausingTasks=" + mPausingTasks);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4f4b809..766a6b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -353,7 +353,7 @@
return this::setRecentsTransitionDuringKeyguard;
} else if (mDesktopTasksController != null
// Check on the default display. Recents/gesture nav is only available there
- && mDesktopTasksController.getVisibleTaskCount(DEFAULT_DISPLAY) > 0) {
+ && mDesktopTasksController.visibleTaskCount(DEFAULT_DISPLAY) > 0) {
return this::setRecentsTransitionDuringDesktop;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 1532211..b5b476d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -18,6 +18,7 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW;
import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW;
import android.graphics.Point;
@@ -103,6 +104,9 @@
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
mTaskOrganizer.applyTransaction(wct);
}
+ } else {
+ mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
+ mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_DRAG_WINDOW);
}
mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
@@ -157,11 +161,16 @@
}
mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_RESIZE_WINDOW);
} else {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
- wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
- mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
+ if (!mTaskBoundsAtDragStart.equals(mRepositionTaskBounds)) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
+ } else {
+ // Drag-move ended where it originally started, no need to update WM.
+ mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_DRAG_WINDOW);
+ }
}
mCtrlType = CTRL_TYPE_UNDEFINED;
@@ -202,6 +211,7 @@
mCtrlType = CTRL_TYPE_UNDEFINED;
finishCallback.onTransitionFinished(null);
mIsResizingOrAnimatingResize = false;
+ mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_DRAG_WINDOW);
return true;
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowMultiWindow.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowMultiWindow.kt
new file mode 100644
index 0000000..bbf0ce5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowMultiWindow.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+
+/** Base scenario test for window drag CUJ with multiple windows. */
+@Ignore("Base Test Class")
+abstract class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase()
+{
+ private val imeAppHelper = ImeAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+ private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+ private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ mailApp.launchViaIntent(wmHelper)
+ newTasksApp.launchViaIntent(wmHelper)
+ imeApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ override fun dragAppWindow() {
+ val (startXIme, startYIme) = getWindowDragStartCoordinate(imeAppHelper)
+
+ imeApp.dragWindow(startXIme, startYIme,
+ endX = startXIme + 150, endY = startYIme + 150,
+ wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ mailApp.exit(wmHelper)
+ newTasksApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowScenarioTestBase.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowScenarioTestBase.kt
new file mode 100644
index 0000000..a613ca1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowScenarioTestBase.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/** Base test class for window drag CUJ. */
+@Ignore("Base Test Class")
+abstract class DragAppWindowScenarioTestBase {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ @Test abstract fun dragAppWindow()
+
+ /** Return the top-center coordinate of the app header as the start coordinate. */
+ fun getWindowDragStartCoordinate(appHelper: StandardAppHelper): Pair<Int, Int> {
+ val windowRect = wmHelper.getWindowRegion(appHelper).bounds
+ // Set start x-coordinate as center of app header.
+ val startX = windowRect.centerX()
+ val startY = windowRect.top
+ return Pair(startX, startY)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowSingleWindow.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowSingleWindow.kt
new file mode 100644
index 0000000..0655620
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowSingleWindow.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+
+/** Base scenario test for window drag CUJ with single window. */
+@Ignore("Base Test Class")
+abstract class DragAppWindowSingleWindow : DragAppWindowScenarioTestBase()
+{
+ private val simpleAppHelper = SimpleAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(simpleAppHelper)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ override fun dragAppWindow() {
+ val (startXTest, startYTest) = getWindowDragStartCoordinate(simpleAppHelper)
+ testApp.dragWindow(startXTest, startYTest,
+ endX = startXTest + 150, endY = startYTest + 150,
+ wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
index 8bf0111..080ad90 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -25,7 +25,6 @@
import android.os.RemoteException
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.Choreographer
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
@@ -37,8 +36,6 @@
import com.android.internal.policy.TransitionAnimation
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
import junit.framework.TestCase.assertEquals
import org.junit.Assert
import org.junit.Before
@@ -50,12 +47,13 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
@SmallTest
@TestableLooper.RunWithLooper
@@ -82,7 +80,6 @@
backAnimationBackground,
rootTaskDisplayAreaOrganizer,
transaction,
- mock(Choreographer::class.java),
customAnimationLoader
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
new file mode 100644
index 0000000..1a86cfd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.impl
+
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link DefaultCompatUIRepository}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DefaultCompatUIRepositoryTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DefaultCompatUIRepositoryTest {
+
+ lateinit var repository: CompatUIRepository
+
+ @get:Rule
+ val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Before
+ fun setUp() {
+ repository = DefaultCompatUIRepository()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun `addSpec throws exception with specs with duplicate id`() {
+ repository.addSpec(CompatUISpec("one"))
+ repository.addSpec(CompatUISpec("one"))
+ }
+
+ @Test
+ fun `iterateOn invokes the consumer`() {
+ with(repository) {
+ addSpec(CompatUISpec("one"))
+ addSpec(CompatUISpec("two"))
+ addSpec(CompatUISpec("three"))
+ val consumer = object : (CompatUISpec) -> Unit {
+ var acc = ""
+ override fun invoke(spec: CompatUISpec) {
+ acc += spec.name
+ }
+ }
+ iterateOn(consumer)
+ assertEquals("onetwothree", consumer.acc)
+ }
+ }
+
+ @Test
+ fun `findSpec returns existing specs`() {
+ with(repository) {
+ val one = CompatUISpec("one")
+ val two = CompatUISpec("two")
+ val three = CompatUISpec("three")
+ addSpec(one)
+ addSpec(two)
+ addSpec(three)
+ assertEquals(findSpec("one"), one)
+ assertEquals(findSpec("two"), two)
+ assertEquals(findSpec("three"), three)
+ assertNull(findSpec("abc"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt
new file mode 100644
index 0000000..cdc524a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+
+/**
+ * Fake implementation for {@link CompatUIRepository}
+ */
+class FakeCompatUIRepository : CompatUIRepository {
+ val allSpecs = mutableMapOf<String, CompatUISpec>()
+ override fun addSpec(spec: CompatUISpec) {
+ if (findSpec(spec.name) != null) {
+ throw IllegalStateException("Spec with name:${spec.name} already present")
+ }
+ allSpecs[spec.name] = spec
+ }
+
+ override fun iterateOn(fn: (CompatUISpec) -> Unit) =
+ allSpecs.values.forEach(fn)
+
+ override fun findSpec(name: String): CompatUISpec? =
+ allSpecs[name]
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 6612aee..18b08bf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -337,65 +337,65 @@
}
@Test
- fun getVisibleTaskCount() {
+ fun visibleTaskCount_defaultDisplay_returnsCorrectCount() {
// No tasks, count is 0
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
// New task increments count to 1
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Visibility update to same task does not increase count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Second task visible increments count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
// Hiding a task decrements count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Hiding all tasks leaves count at 0
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
- assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(displayId = 9)).isEqualTo(0)
// Hiding a not existing task, count remains at 0
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 999, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@Test
- fun getVisibleTaskCount_multipleDisplays() {
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+ fun visibleTaskCount_multipleDisplays_returnsCorrectCount() {
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on default display increments count for that display only
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on secondary display, increments count for that display only
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 2, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
// Marking task visible on another display, updates counts for both displays
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Marking task that is on secondary display, hidden on default display, does not affect
// secondary display
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Hiding a task on that display, decrements count
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
}
@Test
@@ -494,28 +494,6 @@
}
@Test
- fun isDesktopModeShowing_noActiveTasks_returnsFalse() {
- assertThat(repo.isDesktopModeShowing(displayId = 0)).isFalse()
- }
-
- @Test
- fun isDesktopModeShowing_noTasksVisible_returnsFalse() {
- repo.addActiveTask(displayId = 0, taskId = 1)
- repo.addActiveTask(displayId = 0, taskId = 2)
-
- assertThat(repo.isDesktopModeShowing(displayId = 0)).isFalse()
- }
-
- @Test
- fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() {
- repo.addActiveTask(displayId = 0, taskId = 1)
- repo.addActiveTask(displayId = 0, taskId = 2)
- repo.updateVisibleFreeformTasks(displayId = 0, taskId = 1, visible = true)
-
- assertThat(repo.isDesktopModeShowing(displayId = 0)).isTrue()
- }
-
- @Test
fun getActiveNonMinimizedTasksOrderedFrontToBack_returnsFreeformTasksInCorrectOrder() {
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 8558a77..da88686 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -20,7 +20,6 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.KeyguardManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
@@ -68,6 +67,7 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
@@ -125,11 +125,11 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
/**
* Test class for {@link DesktopTasksController}
@@ -311,6 +311,31 @@
}
@Test
+ fun isDesktopModeShowing_noTasks_returnsFalse() {
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
+ }
+
+ @Test
+ fun isDesktopModeShowing_noTasksVisible_returnsFalse() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
+ }
+
+ @Test
+ fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskHidden(task2)
+
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isTrue()
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
val homeTask = setUpHomeTask(SECOND_DISPLAY)
@@ -526,32 +551,32 @@
}
@Test
- fun getVisibleTaskCount_noTasks_returnsZero() {
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ fun visibleTaskCount_noTasks_returnsZero() {
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@Test
- fun getVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
+ fun visibleTaskCount_twoTasks_bothVisible_returnsTwo() {
setUpHomeTask()
setUpFreeformTask().also(::markTaskVisible)
setUpFreeformTask().also(::markTaskVisible)
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
}
@Test
- fun getVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
+ fun visibleTaskCount_twoTasks_oneVisible_returnsOne() {
setUpHomeTask()
setUpFreeformTask().also(::markTaskVisible)
setUpFreeformTask().also(::markTaskHidden)
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
}
@Test
- fun getVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
+ fun visibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
setUpHomeTask()
setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
- assertThat(controller.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+ assertThat(controller.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
}
@Test
@@ -1451,7 +1476,7 @@
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.build()
- val transition = createTransition(task = task, type = WindowManager.TRANSIT_CLOSE)
+ val transition = createTransition(task = task, type = TRANSIT_CLOSE)
val result = controller.handleRequest(Binder(), transition)
assertThat(result).isNull()
}
@@ -1545,8 +1570,11 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() {
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ )
+ fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -1555,8 +1583,22 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(0, task.token)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -1565,8 +1607,11 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() {
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
@@ -1576,22 +1621,42 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task = setUpFreeformTask()
val wallpaperToken = MockToken().token()
desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task.token)
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksFlagDisabled_doesNotHandle() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1602,8 +1667,24 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task1.token)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasks_backNavigationDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1614,8 +1695,11 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksSingleNonClosing_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1624,14 +1708,33 @@
desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasksSingleNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1640,24 +1743,53 @@
desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() {
- val task = setUpFreeformTask()
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_nonMinimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ // Task is being minimized so mark it as not visible.
+ desktopModeTaskRepository
+ .updateVisibleFreeformTasks(displayId = DEFAULT_DISPLAY, task2.taskId, false)
+ val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
@@ -1666,8 +1798,35 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
@@ -1677,22 +1836,71 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task = setUpFreeformTask()
val wallpaperToken = MockToken().token()
desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 0, task1.token)
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksFlagDisabled_doesNotHandle() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksFlagEnabled_backNavigationDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1703,20 +1911,11 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksFlagEnabled_doesNotHandle() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksSingleNonClosing_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1725,14 +1924,33 @@
desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasksOneNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1741,9 +1959,45 @@
desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_minimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ // Task is being minimized so mark it as not visible.
+ desktopModeTaskRepository
+ .updateVisibleFreeformTasks(displayId = DEFAULT_DISPLAY, task2.taskId, false)
+ val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 75d2145..6888de5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -182,7 +182,7 @@
@Test
public void instantiatePipController_registersPipTransitionCallback() {
- verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any());
+ verify(mMockPipTransitionController).registerPipTransitionCallback(any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 66f8c0b..ace09a8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -114,8 +114,8 @@
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
mSizeSpecSource);
- final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor,
- mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
+ final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
+ mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 6d18e36..92762fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -116,8 +116,8 @@
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
- PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor,
- mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
+ PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
+ mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index d42b256..d20b7f0 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -762,14 +762,14 @@
void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher);
- oneway void startLoudnessCodecUpdates(int sessionId);
+ void startLoudnessCodecUpdates(int sessionId);
- oneway void stopLoudnessCodecUpdates(int sessionId);
+ void stopLoudnessCodecUpdates(int sessionId);
- oneway void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
+ void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
in LoudnessCodecInfo codecInfo);
- oneway void removeLoudnessCodecInfo(int sessionId, in LoudnessCodecInfo codecInfo);
+ void removeLoudnessCodecInfo(int sessionId, in LoudnessCodecInfo codecInfo);
PersistableBundle getLoudnessParams(in LoudnessCodecInfo codecInfo);
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 4059291..999f40e5 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -312,6 +312,10 @@
* <p>Once a MediaProjection has been stopped, it's up to the application to release any
* resources it may be holding (e.g. releasing the {@link VirtualDisplay} and
* {@link Surface}).
+ *
+ * <p>After this callback any call to
+ * {@link MediaProjection#createVirtualDisplay} will fail, even if no such
+ * {@link VirtualDisplay} was ever created for this MediaProjection session.
*/
public void onStop() { }
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 7ed67dc..4013d84 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -43,25 +43,31 @@
/**
* Manages the retrieval of certain types of {@link MediaProjection} tokens.
*
- * <p><ol>An example flow of starting a media projection will be:
- * <li>Declare a foreground service with the type {@code mediaProjection} in
- * the {@code AndroidManifest.xml}.
- * </li>
- * <li>Create an intent by calling {@link MediaProjectionManager#createScreenCaptureIntent()}
- * and pass this intent to {@link Activity#startActivityForResult(Intent, int)}.
- * </li>
- * <li>On getting {@link Activity#onActivityResult(int, int, Intent)},
- * start the foreground service with the type
- * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
- * </li>
- * <li>Retrieve the media projection token by calling
- * {@link MediaProjectionManager#getMediaProjection(int, Intent)} with the result code and
- * intent from the {@link Activity#onActivityResult(int, int, Intent)} above.
- * </li>
- * <li>Start the screen capture session for media projection by calling
- * {@link MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
- * android.hardware.display.VirtualDisplay.Callback, Handler)}.
- * </li>
+ * <p>
+ *
+ * <ol>
+ * An example flow of starting a media projection will be:
+ * <li>Declare a foreground service with the type {@code mediaProjection} in the {@code
+ * AndroidManifest.xml}.
+ * <li>Create an intent by calling {@link MediaProjectionManager#createScreenCaptureIntent()} and
+ * pass this intent to {@link Activity#startActivityForResult(Intent, int)}.
+ * <li>On getting {@link Activity#onActivityResult(int, int, Intent)}, start the foreground
+ * service with the type {@link
+ * android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
+ * <li>Retrieve the media projection token by calling {@link
+ * MediaProjectionManager#getMediaProjection(int, Intent)} with the result code and intent
+ * from the {@link Activity#onActivityResult(int, int, Intent)} above.
+ * <li>Register a {@link MediaProjection.Callback} by calling {@link
+ * MediaProjection#registerCallback(MediaProjection.Callback, Handler)}. This is required to
+ * receive notifications about when the {@link MediaProjection} or captured content changes
+ * state. When receiving an `onStop()` callback, the client must clean up any resources it is
+ * holding, e.g. the {@link VirtualDisplay} and {@link Surface}. The MediaProjection may
+ * further no longer create any new {@link VirtualDisplay}s via {@link
+ * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * VirtualDisplay.Callback, Handler)}.
+ * <li>Start the screen capture session for media projection by calling {@link
+ * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * android.hardware.display.VirtualDisplay.Callback, Handler)}.
* </ol>
*/
@SystemService(Context.MEDIA_PROJECTION_SERVICE)
diff --git a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
index 7065e3a..46c7273 100644
--- a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
+++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
@@ -27,6 +27,7 @@
import com.google.common.util.concurrent.MoreExecutors;
import java.lang.ref.WeakReference;
+import java.util.Collection;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -35,12 +36,13 @@
/** Utils for audio tests. */
public class TestUtils {
/**
- * Return a future for an intent delivered by a broadcast receiver which matches an
- * action and predicate.
+ * Return a future for an intent delivered by a broadcast receiver which matches an action and
+ * predicate.
+ *
* @param context - Context to register the receiver with
* @param action - String representing action to register receiver for
- * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves
- * the future unset. If the predicate throws, the future is set exceptionally
+ * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves the
+ * future unset. If the predicate throws, the future is set exceptionally
* @return - The future representing intent delivery matching predicate.
*/
public static ListenableFuture<Intent> getFutureForIntent(
@@ -76,20 +78,77 @@
}
/**
- * Same as previous, but with no predicate.
+ * Return a future for an intent delivered by a broadcast receiver which matches one of a set of
+ * actions and predicate.
+ *
+ * @param context - Context to register the receiver with
+ * @param actionsCollection - Collection of actions which to listen for, completing on any
+ * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves the
+ * future unset. If the predicate throws, the future is set exceptionally
+ * @return - The future representing intent delivery matching predicate.
*/
+ public static ListenableFuture<Intent> getFutureForIntent(
+ Context context, Collection<String> actionsCollection, Predicate<Intent> pred) {
+ // These are evaluated async
+ Objects.requireNonNull(actionsCollection);
+ Objects.requireNonNull(pred);
+ if (actionsCollection.isEmpty()) {
+ throw new IllegalArgumentException("actionsCollection must not be empty");
+ }
+ return getFutureForListener(
+ (recv) ->
+ context.registerReceiver(
+ recv,
+ actionsCollection.stream()
+ .reduce(
+ new IntentFilter(),
+ (IntentFilter filter, String x) -> {
+ filter.addAction(x);
+ return filter;
+ },
+ (x, y) -> {
+ throw new IllegalStateException(
+ "No parallel support");
+ }),
+ Context.RECEIVER_EXPORTED),
+ (recv) -> {
+ try {
+ context.unregisterReceiver(recv);
+ } catch (IllegalArgumentException e) {
+ // Thrown when receiver is already unregistered, nothing to do
+ }
+ },
+ (completer) ->
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ if (actionsCollection.contains(intent.getAction())
+ && pred.test(intent)) {
+ completer.set(intent);
+ }
+ } catch (Exception e) {
+ completer.setException(e);
+ }
+ }
+ },
+ "Intent receiver future for actions: " + actionsCollection);
+ }
+
+ /** Same as previous, but with no predicate. */
public static ListenableFuture<Intent> getFutureForIntent(Context context, String action) {
return getFutureForIntent(context, action, i -> true);
}
/**
* Return a future for a callback registered to a listener interface.
+ *
* @param registerFunc - Function which consumes the callback object for registration
- * @param unregisterFunc - Function which consumes the callback object for unregistration
- * This function is called when the future is completed or cancelled
+ * @param unregisterFunc - Function which consumes the callback object for unregistration This
+ * function is called when the future is completed or cancelled
* @param instantiateCallback - Factory function for the callback object, provided a completer
- * object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference
- * to the future returned by this function
+ * object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference
+ * to the future returned by this function
* @param debug - Debug string contained in future {@code toString} representation.
*/
public static <T, V> ListenableFuture<T> getFutureForListener(
diff --git a/nfc/java/android/nfc/AvailableNfcAntenna.java b/nfc/java/android/nfc/AvailableNfcAntenna.java
index 6e6512a..e76aeb0 100644
--- a/nfc/java/android/nfc/AvailableNfcAntenna.java
+++ b/nfc/java/android/nfc/AvailableNfcAntenna.java
@@ -28,13 +28,13 @@
public final class AvailableNfcAntenna implements Parcelable {
/**
* Location of the antenna on the Y axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen
+ * 0 is the top-left when the user is facing the screen
* and the device orientation is Portrait.
*/
private final int mLocationX;
/**
* Location of the antenna on the Y axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen
+ * 0 is the top-left when the user is facing the screen
* and the device orientation is Portrait.
*/
private final int mLocationY;
@@ -46,7 +46,7 @@
/**
* Location of the antenna on the X axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen
+ * 0 is the top-left when the user is facing the screen
* and the device orientation is Portrait.
*/
public int getLocationX() {
@@ -55,7 +55,7 @@
/**
* Location of the antenna on the Y axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen
+ * 0 is the top-left when the user is facing the screen
* and the device orientation is Portrait.
*/
public int getLocationY() {
diff --git a/nfc/java/android/nfc/NfcAntennaInfo.java b/nfc/java/android/nfc/NfcAntennaInfo.java
index b002ca2..c57b2e0 100644
--- a/nfc/java/android/nfc/NfcAntennaInfo.java
+++ b/nfc/java/android/nfc/NfcAntennaInfo.java
@@ -64,9 +64,9 @@
/**
* Whether the device is foldable. When the device is foldable,
- * the 0, 0 is considered to be bottom-left when the device is unfolded and
+ * the 0, 0 is considered to be top-left when the device is unfolded and
* the screens are facing the user. For non-foldable devices 0, 0
- * is bottom-left when the user is facing the screen.
+ * is top-left when the user is facing the screen.
*/
public boolean isDeviceFoldable() {
return mDeviceFoldable;
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index df5644b..2645360 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -46,20 +46,6 @@
</intent-filter>
</provider>
- <provider android:name="com.android.settingslib.spa.slice.SpaSliceProvider"
- android:authorities="com.android.spa.gallery.slice.provider"
- android:exported="true" >
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.app.slice.category.SLICE" />
- </intent-filter>
- </provider>
-
- <receiver
- android:name="com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver"
- android:exported="false">
- </receiver>
-
<activity
android:name="com.android.settingslib.spa.debug.BlankActivity"
android:exported="true">
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 91bd791..ffd2879 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -55,7 +55,6 @@
import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
import com.android.settingslib.spa.gallery.scaffold.ScrollablePagerPageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
-import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver
/**
* Enum to define all SPP name here.
@@ -120,9 +119,7 @@
override val logger = DebugLogger()
override val browseActivityClass = GalleryMainActivity::class.java
- override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
// For debugging
override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
- override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider"
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
index 96de1a7..6d1d346 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
@@ -27,7 +27,6 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntrySliceData
import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -35,10 +34,8 @@
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.util.createIntent
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
@@ -48,15 +45,10 @@
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
-import com.android.settingslib.spa.slice.createBrowsePendingIntent
-import com.android.settingslib.spa.slice.provider.createDemoActionSlice
-import com.android.settingslib.spa.slice.provider.createDemoBrowseSlice
-import com.android.settingslib.spa.slice.provider.createDemoSlice
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.ui.SettingsIcon
-import kotlinx.coroutines.delay
private const val TAG = "PreferencePage"
@@ -139,26 +131,6 @@
override val enabled = { model.asyncEnable.value }
}
)
- }
- .setSliceDataFn { sliceUri, _ ->
- val createSliceImpl = { s: String ->
- createDemoBrowseSlice(
- sliceUri = sliceUri,
- title = ASYNC_PREFERENCE_TITLE,
- summary = s,
- )
- }
- return@setSliceDataFn object : EntrySliceData() {
- init {
- postValue(createSliceImpl("(loading)"))
- }
-
- override suspend fun asyncRunner() {
- spaLogger.message(TAG, "Async entry loading")
- delay(2000L)
- postValue(createSliceImpl(ASYNC_PREFERENCE_SUMMARY))
- }
- }
}.build()
)
entryList.add(
@@ -176,28 +148,6 @@
}
}
)
- }
- .setSliceDataFn { sliceUri, args ->
- val createSliceImpl = { v: Int ->
- createDemoActionSlice(
- sliceUri = sliceUri,
- title = MANUAL_UPDATE_PREFERENCE_TITLE,
- summary = "manual update value $v",
- )
- }
-
- return@setSliceDataFn object : EntrySliceData() {
- private var tick = args?.getString("init")?.toInt() ?: 0
-
- init {
- postValue(createSliceImpl(tick))
- }
-
- override suspend fun asyncAction() {
- tick++
- postValue(createSliceImpl(tick))
- }
- }
}.build()
)
entryList.add(
@@ -216,33 +166,6 @@
}
}
)
- }
- .setSliceDataFn { sliceUri, args ->
- val createSliceImpl = { v: Int ->
- createDemoBrowseSlice(
- sliceUri = sliceUri,
- title = AUTO_UPDATE_PREFERENCE_TITLE,
- summary = "auto update value $v",
- )
- }
-
- return@setSliceDataFn object : EntrySliceData() {
- private var tick = args?.getString("init")?.toInt() ?: 0
-
- init {
- postValue(createSliceImpl(tick))
- }
-
- override suspend fun asyncRunner() {
- spaLogger.message(TAG, "autoUpdater.active")
- while (true) {
- delay(1000L)
- tick++
- spaLogger.message(TAG, "autoUpdater.value $tick")
- postValue(createSliceImpl(tick))
- }
- }
- }
}.build()
)
@@ -272,22 +195,6 @@
clickRoute = SettingsPageProviderEnum.PREFERENCE.name
)
}
- .setSliceDataFn { sliceUri, _ ->
- val intent = owner.createIntent()?.createBrowsePendingIntent()
- ?: return@setSliceDataFn null
- return@setSliceDataFn object : EntrySliceData() {
- init {
- postValue(
- createDemoSlice(
- sliceUri = sliceUri,
- title = PAGE_TITLE,
- summary = "Injected Entry",
- intent = intent,
- )
- )
- }
- }
- }
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 2d956d5..6e5132b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -17,12 +17,10 @@
package com.android.settingslib.spa.framework.common
import android.app.Activity
-import android.content.BroadcastReceiver
import android.content.Context
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
-import com.android.settingslib.spa.slice.SettingsSliceDataRepository
private const val TAG = "SpaEnvironment"
@@ -69,8 +67,6 @@
val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
- val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) }
-
// The application context. Use local context as fallback when applicationContext is not
// available (e.g. in Robolectric test).
val appContext: Context = context.applicationContext ?: context
@@ -81,11 +77,9 @@
// Specify class name of browse activity and slice broadcast receiver, which is used to
// generate the necessary intents.
open val browseActivityClass: Class<out Activity>? = null
- open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
// Specify provider authorities for debugging purpose.
open val searchProviderAuthorities: String? = null
- open val sliceProviderAuthorities: String? = null
// TODO: add other environment setup here.
companion object {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
deleted file mode 100644
index 7a4750d..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.net.Uri
-import android.util.Log
-import com.android.settingslib.spa.framework.common.EntrySliceData
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
-import com.android.settingslib.spa.framework.util.getEntryId
-
-private const val TAG = "SliceDataRepository"
-
-class SettingsSliceDataRepository(private val entryRepository: SettingsEntryRepository) {
- // The map of slice uri to its EntrySliceData, a.k.a. LiveData<Slice?>
- private val sliceDataMap: MutableMap<String, EntrySliceData> = mutableMapOf()
-
- // Note: mark this function synchronized, so that we can get the same livedata during the
- // whole lifecycle of a Slice.
- @Synchronized
- fun getOrBuildSliceData(sliceUri: Uri): EntrySliceData? {
- val sliceString = sliceUri.getSliceId() ?: return null
- return sliceDataMap[sliceString] ?: buildLiveDataImpl(sliceUri)?.let {
- sliceDataMap[sliceString] = it
- it
- }
- }
-
- fun getActiveSliceData(sliceUri: Uri): EntrySliceData? {
- val sliceString = sliceUri.getSliceId() ?: return null
- val sliceData = sliceDataMap[sliceString] ?: return null
- return if (sliceData.isActive()) sliceData else null
- }
-
- private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? {
- Log.d(TAG, "buildLiveData: $sliceUri")
-
- val entryId = sliceUri.getEntryId() ?: return null
- val entry = entryRepository.getEntry(entryId) ?: return null
- if (!entry.hasSliceSupport) return null
- val arguments = sliceUri.getRuntimeArguments()
- return entry.getSliceData(runtimeArguments = arguments, sliceUri = sliceUri)
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
index f362890..ec89c7c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -16,23 +16,10 @@
package com.android.settingslib.spa.slice
-import android.app.Activity
-import android.app.PendingIntent
-import android.content.BroadcastReceiver
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.util.KEY_DESTINATION
import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
-import com.android.settingslib.spa.framework.util.SESSION_SLICE
-import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS
-import com.android.settingslib.spa.framework.util.appendSpaParams
-import com.android.settingslib.spa.framework.util.getDestination
-import com.android.settingslib.spa.framework.util.getEntryId
// Defines SliceUri, which contains special query parameters:
// -- KEY_DESTINATION: The route that this slice is navigated to.
@@ -45,25 +32,6 @@
return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
}
-fun SliceUri.getDestination(): String? {
- return getQueryParameter(KEY_DESTINATION)
-}
-
-fun SliceUri.getRuntimeArguments(): Bundle {
- val params = Bundle()
- for (queryName in queryParameterNames) {
- if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue
- params.putString(queryName, getQueryParameter(queryName))
- }
- return params
-}
-
-fun SliceUri.getSliceId(): String? {
- val entryId = getEntryId() ?: return null
- val params = getRuntimeArguments()
- return "${entryId}_$params"
-}
-
fun Uri.Builder.appendSpaParams(
destination: String? = null,
entryId: String? = null,
@@ -79,72 +47,3 @@
return this
}
-fun Uri.Builder.fromEntry(
- entry: SettingsEntry,
- authority: String?,
- runtimeArguments: Bundle? = null
-): Uri.Builder {
- if (authority == null) return this
- val sp = entry.containerPage()
- return scheme("content").authority(authority).appendSpaParams(
- destination = sp.buildRoute(),
- entryId = entry.id,
- runtimeArguments = runtimeArguments
- )
-}
-
-fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val sliceBroadcastClass =
- SpaEnvironmentFactory.instance.sliceBroadcastReceiverClass ?: return null
- val entryId = getEntryId() ?: return null
- return createBroadcastPendingIntent(context, sliceBroadcastClass, entryId)
-}
-
-fun SliceUri.createBrowsePendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
- val destination = getDestination() ?: return null
- val entryId = getEntryId()
- return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
-}
-
-fun Intent.createBrowsePendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
- val destination = getDestination() ?: return null
- val entryId = getEntryId()
- return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
-}
-
-private fun createBrowsePendingIntent(
- context: Context,
- browseActivityClass: Class<out Activity>,
- destination: String,
- entryId: String?
-): PendingIntent {
- val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
- .appendSpaParams(destination, entryId, SESSION_SLICE)
- .apply {
- // Set both extra and data (which is a Uri) in Slice Intent:
- // 1) extra is used in SPA navigation framework
- // 2) data is used in Slice framework
- data = Uri.Builder().appendSpaParams(destination, entryId).build()
- flags = Intent.FLAG_ACTIVITY_NEW_TASK
- }
-
- return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
-}
-
-private fun createBroadcastPendingIntent(
- context: Context,
- sliceBroadcastClass: Class<out BroadcastReceiver>,
- entryId: String
-): PendingIntent {
- val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
- .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() }
- return PendingIntent.getBroadcast(
- context, 0 /* requestCode */, intent,
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
- )
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
deleted file mode 100644
index 39cb431..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-
-class SpaSliceBroadcastReceiver : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
- val sliceUri = intent?.data ?: return
- val sliceData = sliceRepository.getActiveSliceData(sliceUri) ?: return
- sliceData.doAction()
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
deleted file mode 100644
index 3496f02..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.net.Uri
-import android.util.Log
-import androidx.lifecycle.Observer
-import androidx.slice.Slice
-import androidx.slice.SliceProvider
-import com.android.settingslib.spa.framework.common.EntrySliceData
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
-
-private const val TAG = "SpaSliceProvider"
-
-class SpaSliceProvider : SliceProvider(), Observer<Slice?> {
- private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? {
- if (!SpaEnvironmentFactory.isReady()) return null
- val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
- return sliceRepository.getOrBuildSliceData(sliceUri)
- }
-
- override fun onBindSlice(sliceUri: Uri): Slice? {
- if (context == null) return null
- Log.d(TAG, "onBindSlice: $sliceUri")
- return getOrPutSliceData(sliceUri)?.value
- }
-
- override fun onSlicePinned(sliceUri: Uri) {
- Log.d(TAG, "onSlicePinned: $sliceUri")
- super.onSlicePinned(sliceUri)
- val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
- runBlocking {
- withContext(Dispatchers.Main) {
- sliceLiveData.observeForever(this@SpaSliceProvider)
- }
- }
- }
-
- override fun onSliceUnpinned(sliceUri: Uri) {
- Log.d(TAG, "onSliceUnpinned: $sliceUri")
- super.onSliceUnpinned(sliceUri)
- val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
- runBlocking {
- withContext(Dispatchers.Main) {
- sliceLiveData.removeObserver(this@SpaSliceProvider)
- }
- }
- }
-
- override fun onChanged(value: Slice?) {
- val uri = value?.uri ?: return
- Log.d(TAG, "onChanged: $uri")
- context?.contentResolver?.notifyChange(uri, null)
- }
-
- override fun onCreateSliceProvider(): Boolean {
- Log.d(TAG, "onCreateSliceProvider")
- return true
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
deleted file mode 100644
index 007f47b..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice.presenter
-
-import android.net.Uri
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.lifecycle.compose.LocalLifecycleOwner
-import androidx.slice.widget.SliceLiveData
-import androidx.slice.widget.SliceView
-
-@Composable
-fun SliceDemo(sliceUri: Uri) {
- val context = LocalContext.current
- val lifecycleOwner = LocalLifecycleOwner.current
- val sliceData = remember {
- SliceLiveData.fromUri(context, sliceUri)
- }
-
- HorizontalDivider()
- AndroidView(
- factory = { localContext ->
- val view = SliceView(localContext)
- view.setShowTitleItems(true)
- view.isScrollable = false
- view
- },
- update = { view -> sliceData.observe(lifecycleOwner, view) }
- )
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
deleted file mode 100644
index e4a7386..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice.provider
-
-import android.app.PendingIntent
-import android.content.Context
-import android.net.Uri
-import androidx.core.R
-import androidx.core.graphics.drawable.IconCompat
-import androidx.slice.Slice
-import androidx.slice.SliceManager
-import androidx.slice.builders.ListBuilder
-import androidx.slice.builders.SliceAction
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.slice.createBroadcastPendingIntent
-import com.android.settingslib.spa.slice.createBrowsePendingIntent
-
-fun createDemoBrowseSlice(sliceUri: Uri, title: String, summary: String): Slice? {
- val intent = sliceUri.createBrowsePendingIntent() ?: return null
- return createDemoSlice(sliceUri, title, summary, intent)
-}
-
-fun createDemoActionSlice(sliceUri: Uri, title: String, summary: String): Slice? {
- val intent = sliceUri.createBroadcastPendingIntent() ?: return null
- return createDemoSlice(sliceUri, title, summary, intent)
-}
-
-fun createDemoSlice(sliceUri: Uri, title: String, summary: String, intent: PendingIntent): Slice? {
- val context = SpaEnvironmentFactory.instance.appContext
- if (!SliceManager.getInstance(context).pinnedSlices.contains(sliceUri)) return null
- return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
- .addRow(ListBuilder.RowBuilder().apply {
- setPrimaryAction(createSliceAction(context, intent))
- setTitle(title)
- setSubtitle(summary)
- }).build()
-}
-
-private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction {
- return SliceAction.create(
- intent,
- IconCompat.createWithResource(context, R.drawable.notification_action_background),
- ListBuilder.ICON_IMAGE,
- "Enter app"
- )
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
deleted file mode 100644
index 341a4a5..0000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.content.Context
-import android.net.Uri
-import androidx.arch.core.executor.testing.InstantTaskExecutorRule
-import androidx.lifecycle.Observer
-import androidx.slice.Slice
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.util.genEntryId
-import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
-import com.android.settingslib.spa.tests.testutils.SppHome
-import com.android.settingslib.spa.tests.testutils.SppLayer2
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class SettingsSliceDataRepositoryTest {
- @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
-
- private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment =
- SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
- private val sliceDataRepository by spaEnvironment.sliceDataRepository
-
- @Test
- fun getOrBuildSliceDataTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Slice empty
- assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull()
-
- // Slice supported
- val page = SppLayer2.createSettingsPage()
- val entryId = genEntryId("Layer2Entry1", page)
- val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
- assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
- assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
- val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
- assertThat(sliceData).isNotNull()
- assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri)).isSameInstanceAs(sliceData)
-
- // Slice unsupported
- val entryId2 = genEntryId("Layer2Entry2", page)
- val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
- assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
- assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
- assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull()
- }
-
- @Test
- fun getActiveSliceDataTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- val page = SppLayer2.createSettingsPage()
- val entryId = genEntryId("Layer2Entry1", page)
- val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
-
- // build slice data first
- val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
-
- // slice data is inactive
- assertThat(sliceData!!.isActive()).isFalse()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
-
- // slice data is active
- val observer = Observer<Slice?> { }
- sliceData.observeForever(observer)
- assertThat(sliceData.isActive()).isTrue()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isSameInstanceAs(sliceData)
-
- // slice data is inactive again
- sliceData.removeObserver(observer)
- assertThat(sliceData.isActive()).isFalse()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
- }
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
index d1c4e51..b489afd 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
@@ -16,91 +16,27 @@
package com.android.settingslib.spa.slice
-import android.content.Context
-import android.content.Intent
import android.net.Uri
import androidx.core.os.bundleOf
-import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SliceUtilTest {
- private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment = SpaEnvironmentForTest(context)
-
@Test
fun sliceUriTest() {
assertThat(Uri.EMPTY.getEntryId()).isNull()
- assertThat(Uri.EMPTY.getDestination()).isNull()
- assertThat(Uri.EMPTY.getRuntimeArguments().size()).isEqualTo(0)
- assertThat(Uri.EMPTY.getSliceId()).isNull()
// valid slice uri
val dest = "myRoute"
val entryId = "myEntry"
val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId)
- assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest)
- assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0)
- assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
val sliceUriWithParams =
Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build()
assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId)
- assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest)
- assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1)
- assertThat(sliceUriWithParams.getSliceId()).isEqualTo("${entryId}_Bundle[{p1=v1}]")
- }
-
- @Test
- fun createBroadcastPendingIntentTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Empty Slice Uri
- assertThat(Uri.EMPTY.createBroadcastPendingIntent()).isNull()
-
- // Valid Slice Uri
- val dest = "myRoute"
- val entryId = "myEntry"
- val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
- val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent()
- assertThat(pendingIntent).isNotNull()
- assertThat(pendingIntent!!.isBroadcast).isTrue()
- assertThat(pendingIntent.isImmutable).isFalse()
- }
-
- @Test
- fun createBrowsePendingIntentTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Empty Slice Uri
- assertThat(Uri.EMPTY.createBrowsePendingIntent()).isNull()
-
- // Empty Intent
- assertThat(Intent().createBrowsePendingIntent()).isNull()
-
- // Valid Slice Uri
- val dest = "myRoute"
- val entryId = "myEntry"
- val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build()
- val pendingIntent = sliceUri.createBrowsePendingIntent()
- assertThat(pendingIntent).isNotNull()
- assertThat(pendingIntent!!.isActivity).isTrue()
- assertThat(pendingIntent.isImmutable).isTrue()
-
- // Valid Intent
- val intent = Intent().apply {
- putExtra("spaActivityDestination", dest)
- putExtra("highlightEntry", entryId)
- }
- val pendingIntent2 = intent.createBrowsePendingIntent()
- assertThat(pendingIntent2).isNotNull()
- assertThat(pendingIntent2!!.isActivity).isTrue()
- assertThat(pendingIntent2.isImmutable).isTrue()
}
}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index 22a5ca3..4f8fd79 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -216,8 +216,6 @@
context: Context,
rootPages: List<SettingsPage> = emptyList(),
override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java,
- override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? =
- BlankSliceBroadcastReceiver::class.java,
override val logger: SpaLogger = object : SpaLogger {}
) : SpaEnvironment(context) {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index b949cd5..ac0b9b4 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -15,6 +15,7 @@
*/
package com.android.settingslib.drawer;
+import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -25,7 +26,9 @@
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Icon;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -36,6 +39,8 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
@@ -102,6 +107,9 @@
/** The key used to get the package name of the icon resource for the preference. */
static final String EXTRA_PREFERENCE_ICON_PACKAGE = "com.android.settings.icon_package";
+ /** The key used for the raw byte data of the icon for the preference. */
+ static final String EXTRA_PREFERENCE_ICON_RAW = "com.android.settings.icon_raw";
+
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the key that should be used for the preference.
@@ -518,6 +526,24 @@
}
/**
+ * Retrieves an icon stored in the Bundle as a Parcel with key EXTRA_PREFERENCE_ICON_RAW
+ */
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
+ @Nullable
+ public static Icon getRawIconFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ return null;
+ }
+ final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
+ if (bundle == null) {
+ return null;
+ }
+
+ return bundle.getParcelable(EXTRA_PREFERENCE_ICON_RAW, Icon.class);
+ }
+
+ /**
* Gets text associated with the input key from the content provider.
*
* @param context context
@@ -564,8 +590,9 @@
return getBundleFromUri(context, uri, providerMap, bundle);
}
- private static Bundle getBundleFromUri(Context context, Uri uri,
- Map<String, IContentProvider> providerMap, Bundle bundle) {
+ @Nullable
+ private static Bundle getBundleFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap, @Nullable Bundle bundle) {
final Pair<String, String> args = getMethodAndKey(uri);
if (args == null) {
return null;
@@ -593,8 +620,9 @@
}
}
- private static IContentProvider getProviderFromUri(Context context, Uri uri,
- Map<String, IContentProvider> providerMap) {
+ @Nullable
+ private static IContentProvider getProviderFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap) {
if (uri == null) {
return null;
}
@@ -609,7 +637,8 @@
}
/** Returns method and key of the complete uri. */
- private static Pair<String, String> getMethodAndKey(Uri uri) {
+ @Nullable
+ private static Pair<String, String> getMethodAndKey(@Nullable Uri uri) {
if (uri == null) {
return null;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManagerExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManagerExt.kt
new file mode 100644
index 0000000..2eaa804
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManagerExt.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth
+
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** [Flow] for [BluetoothCallback] device profile connection state change events */
+val BluetoothEventManager.onProfileConnectionStateChanged: Flow<ProfileConnectionState>
+ get() = callbackFlow {
+ val callback =
+ object : BluetoothCallback {
+ override fun onProfileConnectionStateChanged(
+ cachedDevice: CachedBluetoothDevice,
+ @BluetoothCallback.ConnectionState state: Int,
+ bluetoothProfile: Int
+ ) {
+ launch { send(ProfileConnectionState(cachedDevice, state, bluetoothProfile)) }
+ }
+ }
+ registerCallback(callback)
+ awaitClose { unregisterCallback(callback) }
+ }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
new file mode 100644
index 0000000..91a99ae
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth
+
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothLeBroadcastAssistant
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.bluetooth.BluetoothLeBroadcastReceiveState
+import com.android.internal.util.ConcurrentUtils
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** [Flow] for [BluetoothLeBroadcastAssistant.Callback] source connected/removed events */
+val LocalBluetoothLeBroadcastAssistant.onSourceConnectedOrRemoved: Flow<Unit>
+ get() = callbackFlow {
+ val callback =
+ object : BluetoothLeBroadcastAssistant.Callback {
+ override fun onReceiveStateChanged(
+ sink: BluetoothDevice,
+ sourceId: Int,
+ state: BluetoothLeBroadcastReceiveState
+ ) {
+ if (BluetoothUtils.isConnected(state)) {
+ launch { send(Unit) }
+ }
+ }
+
+ override fun onSourceRemoved(sink: BluetoothDevice, sourceId: Int, reason: Int) {
+ launch { send(Unit) }
+ }
+
+ override fun onSearchStarted(reason: Int) {}
+
+ override fun onSearchStartFailed(reason: Int) {}
+
+ override fun onSearchStopped(reason: Int) {}
+
+ override fun onSearchStopFailed(reason: Int) {}
+
+ override fun onSourceFound(source: BluetoothLeBroadcastMetadata) {}
+
+ override fun onSourceAdded(sink: BluetoothDevice, sourceId: Int, reason: Int) {}
+
+ override fun onSourceAddFailed(
+ sink: BluetoothDevice,
+ source: BluetoothLeBroadcastMetadata,
+ reason: Int
+ ) {}
+
+ override fun onSourceModified(sink: BluetoothDevice, sourceId: Int, reason: Int) {}
+
+ override fun onSourceModifyFailed(
+ sink: BluetoothDevice,
+ sourceId: Int,
+ reason: Int
+ ) {}
+
+ override fun onSourceRemoveFailed(
+ sink: BluetoothDevice,
+ sourceId: Int,
+ reason: Int
+ ) {}
+ }
+ registerServiceCallBack(
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ callback,
+ )
+ awaitClose { unregisterServiceCallBack(callback) }
+ }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/ProfileConnectionState.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/ProfileConnectionState.kt
new file mode 100644
index 0000000..45aaa66
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/ProfileConnectionState.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth
+
+data class ProfileConnectionState(
+ val cachedDevice: CachedBluetoothDevice,
+ @BluetoothCallback.ConnectionState val state: Int,
+ val bluetoothProfile: Int,
+)
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 7f6a8ed..7886e85 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -18,6 +18,9 @@
import android.app.NotificationManager
import android.provider.Settings
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenMode
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -32,6 +35,11 @@
override val globalZenMode: StateFlow<Int>
get() = mutableZenMode.asStateFlow()
+ private val mutableModesFlow: MutableStateFlow<List<ZenMode>> =
+ MutableStateFlow(listOf(TestModeBuilder.EXAMPLE))
+ override val modes: Flow<List<ZenMode>>
+ get() = mutableModesFlow.asStateFlow()
+
init {
updateNotificationPolicy()
}
@@ -43,6 +51,20 @@
fun updateZenMode(zenMode: Int) {
mutableZenMode.value = zenMode
}
+
+ fun addMode(id: String, active: Boolean = false) {
+ mutableModesFlow.value += newMode(id, active)
+ }
+
+ fun removeMode(id: String) {
+ mutableModesFlow.value = mutableModesFlow.value.filter { it.id != id }
+ }
+
+ fun deactivateMode(id: String) {
+ val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
+ removeMode(id)
+ mutableModesFlow.value += TestModeBuilder(oldMode).setActive(false).build()
+ }
}
fun FakeZenModeRepository.updateNotificationPolicy(
@@ -61,5 +83,8 @@
suppressedVisualEffects,
state,
priorityConversationSenders,
- )
- )
+ ))
+
+private fun newMode(id: String, active: Boolean = false): ZenMode {
+ return TestModeBuilder().setId(id).setName("Mode $id").setActive(active).build()
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index 72c3c17..26b5741 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -17,19 +17,28 @@
package com.android.settingslib.notification.data.repository
import android.app.NotificationManager
+import android.app.NotificationManager.EXTRA_NOTIFICATION_POLICY
import android.content.BroadcastReceiver
+import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.database.ContentObserver
import android.os.Handler
+import android.provider.Settings
import com.android.settingslib.flags.Flags
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.settingslib.notification.modes.ZenModesBackend
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -44,11 +53,16 @@
/** @see NotificationManager.getZenMode */
val globalZenMode: StateFlow<Int?>
+
+ /** A list of all existing priority modes. */
+ val modes: Flow<List<ZenMode>>
}
class ZenModeRepositoryImpl(
private val context: Context,
private val notificationManager: NotificationManager,
+ private val backend: ZenModesBackend,
+ private val contentResolver: ContentResolver,
val scope: CoroutineScope,
val backgroundCoroutineContext: CoroutineContext,
// This is nullable just to simplify testing, since SettingsLib doesn't have a good way
@@ -61,7 +75,7 @@
val receiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
- intent?.action?.let { action -> launch { send(action) } }
+ intent?.let { launch { send(it) } }
}
}
@@ -87,7 +101,6 @@
.let {
if (Flags.volumePanelBroadcastFix()) {
it.flowOn(backgroundCoroutineContext)
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
} else {
it.shareIn(
started = SharingStarted.WhileSubscribed(),
@@ -100,7 +113,9 @@
override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi())
flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) {
- notificationManager.consolidatedNotificationPolicy
+ // If available, get the value from extras to avoid a potential binder call.
+ it?.extras?.getParcelable(EXTRA_NOTIFICATION_POLICY)
+ ?: notificationManager.consolidatedNotificationPolicy
}
else
flowFromBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) {
@@ -114,11 +129,52 @@
}
}
- private fun <T> flowFromBroadcast(intentAction: String, mapper: () -> T) =
+ private fun <T> flowFromBroadcast(intentAction: String, mapper: (Intent?) -> T) =
notificationBroadcasts
- .filter { intentAction == it }
- .map { mapper() }
- .onStart { emit(mapper()) }
+ .filter { intentAction == it.action }
+ .map { mapper(it) }
+ .onStart { emit(mapper(null)) }
.flowOn(backgroundCoroutineContext)
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ private val zenConfigChanged by lazy {
+ if (android.app.Flags.modesUi()) {
+ callbackFlow {
+ // emit an initial value
+ trySend(Unit)
+
+ val observer =
+ object : ContentObserver(backgroundHandler) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
+ /* notifyForDescendants= */ false,
+ observer)
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG),
+ /* notifyForDescendants= */ false,
+ observer)
+
+ awaitClose { contentResolver.unregisterContentObserver(observer) }
+ }
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ flowOf(Unit)
+ }
+ }
+
+ override val modes: Flow<List<ZenMode>> by lazy {
+ if (android.app.Flags.modesUi()) {
+ zenConfigChanged
+ .map { backend.modes }
+ .distinctUntilChanged()
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ flowOf(emptyList())
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
new file mode 100644
index 0000000..7b994d5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.notification.modes;
+
+import android.app.AutomaticZenRule;
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.ZenDeviceEffects;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenPolicy;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
+
+import java.util.Random;
+
+public class TestModeBuilder {
+
+ private String mId;
+ private AutomaticZenRule mRule;
+ private ZenModeConfig.ZenRule mConfigZenRule;
+
+ public static final ZenMode EXAMPLE = new TestModeBuilder().build();
+
+ public TestModeBuilder() {
+ // Reasonable defaults
+ int id = new Random().nextInt(1000);
+ mId = "rule_" + id;
+ mRule = new AutomaticZenRule.Builder("Test Rule #" + id, Uri.parse("rule://" + id))
+ .setPackage("some_package")
+ .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
+ .build();
+ mConfigZenRule = new ZenModeConfig.ZenRule();
+ mConfigZenRule.enabled = true;
+ mConfigZenRule.pkg = "some_package";
+ }
+
+ public TestModeBuilder(ZenMode previous) {
+ mId = previous.getId();
+ mRule = previous.getRule();
+
+ mConfigZenRule = new ZenModeConfig.ZenRule();
+ mConfigZenRule.enabled = previous.getRule().isEnabled();
+ mConfigZenRule.pkg = previous.getRule().getPackageName();
+ setActive(previous.isActive());
+ }
+
+ public TestModeBuilder setId(String id) {
+ mId = id;
+ return this;
+ }
+
+ public TestModeBuilder setAzr(AutomaticZenRule rule) {
+ mRule = rule;
+ mConfigZenRule.pkg = rule.getPackageName();
+ mConfigZenRule.conditionId = rule.getConditionId();
+ mConfigZenRule.enabled = rule.isEnabled();
+ return this;
+ }
+
+ public TestModeBuilder setConfigZenRule(ZenModeConfig.ZenRule configZenRule) {
+ mConfigZenRule = configZenRule;
+ return this;
+ }
+
+ public TestModeBuilder setName(String name) {
+ mRule.setName(name);
+ mConfigZenRule.name = name;
+ return this;
+ }
+
+ public TestModeBuilder setPackage(String pkg) {
+ mRule.setPackageName(pkg);
+ mConfigZenRule.pkg = pkg;
+ return this;
+ }
+
+ public TestModeBuilder setOwner(ComponentName owner) {
+ mRule.setOwner(owner);
+ mConfigZenRule.component = owner;
+ return this;
+ }
+
+ public TestModeBuilder setConfigurationActivity(ComponentName configActivity) {
+ mRule.setConfigurationActivity(configActivity);
+ mConfigZenRule.configurationActivity = configActivity;
+ return this;
+ }
+
+ public TestModeBuilder setConditionId(Uri conditionId) {
+ mRule.setConditionId(conditionId);
+ mConfigZenRule.conditionId = conditionId;
+ return this;
+ }
+
+ public TestModeBuilder setType(@AutomaticZenRule.Type int type) {
+ mRule.setType(type);
+ mConfigZenRule.type = type;
+ return this;
+ }
+
+ public TestModeBuilder setInterruptionFilter(
+ @NotificationManager.InterruptionFilter int interruptionFilter) {
+ mRule.setInterruptionFilter(interruptionFilter);
+ mConfigZenRule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
+ interruptionFilter, NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+ return this;
+ }
+
+ public TestModeBuilder setZenPolicy(@Nullable ZenPolicy policy) {
+ mRule.setZenPolicy(policy);
+ mConfigZenRule.zenPolicy = policy;
+ return this;
+ }
+
+ public TestModeBuilder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
+ mRule.setDeviceEffects(deviceEffects);
+ mConfigZenRule.zenDeviceEffects = deviceEffects;
+ return this;
+ }
+
+ public TestModeBuilder setEnabled(boolean enabled) {
+ mRule.setEnabled(enabled);
+ mConfigZenRule.enabled = enabled;
+ return this;
+ }
+
+ public TestModeBuilder setManualInvocationAllowed(boolean allowed) {
+ mRule.setManualInvocationAllowed(allowed);
+ mConfigZenRule.allowManualInvocation = allowed;
+ return this;
+ }
+
+ public TestModeBuilder setTriggerDescription(@Nullable String triggerDescription) {
+ mRule.setTriggerDescription(triggerDescription);
+ mConfigZenRule.triggerDescription = triggerDescription;
+ return this;
+ }
+
+ public TestModeBuilder setIconResId(@DrawableRes int iconResId) {
+ mRule.setIconResId(iconResId);
+ return this;
+ }
+
+ public TestModeBuilder setActive(boolean active) {
+ if (active) {
+ mConfigZenRule.enabled = true;
+ mConfigZenRule.condition = new Condition(mRule.getConditionId(), "...",
+ Condition.STATE_TRUE);
+ } else {
+ mConfigZenRule.condition = null;
+ }
+ return this;
+ }
+
+ public ZenMode build() {
+ return new ZenMode(mId, mRule, mConfigZenRule);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
index 9dbf23e..eb33a7a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
@@ -16,33 +16,84 @@
package com.android.settingslib.volume.data.repository
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothCsipSetCoordinator
+import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothLeBroadcast
import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.bluetooth.BluetoothProfile
+import android.bluetooth.BluetoothVolumeControl
+import android.content.ContentResolver
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings
+import androidx.annotation.IntRange
import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.onProfileConnectionStateChanged
+import com.android.settingslib.bluetooth.onSourceConnectedOrRemoved
import com.android.settingslib.flags.Flags
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.runningFold
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+typealias GroupIdToVolumes = Map<Int, Int>
/** Provides audio sharing functionality. */
interface AudioSharingRepository {
/** Whether the device is in audio sharing. */
val inAudioSharing: Flow<Boolean>
+
+ /** The secondary headset groupId in audio sharing. */
+ val secondaryGroupId: StateFlow<Int>
+
+ /** The headset groupId to volume map during audio sharing. */
+ val volumeMap: StateFlow<GroupIdToVolumes>
+
+ /** Set the volume of secondary headset during audio sharing. */
+ suspend fun setSecondaryVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ volume: Int
+ )
+
+ companion object {
+ const val AUDIO_SHARING_VOLUME_MIN = 0
+ const val AUDIO_SHARING_VOLUME_MAX = 255
+ }
}
+@OptIn(ExperimentalCoroutinesApi::class)
class AudioSharingRepositoryImpl(
- private val localBluetoothManager: LocalBluetoothManager?,
- backgroundCoroutineContext: CoroutineContext,
+ private val context: Context,
+ private val contentResolver: ContentResolver,
+ private val btManager: LocalBluetoothManager?,
+ private val coroutineScope: CoroutineScope,
+ private val backgroundCoroutineContext: CoroutineContext,
) : AudioSharingRepository {
override val inAudioSharing: Flow<Boolean> =
if (Flags.enableLeAudioSharing()) {
- localBluetoothManager?.profileManager?.leAudioBroadcastProfile?.let { leBroadcast ->
+ btManager?.profileManager?.leAudioBroadcastProfile?.let { leBroadcast ->
callbackFlow {
val listener =
object : BluetoothLeBroadcast.Callback {
@@ -92,9 +143,117 @@
flowOf(false)
}
+ private val primaryChange: Flow<Unit> = callbackFlow {
+ val callback =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ launch { send(Unit) }
+ }
+ }
+ contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
+ false,
+ callback)
+ awaitClose { contentResolver.unregisterContentObserver(callback) }
+ }
+
+ override val secondaryGroupId: StateFlow<Int> =
+ if (Flags.volumeDialogAudioSharingFix()) {
+ merge(
+ btManager
+ ?.profileManager
+ ?.leAudioBroadcastAssistantProfile
+ ?.onSourceConnectedOrRemoved
+ ?.map { getSecondaryGroupId() } ?: emptyFlow(),
+ btManager
+ ?.eventManager
+ ?.onProfileConnectionStateChanged
+ ?.filter { profileConnection ->
+ profileConnection.state == BluetoothAdapter.STATE_DISCONNECTED &&
+ profileConnection.bluetoothProfile ==
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+ }
+ ?.map { getSecondaryGroupId() } ?: emptyFlow(),
+ primaryChange.map { getSecondaryGroupId() })
+ .onStart { emit(getSecondaryGroupId()) }
+ .distinctUntilChanged()
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ emptyFlow()
+ }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), getSecondaryGroupId())
+
+ override val volumeMap: StateFlow<GroupIdToVolumes> =
+ if (Flags.volumeDialogAudioSharingFix()) {
+ btManager?.profileManager?.volumeControlProfile?.let { volumeControl ->
+ inAudioSharing.flatMapLatest { isSharing ->
+ if (isSharing) {
+ callbackFlow {
+ val callback =
+ object : BluetoothVolumeControl.Callback {
+ override fun onDeviceVolumeChanged(
+ device: BluetoothDevice,
+ @IntRange(
+ from = AUDIO_SHARING_VOLUME_MIN.toLong(),
+ to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ volume: Int
+ ) {
+ launch { send(Pair(device, volume)) }
+ }
+ }
+ // Once registered, we will receive the initial volume of all
+ // connected BT devices on VolumeControlProfile via callbacks
+ volumeControl.registerCallback(
+ ConcurrentUtils.DIRECT_EXECUTOR, callback)
+ awaitClose { volumeControl.unregisterCallback(callback) }
+ }
+ .runningFold(emptyMap<Int, Int>()) { acc, value ->
+ val groupId =
+ BluetoothUtils.getGroupId(
+ btManager.cachedDeviceManager?.findDevice(value.first))
+ if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ acc + Pair(groupId, value.second)
+ } else {
+ acc
+ }
+ }
+ .distinctUntilChanged()
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ emptyFlow()
+ }
+ }
+ } ?: emptyFlow()
+ } else {
+ emptyFlow()
+ }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyMap())
+
+ override suspend fun setSecondaryVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ volume: Int
+ ) {
+ withContext(backgroundCoroutineContext) {
+ if (Flags.volumeDialogAudioSharingFix()) {
+ btManager?.profileManager?.volumeControlProfile?.let {
+ // Find secondary headset and set volume.
+ val cachedDevice =
+ BluetoothUtils.getSecondaryDeviceForBroadcast(context, btManager)
+ if (cachedDevice != null) {
+ it.setDeviceVolume(cachedDevice.device, volume, /* isGroupOp= */ true)
+ }
+ }
+ }
+ }
+ }
+
private fun isBroadcasting(): Boolean {
return Flags.enableLeAudioSharing() &&
- (localBluetoothManager?.profileManager?.leAudioBroadcastProfile?.isEnabled(null)
- ?: false)
+ (btManager?.profileManager?.leAudioBroadcastProfile?.isEnabled(null) ?: false)
+ }
+
+ private fun getSecondaryGroupId(): Int {
+ return BluetoothUtils.getGroupId(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(context, btManager))
}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
index 1c80ef4..000664d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
@@ -16,15 +16,33 @@
package com.android.settingslib.volume.data.repository
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothLeBroadcast
+import android.bluetooth.BluetoothLeBroadcastAssistant
+import android.bluetooth.BluetoothLeBroadcastReceiveState
+import android.bluetooth.BluetoothProfile
+import android.bluetooth.BluetoothVolumeControl
+import android.content.ContentResolver
+import android.content.Context
+import android.database.ContentObserver
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothCallback
+import com.android.settingslib.bluetooth.BluetoothEventManager
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.settingslib.bluetooth.VolumeControlProfile
import com.android.settingslib.flags.Flags
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -39,6 +57,9 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.never
@@ -52,27 +73,76 @@
@RunWith(AndroidJUnit4::class)
class AudioSharingRepositoryTest {
@get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
+
@get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
- @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
- @Mock private lateinit var localBluetoothProfileManager: LocalBluetoothProfileManager
- @Mock private lateinit var localBluetoothLeBroadcast: LocalBluetoothLeBroadcast
+ @Mock private lateinit var btManager: LocalBluetoothManager
+
+ @Mock private lateinit var profileManager: LocalBluetoothProfileManager
+
+ @Mock private lateinit var broadcast: LocalBluetoothLeBroadcast
+
+ @Mock private lateinit var assistant: LocalBluetoothLeBroadcastAssistant
+
+ @Mock private lateinit var volumeControl: VolumeControlProfile
+
+ @Mock private lateinit var eventManager: BluetoothEventManager
+
+ @Mock private lateinit var deviceManager: CachedBluetoothDeviceManager
+
+ @Mock private lateinit var device1: BluetoothDevice
+
+ @Mock private lateinit var device2: BluetoothDevice
+
+ @Mock private lateinit var cachedDevice1: CachedBluetoothDevice
+
+ @Mock private lateinit var cachedDevice2: CachedBluetoothDevice
+
+ @Mock private lateinit var receiveState: BluetoothLeBroadcastReceiveState
+
+ @Mock private lateinit var contentResolver: ContentResolver
@Captor
- private lateinit var leBroadcastCallbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
- private val testScope = TestScope()
+ private lateinit var broadcastCallbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
+ @Captor
+ private lateinit var assistantCallbackCaptor:
+ ArgumentCaptor<BluetoothLeBroadcastAssistant.Callback>
+
+ @Captor private lateinit var btCallbackCaptor: ArgumentCaptor<BluetoothCallback>
+
+ @Captor private lateinit var contentObserverCaptor: ArgumentCaptor<ContentObserver>
+
+ @Captor
+ private lateinit var volumeCallbackCaptor: ArgumentCaptor<BluetoothVolumeControl.Callback>
+
+ private val testScope = TestScope()
+ private val context: Context = ApplicationProvider.getApplicationContext()
private lateinit var underTest: AudioSharingRepository
@Before
fun setup() {
- `when`(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager)
- `when`(localBluetoothProfileManager.leAudioBroadcastProfile)
- .thenReturn(localBluetoothLeBroadcast)
- `when`(localBluetoothLeBroadcast.isEnabled(null)).thenReturn(true)
+ `when`(btManager.profileManager).thenReturn(profileManager)
+ `when`(profileManager.leAudioBroadcastProfile).thenReturn(broadcast)
+ `when`(profileManager.leAudioBroadcastAssistantProfile).thenReturn(assistant)
+ `when`(profileManager.volumeControlProfile).thenReturn(volumeControl)
+ `when`(btManager.eventManager).thenReturn(eventManager)
+ `when`(btManager.cachedDeviceManager).thenReturn(deviceManager)
+ `when`(broadcast.isEnabled(null)).thenReturn(true)
+ `when`(cachedDevice1.groupId).thenReturn(TEST_GROUP_ID1)
+ `when`(cachedDevice1.device).thenReturn(device1)
+ `when`(deviceManager.findDevice(device1)).thenReturn(cachedDevice1)
+ `when`(cachedDevice2.groupId).thenReturn(TEST_GROUP_ID2)
+ `when`(cachedDevice2.device).thenReturn(device2)
+ `when`(deviceManager.findDevice(device2)).thenReturn(cachedDevice2)
+ `when`(receiveState.bisSyncState).thenReturn(arrayListOf(TEST_RECEIVE_STATE_CONTENT))
+ `when`(assistant.getAllSources(any())).thenReturn(listOf(receiveState))
underTest =
AudioSharingRepositoryImpl(
- localBluetoothManager,
+ context,
+ contentResolver,
+ btManager,
+ testScope.backgroundScope,
testScope.testScheduler,
)
}
@@ -84,9 +154,9 @@
val states = mutableListOf<Boolean?>()
underTest.inAudioSharing.onEach { states.add(it) }.launchIn(backgroundScope)
runCurrent()
- triggerAudioSharingStateChange(false)
+ triggerAudioSharingStateChange(TriggerType.BROADCAST_STOP, broadcastStopped)
runCurrent()
- triggerAudioSharingStateChange(true)
+ triggerAudioSharingStateChange(TriggerType.BROADCAST_START, broadcastStarted)
runCurrent()
Truth.assertThat(states).containsExactly(true, false, true)
@@ -102,19 +172,229 @@
runCurrent()
Truth.assertThat(states).containsExactly(false)
- verify(localBluetoothLeBroadcast, never()).registerServiceCallBack(any(), any())
- verify(localBluetoothLeBroadcast, never()).isEnabled(any())
+ verify(broadcast, never()).registerServiceCallBack(any(), any())
+ verify(broadcast, never()).isEnabled(any())
}
}
- private fun triggerAudioSharingStateChange(inAudioSharing: Boolean) {
- verify(localBluetoothLeBroadcast)
- .registerServiceCallBack(any(), leBroadcastCallbackCaptor.capture())
- `when`(localBluetoothLeBroadcast.isEnabled(null)).thenReturn(inAudioSharing)
- if (inAudioSharing) {
- leBroadcastCallbackCaptor.value.onBroadcastStarted(0, 0)
- } else {
- leBroadcastCallbackCaptor.value.onBroadcastStopped(0, 0)
+ @Test
+ @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun secondaryGroupIdChange_emitValues() {
+ testScope.runTest {
+ val groupIds = mutableListOf<Int?>()
+ underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ triggerSourceAdded()
+ runCurrent()
+ triggerContentObserverChange()
+ runCurrent()
+ triggerSourceRemoved()
+ runCurrent()
+ triggerSourceAdded()
+ runCurrent()
+ triggerProfileConnectionChange(
+ BluetoothAdapter.STATE_CONNECTING, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+ runCurrent()
+ triggerProfileConnectionChange(
+ BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO)
+ runCurrent()
+ triggerProfileConnectionChange(
+ BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+ runCurrent()
+
+ Truth.assertThat(groupIds)
+ .containsExactly(
+ TEST_GROUP_ID_INVALID,
+ TEST_GROUP_ID2,
+ TEST_GROUP_ID1,
+ TEST_GROUP_ID_INVALID,
+ TEST_GROUP_ID2,
+ TEST_GROUP_ID_INVALID)
}
}
+
+ @Test
+ @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun secondaryGroupIdChange_audioSharingFlagOff_returnFalse() {
+ testScope.runTest {
+ val groupIds = mutableListOf<Int?>()
+ underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ Truth.assertThat(groupIds).containsExactly(TEST_GROUP_ID_INVALID)
+ verify(assistant, never()).registerServiceCallBack(any(), any())
+ verify(eventManager, never()).registerCallback(any())
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun volumeMapChange_emitValues() {
+ testScope.runTest {
+ val volumeMaps = mutableListOf<GroupIdToVolumes?>()
+ underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ triggerVolumeMapChange(Pair(device1, TEST_VOLUME1))
+ runCurrent()
+ triggerVolumeMapChange(Pair(device1, TEST_VOLUME2))
+ runCurrent()
+ triggerAudioSharingStateChange(TriggerType.BROADCAST_STOP, broadcastStopped)
+ runCurrent()
+ verify(volumeControl).unregisterCallback(any())
+ runCurrent()
+
+ Truth.assertThat(volumeMaps)
+ .containsExactly(
+ emptyMap<Int, Int>(),
+ mapOf(TEST_GROUP_ID1 to TEST_VOLUME1),
+ mapOf(TEST_GROUP_ID1 to TEST_VOLUME2))
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun volumeMapChange_audioSharingFlagOff_returnFalse() {
+ testScope.runTest {
+ val volumeMaps = mutableListOf<GroupIdToVolumes?>()
+ underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ Truth.assertThat(volumeMaps).isEmpty()
+ verify(broadcast, never()).registerServiceCallBack(any(), any())
+ verify(volumeControl, never()).registerCallback(any(), any())
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun setSecondaryVolume_setValue() {
+ testScope.runTest {
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID2)
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+ underTest.setSecondaryVolume(TEST_VOLUME1)
+
+ runCurrent()
+ verify(volumeControl).setDeviceVolume(device1, TEST_VOLUME1, true)
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun setSecondaryVolume_audioSharingFlagOff_doNothing() {
+ testScope.runTest {
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID2)
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+ underTest.setSecondaryVolume(TEST_VOLUME1)
+
+ runCurrent()
+ verify(volumeControl, never()).setDeviceVolume(any(), anyInt(), anyBoolean())
+ }
+ }
+
+ private fun triggerAudioSharingStateChange(
+ type: TriggerType,
+ broadcastAction: BluetoothLeBroadcast.Callback.() -> Unit
+ ) {
+ verify(broadcast).registerServiceCallBack(any(), broadcastCallbackCaptor.capture())
+ when (type) {
+ TriggerType.BROADCAST_START -> {
+ `when`(broadcast.isEnabled(null)).thenReturn(true)
+ broadcastCallbackCaptor.value.broadcastAction()
+ }
+ TriggerType.BROADCAST_STOP -> {
+ `when`(broadcast.isEnabled(null)).thenReturn(false)
+ broadcastCallbackCaptor.value.broadcastAction()
+ }
+ }
+ }
+
+ private fun triggerSourceAdded() {
+ verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID1)
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+ assistantCallbackCaptor.value.sourceAdded(device1, receiveState)
+ }
+
+ private fun triggerSourceRemoved() {
+ verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID1)
+ assistantCallbackCaptor.value.sourceRemoved(device2)
+ }
+
+ private fun triggerProfileConnectionChange(state: Int, profile: Int) {
+ verify(eventManager).registerCallback(btCallbackCaptor.capture())
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID1)
+ btCallbackCaptor.value.onProfileConnectionStateChanged(cachedDevice2, state, profile)
+ }
+
+ private fun triggerContentObserverChange() {
+ verify(contentResolver)
+ .registerContentObserver(
+ eq(Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast())),
+ eq(false),
+ contentObserverCaptor.capture())
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID2)
+ contentObserverCaptor.value.primaryChanged()
+ }
+
+ private fun triggerVolumeMapChange(change: Pair<BluetoothDevice, Int>) {
+ verify(volumeControl).registerCallback(any(), volumeCallbackCaptor.capture())
+ volumeCallbackCaptor.value.onDeviceVolumeChanged(change.first, change.second)
+ }
+
+ private enum class TriggerType {
+ BROADCAST_START,
+ BROADCAST_STOP
+ }
+
+ private companion object {
+ const val TEST_GROUP_ID_INVALID = -1
+ const val TEST_GROUP_ID1 = 1
+ const val TEST_GROUP_ID2 = 2
+ const val TEST_SOURCE_ID = 1
+ const val TEST_BROADCAST_ID = 1
+ const val TEST_REASON = 1
+ const val TEST_RECEIVE_STATE_CONTENT = 1L
+ const val TEST_VOLUME1 = 10
+ const val TEST_VOLUME2 = 20
+
+ val broadcastStarted: BluetoothLeBroadcast.Callback.() -> Unit = {
+ onBroadcastStarted(TEST_REASON, TEST_BROADCAST_ID)
+ }
+ val broadcastStopped: BluetoothLeBroadcast.Callback.() -> Unit = {
+ onBroadcastStopped(TEST_REASON, TEST_BROADCAST_ID)
+ }
+ val sourceAdded:
+ BluetoothLeBroadcastAssistant.Callback.(
+ sink: BluetoothDevice, state: BluetoothLeBroadcastReceiveState) -> Unit =
+ { sink, state ->
+ onReceiveStateChanged(sink, TEST_SOURCE_ID, state)
+ }
+ val sourceRemoved: BluetoothLeBroadcastAssistant.Callback.(sink: BluetoothDevice) -> Unit =
+ { sink ->
+ onSourceRemoved(sink, TEST_SOURCE_ID, TEST_REASON)
+ }
+ val primaryChanged: ContentObserver.() -> Unit = { onChange(false) }
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index 06333b61..4bd5cc4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -18,13 +18,19 @@
import android.app.NotificationManager
import android.content.BroadcastReceiver
+import android.content.ContentResolver
import android.content.Context
import android.content.Intent
+import android.database.ContentObserver
+import android.os.Parcelable
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings.Global
import androidx.test.filters.SmallTest
import com.android.settingslib.flags.Flags
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.settingslib.notification.modes.ZenModesBackend
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -36,6 +42,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.any
@@ -52,8 +59,16 @@
@Mock private lateinit var notificationManager: NotificationManager
+ @Mock private lateinit var zenModesBackend: ZenModesBackend
+
+ @Mock private lateinit var contentResolver: ContentResolver
+
@Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ @Captor private lateinit var zenModeObserverCaptor: ArgumentCaptor<ContentObserver>
+
+ @Captor private lateinit var zenConfigObserverCaptor: ArgumentCaptor<ContentObserver>
+
private lateinit var underTest: ZenModeRepository
private val testScope: TestScope = TestScope()
@@ -66,6 +81,8 @@
ZenModeRepositoryImpl(
context,
notificationManager,
+ zenModesBackend,
+ contentResolver,
testScope.backgroundScope,
testScope.testScheduler,
backgroundHandler = null,
@@ -110,6 +127,26 @@
}
}
+ @EnableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
+ @Test
+ fun consolidatedPolicyChanges_repositoryEmitsFromExtras() {
+ testScope.runTest {
+ val values = mutableListOf<NotificationManager.Policy?>()
+ `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy1)
+ underTest.consolidatedNotificationPolicy
+ .onEach { values.add(it) }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ triggerIntent(
+ NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED,
+ extras = mapOf(NotificationManager.EXTRA_NOTIFICATION_POLICY to testPolicy2))
+ runCurrent()
+
+ assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
+ }
+ }
+
@Test
fun zenModeChanges_repositoryEmits() {
testScope.runTest {
@@ -128,9 +165,63 @@
}
}
- private fun triggerIntent(action: String) {
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI)
+ @Test
+ fun modesListEmitsOnSettingsChange() {
+ testScope.runTest {
+ val values = mutableListOf<List<ZenMode>>()
+ val modes1 = listOf(TestModeBuilder().setId("One").build())
+ `when`(zenModesBackend.modes).thenReturn(modes1)
+ underTest.modes.onEach { values.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ // zen mode change triggers update
+ val modes2 = listOf(TestModeBuilder().setId("Two").build())
+ `when`(zenModesBackend.modes).thenReturn(modes2)
+ triggerZenModeSettingUpdate()
+ runCurrent()
+
+ // zen config change also triggers update
+ val modes3 = listOf(TestModeBuilder().setId("Three").build())
+ `when`(zenModesBackend.modes).thenReturn(modes3)
+ triggerZenConfigSettingUpdate()
+ runCurrent()
+
+ // setting update with no list change doesn't trigger update
+ triggerZenModeSettingUpdate()
+ runCurrent()
+
+ assertThat(values).containsExactly(modes1, modes2, modes3).inOrder()
+ }
+ }
+
+ private fun triggerIntent(action: String, extras: Map<String, Parcelable>? = null) {
verify(context).registerReceiver(receiverCaptor.capture(), any(), any(), any())
- receiverCaptor.value.onReceive(context, Intent(action))
+ val intent = Intent(action)
+ if (extras?.isNotEmpty() == true) {
+ extras.forEach { (key, value) -> intent.putExtra(key, value) }
+ }
+ receiverCaptor.value.onReceive(context, intent)
+ }
+
+ private fun triggerZenModeSettingUpdate() {
+ verify(contentResolver)
+ .registerContentObserver(
+ eq(Global.getUriFor(Global.ZEN_MODE)),
+ eq(false),
+ zenModeObserverCaptor.capture(),
+ )
+ zenModeObserverCaptor.value.onChange(false)
+ }
+
+ private fun triggerZenConfigSettingUpdate() {
+ verify(contentResolver)
+ .registerContentObserver(
+ eq(Global.getUriFor(Global.ZEN_MODE_CONFIG_ETAG)),
+ eq(false),
+ zenConfigObserverCaptor.capture(),
+ )
+ zenConfigObserverCaptor.value.onChange(false)
}
private companion object {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
index 8dd51b2..8de0c35 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
@@ -22,8 +22,6 @@
import android.content.ContentResolver;
import android.os.Bundle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
@@ -73,24 +71,9 @@
}
/**
- * Test that setting overrides are properly disabled when the flag is off.
- */
- @Test
- @RequiresFlagsDisabled("com.android.providers.settings.support_overrides")
- public void testOverrideDisabled() throws IOException {
- final String newValue = "value2";
-
- executeShellCommand("device_config put " + sNamespace + " " + sKey + " " + sValue);
- executeShellCommand("device_config override " + sNamespace + " " + sKey + " " + newValue);
- String result = readShellCommandOutput("device_config get " + sNamespace + " " + sKey);
- assertEquals(sValue + "\n", result);
- }
-
- /**
* Test that overrides are readable and can be cleared.
*/
@Test
- @RequiresFlagsEnabled("com.android.providers.settings.support_overrides")
public void testOverride() throws IOException {
final String newValue = "value2";
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 43d51c3..92f03d7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -45,7 +45,7 @@
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
-import com.android.systemui.communal.ui.compose.Dimensions.SlideOffsetY
+import com.android.systemui.communal.ui.compose.Dimensions.Companion.SlideOffsetY
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 8f247f6..ef6eec8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -16,6 +16,8 @@
package com.android.systemui.communal.ui.compose
+import android.content.Context
+import android.content.res.Configuration
import android.graphics.drawable.Icon
import android.os.Bundle
import android.util.SizeF
@@ -40,6 +42,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -64,6 +67,7 @@
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
@@ -114,6 +118,7 @@
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
@@ -127,6 +132,7 @@
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
@@ -386,6 +392,10 @@
}
}
+val hubDimensions: Dimensions
+ @Composable
+ get() = Dimensions(LocalContext.current, LocalConfiguration.current, LocalDensity.current)
+
@Composable
private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
val colors = LocalAndroidColorScheme.current
@@ -507,7 +517,6 @@
gridState = gridState,
contentListState = contentListState,
contentOffset = contentOffset,
- updateDragPositionForRemove = updateDragPositionForRemove
)
// A full size box in background that listens to widget drops from the picker.
@@ -515,7 +524,7 @@
// for android drag events.
Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
} else {
- gridModifier = gridModifier.height(Dimensions.GridHeight)
+ gridModifier = gridModifier.height(hubDimensions.GridHeight)
}
LazyHorizontalGrid(
@@ -593,7 +602,7 @@
) {
val colors = LocalAndroidColorScheme.current
Card(
- modifier = Modifier.height(Dimensions.GridHeight).padding(contentPadding),
+ modifier = Modifier.height(hubDimensions.GridHeight).padding(contentPadding),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
border = BorderStroke(3.dp, colors.secondary),
shape = RoundedCornerShape(size = 80.dp)
@@ -963,9 +972,25 @@
val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle()
val selectedIndex =
selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
+
+ val isSelected = selectedKey == model.key
+
+ val selectableModifier =
+ if (viewModel.isEditMode) {
+ Modifier.selectable(
+ selected = isSelected,
+ onClick = { viewModel.setSelectedKey(model.key) },
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ )
+ } else {
+ Modifier
+ }
+
Box(
modifier =
modifier
+ .then(selectableModifier)
.thenIf(!viewModel.isEditMode && model.inQuietMode) {
Modifier.pointerInput(Unit) {
// consume tap to prevent the child view from triggering interactions with
@@ -1262,7 +1287,7 @@
return PaddingValues(
start = Dimensions.ItemSpacing,
end = Dimensions.ItemSpacing,
- top = Dimensions.GridTopSpacing,
+ top = hubDimensions.GridTopSpacing,
)
}
val context = LocalContext.current
@@ -1271,7 +1296,8 @@
val screenHeight = with(density) { windowMetrics.bounds.height().toDp() }
val toolbarHeight = with(density) { Dimensions.ToolbarPaddingTop + toolbarSize.height.toDp() }
val verticalPadding =
- ((screenHeight - toolbarHeight - Dimensions.GridHeight + Dimensions.GridTopSpacing) / 2)
+ ((screenHeight - toolbarHeight - hubDimensions.GridHeight + hubDimensions.GridTopSpacing) /
+ 2)
.coerceAtLeast(Dimensions.Spacing)
return PaddingValues(
start = Dimensions.ToolbarPaddingHorizontal,
@@ -1327,29 +1353,44 @@
fun toOffset(): Offset = Offset(start, top)
}
-object Dimensions {
- val CardHeightFull = 530.dp
- val GridTopSpacing = 114.dp
- val GridHeight = CardHeightFull + GridTopSpacing
- val ItemSpacing = 50.dp
- val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2
- val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3
- val CardWidth = 360.dp
- val CardOutlineWidth = 3.dp
- val Spacing = ItemSpacing / 2
+class Dimensions(val context: Context, val config: Configuration, val density: Density) {
+ val GridTopSpacing: Dp
+ get() {
+ if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ return 114.dp
+ } else {
+ val windowMetrics =
+ WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
+ val screenHeight = with(density) { windowMetrics.bounds.height().toDp() }
- // The sizing/padding of the toolbar in glanceable hub edit mode
- val ToolbarPaddingTop = 27.dp
- val ToolbarPaddingHorizontal = ItemSpacing
- val ToolbarButtonPaddingHorizontal = 24.dp
- val ToolbarButtonPaddingVertical = 16.dp
- val ButtonPadding =
- PaddingValues(
- vertical = ToolbarButtonPaddingVertical,
- horizontal = ToolbarButtonPaddingHorizontal,
- )
- val IconSize = 40.dp
- val SlideOffsetY = 30.dp
+ return (screenHeight - CardHeightFull) / 2
+ }
+ }
+
+ val GridHeight = CardHeightFull + GridTopSpacing
+
+ companion object {
+ val CardHeightFull = 530.dp
+ val ItemSpacing = 50.dp
+ val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2
+ val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3
+ val CardWidth = 360.dp
+ val CardOutlineWidth = 3.dp
+ val Spacing = ItemSpacing / 2
+
+ // The sizing/padding of the toolbar in glanceable hub edit mode
+ val ToolbarPaddingTop = 27.dp
+ val ToolbarPaddingHorizontal = ItemSpacing
+ val ToolbarButtonPaddingHorizontal = 24.dp
+ val ToolbarButtonPaddingVertical = 16.dp
+ val ButtonPadding =
+ PaddingValues(
+ vertical = ToolbarButtonPaddingVertical,
+ horizontal = ToolbarButtonPaddingHorizontal,
+ )
+ val IconSize = 40.dp
+ val SlideOffsetY = 30.dp
+ }
}
private object Colors {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index 9e6f22a..0c29394 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -18,17 +18,13 @@
import android.content.ClipDescription
import android.view.DragEvent
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.draganddrop.dragAndDropTarget
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollBy
-import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
@@ -45,8 +41,7 @@
import com.android.systemui.communal.util.WidgetPickerIntentUtils
import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.isActive
+import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
/**
@@ -59,32 +54,22 @@
gridState: LazyGridState,
contentOffset: Offset,
contentListState: ContentListState,
- updateDragPositionForRemove: (offset: Offset) -> Boolean,
): DragAndDropTargetState {
val scope = rememberCoroutineScope()
- val autoScrollSpeed = remember { mutableFloatStateOf(0f) }
- // Threshold of distance from edges that should start auto-scroll - chosen to be a narrow value
- // that allows differentiating intention of scrolling from intention of dragging over the first
- // visible item.
val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() }
val state =
- remember(gridState, contentListState) {
+ remember(gridState, contentOffset, contentListState, autoScrollThreshold, scope) {
DragAndDropTargetState(
state = gridState,
contentOffset = contentOffset,
contentListState = contentListState,
- scope = scope,
- autoScrollSpeed = autoScrollSpeed,
autoScrollThreshold = autoScrollThreshold,
- updateDragPositionForRemove = updateDragPositionForRemove,
+ scope = scope,
)
}
- LaunchedEffect(autoScrollSpeed.floatValue) {
- if (autoScrollSpeed.floatValue != 0f) {
- while (isActive) {
- gridState.scrollBy(autoScrollSpeed.floatValue)
- delay(10)
- }
+ LaunchedEffect(state) {
+ for (diff in state.scrollChannel) {
+ gridState.scrollBy(diff)
}
}
return state
@@ -96,7 +81,6 @@
* @see androidx.compose.foundation.draganddrop.dragAndDropTarget
* @see DragEvent
*/
-@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun Modifier.dragAndDropTarget(
dragDropTargetState: DragAndDropTargetState,
@@ -122,6 +106,10 @@
return state.onDrop(event)
}
+ override fun onExited(event: DragAndDropEvent) {
+ state.onExited()
+ }
+
override fun onEnded(event: DragAndDropEvent) {
state.onEnded()
}
@@ -149,19 +137,17 @@
private val state: LazyGridState,
private val contentOffset: Offset,
private val contentListState: ContentListState,
- private val scope: CoroutineScope,
- private val autoScrollSpeed: MutableState<Float>,
private val autoScrollThreshold: Float,
- private val updateDragPositionForRemove: (offset: Offset) -> Boolean,
+ private val scope: CoroutineScope,
) {
/**
* The placeholder item that is treated as if it is being dragged across the grid. It is added
* to grid once drag and drop event is started and removed when event ends.
*/
private var placeHolder = CommunalContentModel.WidgetPlaceholder()
-
private var placeHolderIndex: Int? = null
- private var isOnRemoveButton = false
+
+ internal val scrollChannel = Channel<Float>()
fun onStarted() {
// assume item will be added to the end.
@@ -170,39 +156,39 @@
}
fun onMoved(event: DragAndDropEvent) {
- val dragEvent = event.toAndroidDragEvent()
- isOnRemoveButton = updateDragPositionForRemove(Offset(dragEvent.x, dragEvent.y))
- if (!isOnRemoveButton) {
- findTargetItem(dragEvent)?.apply {
- var scrollIndex: Int? = null
- var scrollOffset: Int? = null
- if (placeHolderIndex == state.firstVisibleItemIndex) {
- // Save info about the first item before the move, to neutralize the automatic
- // keeping first item first.
- scrollIndex = placeHolderIndex
- scrollOffset = state.firstVisibleItemScrollOffset
- }
+ val dragOffset = event.toOffset()
- autoScrollIfNearEdges(dragEvent)
+ val targetItem =
+ state.layoutInfo.visibleItemsInfo
+ .asSequence()
+ .filter { item -> contentListState.isItemEditable(item.index) }
+ .firstItemAtOffset(dragOffset - contentOffset)
- if (contentListState.isItemEditable(this.index)) {
- movePlaceholderTo(this.index)
- placeHolderIndex = this.index
- }
-
- if (scrollIndex != null && scrollOffset != null) {
- // this is needed to neutralize automatic keeping the first item first.
- scope.launch { state.scrollToItem(scrollIndex, scrollOffset) }
- }
+ if (targetItem != null) {
+ var scrollIndex: Int? = null
+ var scrollOffset: Int? = null
+ if (placeHolderIndex == state.firstVisibleItemIndex) {
+ // Save info about the first item before the move, to neutralize the automatic
+ // keeping first item first.
+ scrollIndex = placeHolderIndex
+ scrollOffset = state.firstVisibleItemScrollOffset
}
+
+ if (contentListState.isItemEditable(targetItem.index)) {
+ movePlaceholderTo(targetItem.index)
+ placeHolderIndex = targetItem.index
+ }
+
+ if (scrollIndex != null && scrollOffset != null) {
+ // this is needed to neutralize automatic keeping the first item first.
+ scope.launch { state.scrollToItem(scrollIndex, scrollOffset) }
+ }
+ } else {
+ computeAutoscroll(dragOffset).takeIf { it != 0f }?.let { scrollChannel.trySend(it) }
}
}
fun onDrop(event: DragAndDropEvent): Boolean {
- autoScrollSpeed.value = 0f
- if (isOnRemoveButton) {
- return false
- }
return placeHolderIndex?.let { dropIndex ->
val widgetExtra = event.maybeWidgetExtra() ?: return false
val (componentName, user) = widgetExtra
@@ -221,39 +207,35 @@
}
fun onEnded() {
- autoScrollSpeed.value = 0f
placeHolderIndex = null
contentListState.list.remove(placeHolder)
- isOnRemoveButton = updateDragPositionForRemove(Offset.Zero)
}
- private fun autoScrollIfNearEdges(dragEvent: DragEvent) {
+ fun onExited() {
+ onEnded()
+ }
+
+ private fun computeAutoscroll(dragOffset: Offset): Float {
val orientation = state.layoutInfo.orientation
val distanceFromStart =
if (orientation == Orientation.Horizontal) {
- dragEvent.x
+ dragOffset.x
} else {
- dragEvent.y
+ dragOffset.y
}
val distanceFromEnd =
if (orientation == Orientation.Horizontal) {
- state.layoutInfo.viewportSize.width - dragEvent.x
+ state.layoutInfo.viewportEndOffset - dragOffset.x
} else {
- state.layoutInfo.viewportSize.height - dragEvent.y
+ state.layoutInfo.viewportEndOffset - dragOffset.y
}
- autoScrollSpeed.value =
- when {
- distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd
- distanceFromStart < autoScrollThreshold ->
- -(autoScrollThreshold - distanceFromStart)
- else -> 0f
- }
- }
- private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? =
- state.layoutInfo.visibleItemsInfo.firstItemAtOffset(
- Offset(dragEvent.x, dragEvent.y) - contentOffset
- )
+ return when {
+ distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd
+ distanceFromStart < autoScrollThreshold -> distanceFromStart - autoScrollThreshold
+ else -> 0f
+ }
+ }
private fun movePlaceholderTo(index: Int) {
val currentIndex = contentListState.list.indexOf(placeHolder)
@@ -271,4 +253,6 @@
val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 }
return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) }
}
+
+ private fun DragAndDropEvent.toOffset() = this.toAndroidDragEvent().run { Offset(x, y) }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
index 1ea73e1..620892a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
@@ -23,6 +23,8 @@
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -39,11 +41,16 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
@@ -54,6 +61,7 @@
import com.android.systemui.communal.ui.viewmodel.PopupType
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.delay
class CommunalPopupSection
@Inject
@@ -91,6 +99,17 @@
onClick: () -> Unit,
onDismissRequest: () -> Unit,
) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val focusRequester = remember { FocusRequester() }
+
+ val context = LocalContext.current
+
+ LaunchedEffect(Unit) {
+ // Adding a delay to ensure the animation completes before requesting focus
+ delay(250)
+ focusRequester.requestFocus()
+ }
+
Popup(
alignment = Alignment.TopCenter,
offset = IntOffset(0, 40),
@@ -100,6 +119,8 @@
Button(
modifier =
Modifier.height(56.dp)
+ .focusRequester(focusRequester)
+ .focusable(interactionSource = interactionSource)
.graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
.animateEnterExit(
enter =
@@ -142,8 +163,7 @@
) {
Icon(
imageVector = Icons.Outlined.Widgets,
- contentDescription =
- stringResource(R.string.button_to_configure_widgets_text),
+ contentDescription = null,
tint = colors.onSecondary,
modifier = Modifier.size(20.dp)
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index a1f2042..859c036 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -90,10 +90,16 @@
*/
@Composable
fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
+ val areNotificationsVisible by
+ lockscreenContentViewModel
+ .areNotificationsVisible(sceneKey)
+ .collectAsStateWithLifecycle(initialValue = false)
+ if (!areNotificationsVisible) {
+ return
+ }
+
val isShadeLayoutWide by
lockscreenContentViewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
- val areNotificationsVisible by
- lockscreenContentViewModel.areNotificationsVisible.collectAsStateWithLifecycle()
val splitShadeTopMargin: Dp =
if (Flags.centralizedStatusBarHeightFix()) {
LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
@@ -101,10 +107,6 @@
dimensionResource(id = R.dimen.large_screen_shade_header_height)
}
- if (!areNotificationsVisible) {
- return
- }
-
ConstrainedNotificationStack(
stackScrollView = stackScrollView.get(),
viewModel = viewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 12ca997..776e166 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -198,6 +198,7 @@
LaunchedEffect(scrollableState.isScrollInProgress) {
if (!scrollableState.isScrollInProgress && scrollOffset <= minScrollOffset) {
+ viewModel.setHeadsUpAnimatingAway(false)
viewModel.snoozeHun()
}
}
@@ -337,7 +338,7 @@
// expanded, reset scrim offset.
LaunchedEffect(stackHeight, scrimOffset) {
snapshotFlow { stackHeight.intValue < minVisibleScrimHeight() && scrimOffset.value < 0f }
- .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.snapTo(0f) }
+ .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.animateTo(0f, tween()) }
}
// if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index d95b388..20b1303 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -184,31 +184,33 @@
): Swipes {
val fromSource =
startedPosition?.let { position ->
- layoutImpl.swipeSourceDetector.source(
- fromScene.targetSize,
- position.round(),
- layoutImpl.density,
- orientation,
- )
+ layoutImpl.swipeSourceDetector
+ .source(
+ fromScene.targetSize,
+ position.round(),
+ layoutImpl.density,
+ orientation,
+ )
+ ?.resolve(layoutImpl.layoutDirection)
}
val upOrLeft =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Left
- Orientation.Vertical -> SwipeDirection.Up
+ Orientation.Horizontal -> SwipeDirection.Resolved.Left
+ Orientation.Vertical -> SwipeDirection.Resolved.Up
},
pointerCount = pointersDown,
fromSource = fromSource,
)
val downOrRight =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Right
- Orientation.Vertical -> SwipeDirection.Down
+ Orientation.Horizontal -> SwipeDirection.Resolved.Right
+ Orientation.Vertical -> SwipeDirection.Resolved.Down
},
pointerCount = pointersDown,
fromSource = fromSource,
@@ -833,10 +835,10 @@
/** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
private class Swipes(
- val upOrLeft: Swipe?,
- val downOrRight: Swipe?,
- val upOrLeftNoSource: Swipe?,
- val downOrRightNoSource: Swipe?,
+ val upOrLeft: Swipe.Resolved?,
+ val downOrRight: Swipe.Resolved?,
+ val upOrLeftNoSource: Swipe.Resolved?,
+ val downOrRightNoSource: Swipe.Resolved?,
) {
/** The [UserActionResult] associated to up and down swipes. */
var upOrLeftResult: UserActionResult? = null
@@ -844,7 +846,7 @@
fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> {
val userActions = fromScene.userActions
- fun result(swipe: Swipe?): UserActionResult? {
+ fun result(swipe: Swipe.Resolved?): UserActionResult? {
return userActions[swipe ?: return null]
}
@@ -940,25 +942,27 @@
when {
amount < 0f -> {
val actionUpOrLeft =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Left
- Orientation.Vertical -> SwipeDirection.Up
+ Orientation.Horizontal -> SwipeDirection.Resolved.Left
+ Orientation.Vertical -> SwipeDirection.Resolved.Up
},
pointerCount = pointersInfo().pointersDown,
+ fromSource = null,
)
fromScene.userActions[actionUpOrLeft]
}
amount > 0f -> {
val actionDownOrRight =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Right
- Orientation.Vertical -> SwipeDirection.Down
+ Orientation.Horizontal -> SwipeDirection.Resolved.Right
+ Orientation.Vertical -> SwipeDirection.Resolved.Down
},
pointerCount = pointersInfo().pointersDown,
+ fromSource = null,
)
fromScene.userActions[actionDownOrRight]
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
index b0dc3a1..97c0cef 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
@@ -21,14 +21,28 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
/** The edge of a [SceneTransitionLayout]. */
-enum class Edge : SwipeSource {
- Left,
- Right,
- Top,
- Bottom,
+enum class Edge(private val resolveEdge: (LayoutDirection) -> Resolved) : SwipeSource {
+ Top(resolveEdge = { Resolved.Top }),
+ Bottom(resolveEdge = { Resolved.Bottom }),
+ Left(resolveEdge = { Resolved.Left }),
+ Right(resolveEdge = { Resolved.Right }),
+ Start(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
+ End(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });
+
+ override fun resolve(layoutDirection: LayoutDirection): Resolved {
+ return resolveEdge(layoutDirection)
+ }
+
+ enum class Resolved : SwipeSource.Resolved {
+ Left,
+ Right,
+ Top,
+ Bottom,
+ }
}
val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 936f4ba..a49f1af 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -37,7 +37,7 @@
val key: SceneKey,
layoutImpl: SceneTransitionLayoutImpl,
content: @Composable SceneScope.() -> Unit,
- actions: Map<UserAction, UserActionResult>,
+ actions: Map<UserAction.Resolved, UserActionResult>,
zIndex: Float,
) {
internal val scope = SceneScopeImpl(layoutImpl, this)
@@ -54,8 +54,8 @@
}
private fun checkValid(
- userActions: Map<UserAction, UserActionResult>
- ): Map<UserAction, UserActionResult> {
+ userActions: Map<UserAction.Resolved, UserActionResult>
+ ): Map<UserAction.Resolved, UserActionResult> {
userActions.forEach { (action, result) ->
if (key == result.toScene) {
error(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 45758c5..0c467b1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -28,10 +28,13 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import com.android.compose.animation.scene.UserAction.Resolved
/**
* [SceneTransitionLayout] is a container that automatically animates its content whenever its state
@@ -344,34 +347,71 @@
@Stable @ElementDsl interface MovableElementContentScope : BaseSceneScope, ElementBoxScope
/** An action performed by the user. */
-sealed interface UserAction {
+sealed class UserAction {
infix fun to(scene: SceneKey): Pair<UserAction, UserActionResult> {
return this to UserActionResult(toScene = scene)
}
+
+ /** Resolve this into a [Resolved] user action given [layoutDirection]. */
+ internal abstract fun resolve(layoutDirection: LayoutDirection): Resolved
+
+ /** A resolved [UserAction] that does not depend on the layout direction. */
+ internal sealed class Resolved
}
/** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
-data object Back : UserAction
+data object Back : UserAction() {
+ override fun resolve(layoutDirection: LayoutDirection): Resolved = Resolved
+
+ internal object Resolved : UserAction.Resolved()
+}
/** The user swiped on the container. */
data class Swipe(
val direction: SwipeDirection,
val pointerCount: Int = 1,
val fromSource: SwipeSource? = null,
-) : UserAction {
+) : UserAction() {
companion object {
val Left = Swipe(SwipeDirection.Left)
val Up = Swipe(SwipeDirection.Up)
val Right = Swipe(SwipeDirection.Right)
val Down = Swipe(SwipeDirection.Down)
+ val Start = Swipe(SwipeDirection.Start)
+ val End = Swipe(SwipeDirection.End)
}
+
+ override fun resolve(layoutDirection: LayoutDirection): UserAction.Resolved {
+ return Resolved(
+ direction = direction.resolve(layoutDirection),
+ pointerCount = pointerCount,
+ fromSource = fromSource?.resolve(layoutDirection),
+ )
+ }
+
+ /** A resolved [Swipe] that does not depend on the layout direction. */
+ internal data class Resolved(
+ val direction: SwipeDirection.Resolved,
+ val pointerCount: Int,
+ val fromSource: SwipeSource.Resolved?,
+ ) : UserAction.Resolved()
}
-enum class SwipeDirection(val orientation: Orientation) {
- Up(Orientation.Vertical),
- Down(Orientation.Vertical),
- Left(Orientation.Horizontal),
- Right(Orientation.Horizontal),
+enum class SwipeDirection(internal val resolve: (LayoutDirection) -> Resolved) {
+ Up(resolve = { Resolved.Up }),
+ Down(resolve = { Resolved.Down }),
+ Left(resolve = { Resolved.Left }),
+ Right(resolve = { Resolved.Right }),
+ Start(resolve = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
+ End(resolve = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });
+
+ /** A resolved [SwipeDirection] that does not depend on the layout direction. */
+ internal enum class Resolved(val orientation: Orientation) {
+ Up(Orientation.Vertical),
+ Down(Orientation.Vertical),
+ Left(Orientation.Horizontal),
+ Right(Orientation.Horizontal),
+ }
}
/**
@@ -386,6 +426,16 @@
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
+
+ /** Resolve this into a [Resolved] swipe source given [layoutDirection]. */
+ fun resolve(layoutDirection: LayoutDirection): Resolved
+
+ /** A resolved [SwipeSource] that does not depend on the layout direction. */
+ interface Resolved {
+ override fun equals(other: Any?): Boolean
+
+ override fun hashCode(): Int
+ }
}
interface SwipeSourceDetector {
@@ -460,11 +510,13 @@
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
val density = LocalDensity.current
+ val layoutDirection = LocalLayoutDirection.current
val coroutineScope = rememberCoroutineScope()
val layoutImpl = remember {
SceneTransitionLayoutImpl(
state = state as BaseSceneTransitionLayoutState,
density = density,
+ layoutDirection = layoutDirection,
swipeSourceDetector = swipeSourceDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenes,
@@ -475,7 +527,7 @@
// TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a
// SnapshotStateMap anymore.
- layoutImpl.updateScenes(scenes)
+ layoutImpl.updateScenes(scenes, layoutDirection)
SideEffect {
if (state != layoutImpl.state) {
@@ -486,6 +538,7 @@
}
layoutImpl.density = density
+ layoutImpl.layoutDirection = layoutDirection
layoutImpl.swipeSourceDetector = swipeSourceDetector
layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 6095419..3e48c42 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -33,6 +33,7 @@
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachReversed
import com.android.compose.ui.util.lerp
@@ -45,6 +46,7 @@
internal class SceneTransitionLayoutImpl(
internal val state: BaseSceneTransitionLayoutState,
internal var density: Density,
+ internal var layoutDirection: LayoutDirection,
internal var swipeSourceDetector: SwipeSourceDetector,
internal var transitionInterceptionThreshold: Float,
builder: SceneTransitionLayoutScope.() -> Unit,
@@ -114,7 +116,7 @@
private set
init {
- updateScenes(builder)
+ updateScenes(builder, layoutDirection)
// DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the
// current scene (required for SwipeTransition).
@@ -147,7 +149,10 @@
return scenes[key] ?: error("Scene $key is not configured")
}
- internal fun updateScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
+ internal fun updateScenes(
+ builder: SceneTransitionLayoutScope.() -> Unit,
+ layoutDirection: LayoutDirection,
+ ) {
// Keep a reference of the current scenes. After processing [builder], the scenes that were
// not configured will be removed.
val scenesToRemove = scenes.keys.toMutableSet()
@@ -163,11 +168,13 @@
) {
scenesToRemove.remove(key)
+ val resolvedUserActions =
+ userActions.mapKeys { it.key.resolve(layoutDirection) }
val scene = scenes[key]
if (scene != null) {
// Update an existing scene.
scene.content = content
- scene.userActions = userActions
+ scene.userActions = resolvedUserActions
scene.zIndex = zIndex
} else {
// New scene.
@@ -176,7 +183,7 @@
key,
this@SceneTransitionLayoutImpl,
content,
- userActions,
+ resolvedUserActions,
zIndex,
)
}
@@ -213,7 +220,7 @@
@Composable
private fun BackHandler() {
val targetSceneForBack =
- scene(state.transitionState.currentScene).userActions[Back]?.toScene
+ scene(state.transitionState.currentScene).userActions[Back.Resolved]?.toScene
PredictiveBackHandler(state, coroutineScope, targetSceneForBack)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 171e243..aeb6262 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -98,7 +98,9 @@
/** Whether swipe should be enabled in the given [orientation]. */
private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
- return userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
+ return userActions.keys.any {
+ it is Swipe.Resolved && it.direction.orientation == orientation
+ }
}
private fun startDragImmediately(startedPosition: Offset): Boolean {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index aa8dc38..7daefd0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -44,26 +44,26 @@
return value
}
- return when (edge) {
- Edge.Top ->
+ return when (edge.resolve(layoutImpl.layoutDirection)) {
+ Edge.Resolved.Top ->
if (startsOutsideLayoutBounds) {
Offset(value.x, -elementSize.height.toFloat())
} else {
Offset(value.x, 0f)
}
- Edge.Left ->
+ Edge.Resolved.Left ->
if (startsOutsideLayoutBounds) {
Offset(-elementSize.width.toFloat(), value.y)
} else {
Offset(0f, value.y)
}
- Edge.Bottom ->
+ Edge.Resolved.Bottom ->
if (startsOutsideLayoutBounds) {
Offset(value.x, sceneSize.height.toFloat())
} else {
Offset(value.x, (sceneSize.height - elementSize.height).toFloat())
}
- Edge.Right ->
+ Edge.Resolved.Right ->
if (startsOutsideLayoutBounds) {
Offset(sceneSize.width.toFloat(), value.y)
} else {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index ff83d4b..7a5a84e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.NestedScrollBehavior.DuringTransitionBetweenScenes
@@ -61,8 +62,24 @@
canChangeScene = { canChangeScene(it) },
)
- val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
- val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+ var layoutDirection = LayoutDirection.Rtl
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
+ var mutableUserActionsA = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
+ var mutableUserActionsB = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
scene(
key = SceneA,
@@ -94,6 +111,7 @@
SceneTransitionLayoutImpl(
state = layoutState,
density = Density(1f),
+ layoutDirection = LayoutDirection.Ltr,
swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
@@ -466,10 +484,8 @@
dragController1.onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
- mutableUserActionsA.remove(Swipe.Up)
- mutableUserActionsA.remove(Swipe.Down)
- mutableUserActionsB.remove(Swipe.Up)
- mutableUserActionsB.remove(Swipe.Down)
+ mutableUserActionsA = emptyMap()
+ mutableUserActionsB = emptyMap()
// start accelaratedScroll and scroll over to B -> null
val dragController2 = onDragStartedImmediately()
@@ -495,7 +511,7 @@
val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
+ mutableUserActionsA += Swipe.Up to UserActionResult(SceneC)
dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
// target stays B even though UserActions changed
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
@@ -512,7 +528,7 @@
val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
+ mutableUserActionsA += Swipe.Up to UserActionResult(SceneC)
dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
dragController1.onDragStopped(velocity = down(fractionOfScreen = 0.1f))
@@ -1149,8 +1165,7 @@
overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
}
- mutableUserActionsA.clear()
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB)
+ mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB))
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f))
@@ -1178,8 +1193,7 @@
overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
}
- mutableUserActionsA.clear()
- mutableUserActionsA[Swipe.Down] = UserActionResult(SceneC)
+ mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC))
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f))
@@ -1220,7 +1234,8 @@
@Test
fun requireFullDistanceSwipe() = runGestureTest {
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB, requiresFullDistanceSwipe = true)
+ mutableUserActionsA +=
+ Swipe.Up to UserActionResult(SceneB, requiresFullDistanceSwipe = true)
val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.9f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.9f)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 25ea2ee..0766e00 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -23,11 +23,13 @@
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
@@ -37,10 +39,12 @@
import androidx.compose.ui.test.swipeWithVelocity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.subjects.assertThat
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -634,4 +638,152 @@
// Foo should be translated by (20dp, 30dp).
rule.onNode(isElement(TestElements.Foo)).assertPositionInRootIsEqualTo(20.dp, 30.dp)
}
+
+ @Test
+ fun startEnd_ltrLayout() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ transitions =
+ transitions {
+ from(SceneA, to = SceneB) {
+ // We go to B by swiping to the start (left in LTR), so we make
+ // scene B appear from the end (right) edge.
+ translate(SceneB.rootElementKey, Edge.End)
+ }
+
+ from(SceneA, to = SceneC) {
+ // We go to C by swiping to the end (right in LTR), so we make
+ // scene C appear from the start (left) edge.
+ translate(SceneC.rootElementKey, Edge.Start)
+ }
+ },
+ )
+ }
+
+ val layoutSize = 200.dp
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) }
+ scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) }
+ }
+ }
+
+ // Swipe to the left (start).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene B should come from the right (end) edge.
+ var transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+ rule
+ .onNode(isElement(SceneB.rootElementKey))
+ .assertPositionInRootIsEqualTo(layoutSize, 0.dp)
+
+ // Release to go back to A.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+ // Swipe to the right (end).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the left (start) edge.
+ transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneC)
+ rule
+ .onNode(isElement(SceneC.rootElementKey))
+ .assertPositionInRootIsEqualTo(-layoutSize, 0.dp)
+ }
+
+ @Test
+ fun startEnd_rtlLayout() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ transitions =
+ transitions {
+ from(SceneA, to = SceneB) {
+ // We go to B by swiping to the start (right in RTL), so we make
+ // scene B appear from the end (left) edge.
+ translate(SceneB.rootElementKey, Edge.End)
+ }
+
+ from(SceneA, to = SceneC) {
+ // We go to C by swiping to the end (left in RTL), so we make
+ // scene C appear from the start (right) edge.
+ translate(SceneC.rootElementKey, Edge.Start)
+ }
+ },
+ )
+ }
+
+ val layoutSize = 200.dp
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) }
+ scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) }
+ }
+ }
+ }
+
+ // Swipe to the left (end).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the right (start) edge.
+ var transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneC)
+ rule
+ .onNode(isElement(SceneC.rootElementKey))
+ .assertPositionInRootIsEqualTo(layoutSize, 0.dp)
+
+ // Release to go back to A.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+ // Swipe to the right (start).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the left (end) edge.
+ transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+ rule
+ .onNode(isElement(SceneB.rootElementKey))
+ .assertPositionInRootIsEqualTo(-layoutSize, 0.dp)
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 51c008a..9b725eb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -17,7 +17,6 @@
import android.app.UserSwitchObserver
import android.content.Context
import android.database.ContentObserver
-import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.UserHandle
import android.provider.Settings
@@ -33,6 +32,7 @@
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockProviderPlugin
import com.android.systemui.plugins.clocks.ClockSettings
@@ -341,6 +341,7 @@
}
private var isClockChanged = AtomicBoolean(false)
+
private fun triggerOnCurrentClockChanged() {
val shouldSchedule = isClockChanged.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -355,6 +356,7 @@
}
private var isClockListChanged = AtomicBoolean(false)
+
private fun triggerOnAvailableClocksChanged() {
val shouldSchedule = isClockListChanged.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -458,6 +460,7 @@
}
private var isQueued = AtomicBoolean(false)
+
fun verifyLoadedProviders() {
val shouldSchedule = isQueued.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -565,8 +568,8 @@
return availableClocks.map { (_, clock) -> clock.metadata }
}
- fun getClockThumbnail(clockId: ClockId): Drawable? =
- availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
+ fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? =
+ availableClocks[clockId]?.provider?.getClockPickerConfig(clockId)
fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 9e0af97..4802e34 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -15,13 +15,13 @@
import android.content.Context
import android.content.res.Resources
-import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockSettings
@@ -60,12 +60,17 @@
)
}
- override fun getClockThumbnail(id: ClockId): Drawable? {
+ override fun getClockPickerConfig(id: ClockId): ClockPickerConfig {
if (id != DEFAULT_CLOCK_ID) {
throw IllegalArgumentException("$id is unsupported by $TAG")
}
- // TODO(b/352049256): Update placeholder to actual resource
- return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
+ return ClockPickerConfig(
+ DEFAULT_CLOCK_ID,
+ resources.getString(R.string.clock_default_name),
+ resources.getString(R.string.clock_default_description),
+ // TODO(b/352049256): Update placeholder to actual resource
+ resources.getDrawable(R.drawable.clock_default_thumbnail, null),
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index f9f7df8..4f5d0e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
import com.android.systemui.biometrics.data.repository.fakeFacePropertyRepository
import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
@@ -36,7 +37,7 @@
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.shared.flag.fakeComposeBouncerFlags
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
@@ -75,15 +76,20 @@
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
private lateinit var underTest: BouncerMessageViewModel
+ private val ignoreHelpMessageId = 1
@Before
fun setUp() {
kosmos.fakeUserRepository.setUserInfos(listOf(PRIMARY_USER))
kosmos.fakeComposeBouncerFlags.composeBouncerEnabled = true
+ overrideResource(
+ R.array.config_face_acquire_device_entry_ignorelist,
+ intArrayOf(ignoreHelpMessageId)
+ )
underTest = kosmos.bouncerMessageViewModel
overrideResource(R.string.kg_trust_agent_disabled, "Trust agent is unavailable")
kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
"not mainline reboot"
)
}
@@ -379,7 +385,15 @@
runCurrent()
kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
- HelpFaceAuthenticationStatus(1, "some helpful message")
+ HelpFaceAuthenticationStatus(0, "some helpful message", 0)
+ )
+ runCurrent()
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(
+ 0,
+ "some helpful message",
+ FaceHelpMessageDebouncer.DEFAULT_WINDOW_MS
+ )
)
runCurrent()
assertThat(bouncerMessage?.text).isEqualTo("Enter PIN")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 0de0369..2cbfa7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -16,9 +16,11 @@
package com.android.systemui.communal
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
@@ -60,6 +62,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_COMMUNAL_HUB)
class CommunalSceneStartableTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -98,7 +101,7 @@
}
@Test
- fun keyguardGoesAway_forceBlankScene() =
+ fun keyguardGoesAway_whenLaunchingWidget_doNotForceBlankScene() =
with(kosmos) {
testScope.runTest {
val scene by collectLastValue(communalSceneInteractor.currentScene)
@@ -106,6 +109,27 @@
communalSceneInteractor.changeScene(CommunalScenes.Communal)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ testScope = this
+ )
+
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
+ fun keyguardGoesAway_whenNotLaunchingWidget_forceBlankScene() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ communalSceneInteractor.setIsLaunchingWidget(false)
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
index 0cd3fb2..d51d356 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
@@ -26,14 +26,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
-import org.mockito.kotlin.notNull
import org.mockito.kotlin.refEq
import org.mockito.kotlin.verify
@@ -41,6 +50,7 @@
@RunWith(AndroidJUnit4::class)
class SmartspaceInteractionHandlerTest : SysuiTestCase() {
private val activityStarter = mock<ActivityStarter>()
+ private val kosmos = testKosmos()
private val testIntent =
PendingIntent.getActivity(
@@ -51,29 +61,43 @@
)
private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
- private val underTest: SmartspaceInteractionHandler by lazy {
- SmartspaceInteractionHandler(activityStarter)
+ private lateinit var underTest: SmartspaceInteractionHandler
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = SmartspaceInteractionHandler(activityStarter, communalSceneInteractor)
+ }
}
@Test
fun launchAnimatorIsUsedForSmartspaceView() {
- val parent = FrameLayout(context)
- val view = SmartspaceAppWidgetHostView(context)
- parent.addView(view)
- val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ assertFalse(launching!!)
- underTest.onInteraction(view, testIntent, testResponse)
+ val parent = FrameLayout(context)
+ val view = SmartspaceAppWidgetHostView(context)
+ parent.addView(view)
+ val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
- // Verify that we pass in a non-null animation controller
- verify(activityStarter)
- .startPendingIntentWithoutDismissing(
- /* intent = */ eq(testIntent),
- /* dismissShade = */ eq(false),
- /* intentSentUiThreadCallback = */ isNull(),
- /* animationController = */ notNull(),
- /* fillInIntent = */ refEq(fillInIntent),
- /* extraOptions = */ refEq(activityOptions.toBundle()),
- )
+ underTest.onInteraction(view, testIntent, testResponse)
+
+ // Verify that we set the state correctly
+ assertTrue(launching!!)
+ // Verify that we pass in a non-null Communal animation controller
+ verify(activityStarter)
+ .startPendingIntentWithoutDismissing(
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ any<CommunalTransitionAnimatorController>(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
+ )
+ }
+ }
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
new file mode 100644
index 0000000..ac50db4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalTransitionAnimatorControllerTest : SysuiTestCase() {
+ private val controller = mock<ActivityTransitionAnimator.Controller>()
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: CommunalTransitionAnimatorController
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = CommunalTransitionAnimatorController(controller, communalSceneInteractor)
+ }
+ }
+
+ @Test
+ fun doNotAnimate_launchingWidgetStateIsCleared() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = false)
+ assertFalse(launching!!)
+ verify(controller).onIntentStarted(willAnimate = false)
+ }
+ }
+ }
+
+ @Test
+ fun animationCancelled_launchingWidgetStateIsClearedAndSceneIsNotChanged() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = true)
+ assertTrue(launching!!)
+ verify(controller).onIntentStarted(willAnimate = true)
+
+ underTest.onTransitionAnimationCancelled(newKeyguardOccludedState = true)
+ assertFalse(launching!!)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ verify(controller).onTransitionAnimationCancelled(newKeyguardOccludedState = true)
+ }
+ }
+ }
+
+ @Test
+ fun animationComplete_launchingWidgetStateIsClearedAndSceneIsChanged() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = true)
+ assertTrue(launching!!)
+ verify(controller).onIntentStarted(willAnimate = true)
+
+ underTest.onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ assertFalse(launching!!)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ verify(controller).onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
index 7044895..ea8b5ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -26,13 +26,21 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
-import org.mockito.kotlin.notNull
import org.mockito.kotlin.refEq
import org.mockito.kotlin.verify
@@ -40,6 +48,7 @@
@RunWith(AndroidJUnit4::class)
class WidgetInteractionHandlerTest : SysuiTestCase() {
private val activityStarter = mock<ActivityStarter>()
+ private val kosmos = testKosmos()
private val testIntent =
PendingIntent.getActivity(
@@ -50,30 +59,44 @@
)
private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
- private val underTest: WidgetInteractionHandler by lazy {
- WidgetInteractionHandler(activityStarter)
+ private lateinit var underTest: WidgetInteractionHandler
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = WidgetInteractionHandler(activityStarter, communalSceneInteractor)
+ }
}
@Test
fun launchAnimatorIsUsedForWidgetView() {
- val parent = FrameLayout(context)
- val view = CommunalAppWidgetHostView(context)
- parent.addView(view)
- val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ assertFalse(launching!!)
- underTest.onInteraction(view, testIntent, testResponse)
+ val parent = FrameLayout(context)
+ val view = CommunalAppWidgetHostView(context)
+ parent.addView(view)
+ val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
- // Verify that we pass in a non-null animation controller
- verify(activityStarter)
- .startPendingIntentMaybeDismissingKeyguard(
- /* intent = */ eq(testIntent),
- /* dismissShade = */ eq(false),
- /* intentSentUiThreadCallback = */ isNull(),
- /* animationController = */ notNull(),
- /* fillInIntent = */ refEq(fillInIntent),
- /* extraOptions = */ refEq(activityOptions.toBundle()),
- /* customMessage */ isNull(),
- )
+ underTest.onInteraction(view, testIntent, testResponse)
+
+ // Verify that we set the state correctly
+ assertTrue(launching!!)
+ // Verify that we pass in a non-null Communal animation controller
+ verify(activityStarter)
+ .startPendingIntentMaybeDismissingKeyguard(
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ any<CommunalTransitionAnimatorController>(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
+ /* customMessage */ isNull(),
+ )
+ }
+ }
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 2546f27..2bf50b3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -52,7 +52,6 @@
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
-import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.dump.DumpManager
@@ -79,7 +78,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.res.R
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.testKosmos
@@ -478,29 +476,6 @@
}
@Test
- fun faceHelpMessagesAreIgnoredBasedOnConfig() =
- testScope.runTest {
- overrideResource(
- R.array.config_face_acquire_device_entry_ignorelist,
- intArrayOf(10, 11)
- )
- underTest = createDeviceEntryFaceAuthRepositoryImpl()
- initCollectors()
- allPreconditionsToRunFaceAuthAreTrue()
-
- underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
- faceAuthenticateIsCalled()
-
- authenticationCallback.value.onAuthenticationHelp(9, "help msg")
- authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
- authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
-
- val response = authStatus() as HelpFaceAuthenticationStatus
- assertThat(response.msg).isEqualTo("help msg")
- assertThat(response.msgId).isEqualTo(response.msgId)
- }
-
- @Test
fun dumpDoesNotErrorOutWhenFaceManagerOrBypassControllerIsNull() =
testScope.runTest {
fakeUserRepository.setSelectedUserInfo(primaryUser)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 546510b..3253edf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -20,7 +20,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
-import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -32,27 +31,14 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.AdaptiveAuthRequest
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.BouncerLockedOut
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.PolicyLockdown
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.SecurityTimeout
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
@@ -61,7 +47,6 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -438,231 +423,6 @@
assertThat(isUnlocked).isTrue()
}
- @Test
- fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to null
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
- )
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
- DeviceNotUnlockedSinceReboot,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
- AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- BouncerLockedOut,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
- SecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
- UserLockdown,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
- NonStrongBiometricsSecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- UnattendedUpdate,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- PolicyLockdown,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- null,
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
- )
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
- DeviceNotUnlockedSinceReboot,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
- AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- BouncerLockedOut,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
- SecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
- UserLockdown,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
- NonStrongBiometricsSecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- UnattendedUpdate,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- PolicyLockdown,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- null,
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
- kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
- )
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
- DeviceNotUnlockedSinceReboot,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
- AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- BouncerLockedOut,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
- SecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
- UserLockdown,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
- NonStrongBiometricsSecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- UnattendedUpdate,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- PolicyLockdown,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
- TrustAgentDisabled,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- TrustAgentDisabled,
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
- testScope.runTest {
- val deviceEntryRestrictionReason by
- collectLastValue(underTest.deviceEntryRestrictionReason)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- DeviceEntryInteractor.REBOOT_MAINLINE_UPDATE
- )
- kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
- AuthenticationFlags(
- userId = 1,
- flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
- )
- )
- runCurrent()
-
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason).isNull()
-
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason)
- .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
-
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason)
- .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
-
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason)
- .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
- }
-
- @Test
- fun reportUserPresent_whenDeviceEntered() =
- testScope.runTest {
- val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
- assertThat(isDeviceEntered).isFalse()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(0)
-
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- runCurrent()
- switchToScene(Scenes.Gone)
- assertThat(isDeviceEntered).isTrue()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(1)
-
- switchToScene(Scenes.Lockscreen)
- assertThat(isDeviceEntered).isFalse()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(1)
-
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- switchToScene(Scenes.Gone)
- assertThat(isDeviceEntered).isTrue()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(2)
- }
-
- private fun TestScope.verifyRestrictionReasonsForAuthFlags(
- vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
- ) {
- val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
-
- authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
- kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
- AuthenticationFlags(userId = 1, flag = flag)
- )
- runCurrent()
-
- if (expectedReason == null) {
- assertThat(deviceEntryRestrictionReason).isNull()
- } else {
- assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
- }
- }
- }
-
private fun switchToScene(sceneKey: SceneKey) {
sceneInteractor.changeScene(sceneKey, "reason")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index a7a7bea3..c2acc5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -19,17 +19,20 @@
import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.trustInteractor
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -40,6 +43,7 @@
import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -54,18 +58,8 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val authenticationRepository = kosmos.fakeAuthenticationRepository
- private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
- val underTest =
- DeviceUnlockedInteractor(
- applicationScope = testScope.backgroundScope,
- authenticationInteractor = kosmos.authenticationInteractor,
- deviceEntryRepository = deviceEntryRepository,
- trustInteractor = kosmos.trustInteractor,
- faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
- fingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor,
- powerInteractor = kosmos.powerInteractor,
- )
+ val underTest = kosmos.deviceUnlockedInteractor
@Before
fun setup() {
@@ -100,6 +94,30 @@
}
@Test
+ fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsPinAndInLockdown_isFalse() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ val isInLockdown by collectLastValue(underTest.isInLockdown)
+
+ authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(
+ userId = 1,
+ flag =
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
+ )
+ )
+ runCurrent()
+ assertThat(isInLockdown).isTrue()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource).isNull()
+ }
+
+ @Test
fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsSim_isFalse() =
testScope.runTest {
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
@@ -221,6 +239,218 @@
assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
}
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_whenLockdown() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ DeviceEntryRestrictionReason.BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ DeviceEntryRestrictionReason.SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ DeviceEntryRestrictionReason.UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ DeviceEntryRestrictionReason.BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ DeviceEntryRestrictionReason.SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ DeviceEntryRestrictionReason.UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ DeviceEntryRestrictionReason.BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ DeviceEntryRestrictionReason.SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ DeviceEntryRestrictionReason.UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+ DeviceEntryRestrictionReason.TrustAgentDisabled,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ DeviceEntryRestrictionReason.TrustAgentDisabled,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
+ testScope.runTest {
+ val deviceEntryRestrictionReason by
+ collectLastValue(underTest.deviceEntryRestrictionReason)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE
+ )
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(
+ userId = 1,
+ flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ )
+ )
+ runCurrent()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason).isNull()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+ }
+
+ private fun TestScope.verifyRestrictionReasonsForAuthFlags(
+ vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
+ ) {
+ val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
+
+ authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(userId = 1, flag = flag)
+ )
+ runCurrent()
+
+ if (expectedReason == null) {
+ assertThat(deviceEntryRestrictionReason).isNull()
+ } else {
+ assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
+ }
+ }
+ }
+
companion object {
private const val primaryUserId = 1
private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 444f63a..60c9bb0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -41,6 +41,7 @@
import com.android.internal.logging.UiEventLogger
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
@@ -118,11 +119,11 @@
@Mock
lateinit var mDreamComplicationComponentFactory:
- com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
+ com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
@Mock
lateinit var mDreamComplicationComponent:
- com.android.systemui.dreams.complication.dagger.ComplicationComponent
+ com.android.systemui.dreams.complication.dagger.ComplicationComponent
@Mock lateinit var mHideComplicationTouchHandler: HideComplicationTouchHandler
@@ -202,8 +203,12 @@
whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
whenever(mLazyViewCapture.value).thenReturn(viewCaptureSpy)
mWindowParams = WindowManager.LayoutParams()
- mViewCaptureAwareWindowManager = ViewCaptureAwareWindowManager(mWindowManager,
- mLazyViewCapture, isViewCaptureEnabled = false)
+ mViewCaptureAwareWindowManager =
+ ViewCaptureAwareWindowManager(
+ mWindowManager,
+ mLazyViewCapture,
+ isViewCaptureEnabled = false
+ )
mService =
DreamOverlayService(
mContext,
@@ -257,7 +262,7 @@
mMainExecutor.runAllReady()
verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START)
verify(mUiEventLogger)
- .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START)
+ .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START)
}
@Test
@@ -634,7 +639,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT)
+ @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
@kotlin.Throws(RemoteException::class)
fun testTransitionToGlanceableHub() =
testScope.runTest {
@@ -658,7 +663,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT)
+ @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
@Throws(RemoteException::class)
fun testRedirectExit() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index 4a5342a..3a4b14b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -28,6 +28,8 @@
import com.android.systemui.shared.education.GestureType.BACK_GESTURE
import com.google.common.truth.Truth.assertThat
import java.io.File
+import java.time.Clock
+import java.time.Instant
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope
@@ -48,6 +50,7 @@
private val dsScopeProvider: Provider<CoroutineScope> = Provider {
TestScope(kosmos.testDispatcher).backgroundScope
}
+ private val clock: Clock = FakeEduClock(Instant.ofEpochMilli(1000))
private val testUserId = 1111
// For deleting any test files created after the test
@@ -59,7 +62,7 @@
// needed before calling TemporaryFolder.newFolder().
val testContext = TestContext(context, tmpFolder.newFolder())
val userRepository = UserContextualEducationRepository(testContext, dsScopeProvider)
- underTest = ContextualEducationRepository(userRepository)
+ underTest = ContextualEducationRepositoryImpl(clock, userRepository)
underTest.setUser(testUserId)
}
@@ -85,6 +88,15 @@
assertThat(model?.signalCount).isEqualTo(1)
}
+ @Test
+ fun dataAddedOnUpdateShortcutTriggerTime() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.readGestureEduModelFlow(BACK_GESTURE))
+ assertThat(model?.lastShortcutTriggeredTime).isNull()
+ underTest.updateShortcutTriggerTime(BACK_GESTURE)
+ assertThat(model?.lastShortcutTriggeredTime).isEqualTo(clock.instant())
+ }
+
/** Test context which allows overriding getFilesDir path */
private class TestContext(context: Context, private val folder: File) :
SysuiTestableContext(context) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index ec4fd79..b885800 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -123,7 +123,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, Flags.FLAG_COMMUNAL_HUB)
fun testTransitionToLockscreen_onPowerButtonPress_canDream_glanceableHubAvailable() =
testScope.runTest {
whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 50772ee..3075c54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.testKosmos
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
@@ -124,7 +126,50 @@
fun areNotificationsVisible_splitShadeTrue_true() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isTrue()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnLockscreen_true() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isTrue()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnNotificationsShade_false() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.NotificationsShade))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isFalse()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnQuickSettingsShade_true() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.QuickSettingsShade))
shadeRepository.setShadeLayoutWide(true)
fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
@@ -137,7 +182,8 @@
fun areNotificationsVisible_withSmallClock_true() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL)
assertThat(areNotificationsVisible).isTrue()
}
@@ -148,7 +194,8 @@
fun areNotificationsVisible_withLargeClock_false() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
assertThat(areNotificationsVisible).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
index 1c3021e..73a0039 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
@@ -82,6 +82,20 @@
TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
}
+ @Test
+ fun onMoveOutOfBounds_removeMovingTileFromCurrentList() {
+ val movingTileSpec = TestEditTiles[0].tileSpec
+
+ // Start the drag movement
+ underTest.onStarted(movingTileSpec)
+
+ // Move the tile outside of the list
+ underTest.movedOutOfBounds()
+
+ // Asserts the moving tile is not current
+ assertThat(listState.tiles.first { it.tileSpec == movingTileSpec }.isCurrent).isFalse()
+ }
+
companion object {
private fun createEditTile(tileSpec: String): EditTileViewModel {
return EditTileViewModel(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index 583c10f..09dca25 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -20,9 +20,6 @@
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
-import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
-import android.provider.Settings.Global.ZEN_MODE_OFF
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.data.repository.FakeZenModeRepository
@@ -65,24 +62,28 @@
@EnableFlags(Flags.FLAG_MODES_UI)
@Test
- fun dataMatchesTheRepository() = runTest {
+ fun isActivatedWhenModesChange() = runTest {
val dataList: List<ModesTileModel> by
collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false).inOrder()
- // Enable zen mode
- zenModeRepository.updateZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ // Add active mode
+ zenModeRepository.addMode(id = "One", active = true)
runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
- // Change zen mode: it's still enabled, so this shouldn't cause another emission
- zenModeRepository.updateZenMode(ZEN_MODE_NO_INTERRUPTIONS)
+ // Add another mode: state hasn't changed, so this shouldn't cause another emission
+ zenModeRepository.addMode(id = "Two", active = true)
runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
- // Disable zen mode
- zenModeRepository.updateZenMode(ZEN_MODE_OFF)
+ // Remove a mode and disable the other
+ zenModeRepository.removeMode("One")
runCurrent()
-
- assertThat(dataList.map { it.isActivated }).containsExactly(false, true, false)
+ zenModeRepository.deactivateMode("Two")
+ runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true, false).inOrder()
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index ba7ddce..f8e6337 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -633,7 +633,7 @@
@Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun headsUpAnimationsEnabled_keyguardShowing_false() =
+ fun headsUpAnimationsEnabled_keyguardShowing_true() =
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
@@ -641,6 +641,6 @@
fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
runCurrent()
- assertThat(animationsEnabled).isFalse()
+ assertThat(animationsEnabled).isTrue()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index ccd78ee..59e8ea6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -136,7 +137,9 @@
communalSceneInteractor = communalSceneInteractor,
)
`when`(userTracker.userHandle).thenReturn(UserHandle.OWNER)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(false))
`when`(communalSceneInteractor.isIdleOnCommunal).thenReturn(MutableStateFlow(false))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(false))
}
@Test
@@ -335,6 +338,102 @@
)
}
+ @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @Test
+ fun startPendingIntentDismissingKeyguard_transitionAnimator_animateCommunal() {
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ val pendingIntent = mock(PendingIntent::class.java)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(true))
+ `when`(activityIntentHelper.wouldPendingLaunchResolverActivity(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+ `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ dismissShade = false,
+ animationController = controller,
+ showOverLockscreen = true,
+ skipLockscreenChecks = false
+ )
+ mainExecutor.runAllReady()
+
+ val actionCaptor = argumentCaptor<OnDismissAction>()
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(actionCaptor.capture(), eq(null), anyBoolean(), eq(null))
+ actionCaptor.firstValue.onDismiss()
+ mainExecutor.runAllReady()
+
+ verify(activityTransitionAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(true),
+ nullable(String::class.java),
+ eq(false),
+ any(),
+ )
+ }
+
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @Test
+ fun startPendingIntentDismissingKeyguard_transitionAnimator_doNotAnimateCommunal() {
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ val pendingIntent = mock(PendingIntent::class.java)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(true))
+ `when`(activityIntentHelper.wouldPendingLaunchResolverActivity(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+ `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ dismissShade = false,
+ animationController = controller,
+ showOverLockscreen = true,
+ skipLockscreenChecks = false
+ )
+ mainExecutor.runAllReady()
+
+ val actionCaptor = argumentCaptor<OnDismissAction>()
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(actionCaptor.capture(), eq(null), anyBoolean(), eq(null))
+ actionCaptor.firstValue.onDismiss()
+ mainExecutor.runAllReady()
+
+ val runnableCaptor = argumentCaptor<Runnable>()
+ verify(statusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(runnableCaptor.capture())
+ runnableCaptor.firstValue.run()
+
+ verify(activityTransitionAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(false),
+ nullable(String::class.java),
+ eq(false),
+ any(),
+ )
+ }
+
@Test
fun startActivity_noUserHandleProvided_getUserHandle() {
val intent = mock(Intent::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index 663cf1c..d0ddbff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -22,11 +22,15 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.FakeStatusBarStateController
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -34,13 +38,17 @@
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.mockExecutorHandler
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.SystemClock
import junit.framework.Assert
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
@@ -51,6 +59,7 @@
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper
@@ -58,6 +67,9 @@
private val mHeadsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
@Mock private lateinit var mGroupManager: GroupMembershipManager
@Mock private lateinit var mVSProvider: VisualStabilityProvider
@@ -72,7 +84,7 @@
@Mock private lateinit var mUiEventLogger: UiEventLogger
- @Mock private lateinit var mJavaAdapter: JavaAdapter
+ private val mJavaAdapter: JavaAdapter = JavaAdapter(testScope.backgroundScope)
@Mock private lateinit var mShadeInteractor: ShadeInteractor
@@ -120,6 +132,11 @@
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME
mAutoDismissTime = TEST_AUTO_DISMISS_TIME
}
+
+ /** Wrapper for [BaseHeadsUpManager.shouldHeadsUpBecomePinned] for testing */
+ fun shouldHeadsUpBecomePinnedWrapper(entry: NotificationEntry): Boolean {
+ return shouldHeadsUpBecomePinned(entry)
+ }
}
private fun createHeadsUpManagerPhone(): HeadsUpManagerPhone {
@@ -219,6 +236,196 @@
Assert.assertTrue(hmp.isHeadsUpEntry(entry.key))
}
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mShadeInteractor.isAnyFullyExpanded).thenReturn(MutableStateFlow(false))
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN
+ Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeLocked_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeUnknown_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(1207)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_keyguardWithBypassOn_true() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mBypassController.bypassEnabled).thenReturn(true)
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN
+ Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_keyguardWithBypassOff_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mBypassController.bypassEnabled).thenReturn(false)
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeExpanded_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(true))
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
companion object {
@get:Parameters(name = "{0}")
val flags: List<FlagsParameterization>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
new file mode 100644
index 0000000..142631e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.audioSharingRepository
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AudioSharingInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ lateinit var underTest: AudioSharingInteractor
+
+ @Before
+ fun setUp() {
+ with(kosmos) { underTest = audioSharingInteractor }
+ }
+
+ @Test
+ fun volumeChanges_returnVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) {
+ setSecondaryGroupId(TEST_GROUP_ID)
+ setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
+ }
+ val volume by collectLastValue(underTest.volume)
+ runCurrent()
+
+ Truth.assertThat(volume).isEqualTo(TEST_VOLUME)
+ }
+ }
+ }
+
+ @Test
+ fun volumeChanges_returnNull() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) {
+ setSecondaryGroupId(TEST_GROUP_ID_INVALID)
+ setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
+ }
+ val volume by collectLastValue(underTest.volume)
+ runCurrent()
+
+ Truth.assertThat(volume).isNull()
+ }
+ }
+ }
+
+ @Test
+ fun volumeChanges_returnDefaultVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) {
+ setSecondaryGroupId(TEST_GROUP_ID)
+ setVolumeMap(emptyMap())
+ }
+ val volume by collectLastValue(underTest.volume)
+ runCurrent()
+
+ Truth.assertThat(volume).isEqualTo(TEST_VOLUME_DEFAULT)
+ }
+ }
+ }
+
+ private companion object {
+ const val TEST_GROUP_ID = 1
+ const val TEST_GROUP_ID_INVALID = -1
+ const val TEST_VOLUME = 10
+ const val TEST_VOLUME_DEFAULT = 20
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index c7998f0..4812ff0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -50,8 +50,8 @@
/** Initializes and returns the target clock design */
fun createClock(settings: ClockSettings): ClockController
- /** A static thumbnail for rendering in some examples */
- fun getClockThumbnail(id: ClockId): Drawable?
+ /** Settings configuration parameters for the clock */
+ fun getClockPickerConfig(id: ClockId): ClockPickerConfig
}
/** Interface for controlling an active clock */
@@ -133,6 +133,7 @@
// both small and large clock should have a container (RelativeLayout in
// SimpleClockFaceController)
override val views = listOf(view)
+
override fun applyConstraints(constraints: ConstraintSet): ConstraintSet {
if (views.size != 1) {
throw IllegalArgumentException(
@@ -267,6 +268,25 @@
val clockId: ClockId,
)
+data class ClockPickerConfig(
+ val id: String,
+
+ /** Localized name of the clock */
+ val name: String,
+
+ /** Localized accessibility description for the clock */
+ val description: String,
+
+ /* Static & lightweight thumbnail version of the clock */
+ val thumbnail: Drawable,
+
+ /** True if the clock will react to tone changes in the seed color */
+ val isReactiveToTone: Boolean = true,
+
+ /** True if the clock is capable of chagning style in reaction to touches */
+ val isReactiveToTouch: Boolean = false,
+)
+
/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
data class ClockConfig(
val id: String,
@@ -280,7 +300,7 @@
/** Transition to AOD should move smartspace like large clock instead of small clock */
val useAlternateSmartspaceAODTransition: Boolean = false,
- /** True if the clock will react to tone changes in the seed color. */
+ @Deprecated("TODO(b/352049256): Remove")
val isReactiveToTone: Boolean = true,
/** True if the clock is large frame clock, which will use weather in compose. */
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7f7e634..30f23bf 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -287,7 +287,8 @@
<integer name="doze_small_icon_alpha">222</integer><!-- 87% of 0xff -->
<!-- Doze: Table that translates sensor values from the doze_brightness_sensor_type sensor
- to brightness values; -1 means keeping the current brightness. -->
+ to brightness values in the integer scale [1, 255]; -1 means keeping the current
+ brightness. -->
<integer-array name="config_doze_brightness_sensor_to_brightness">
<item>-1</item> <!-- 0: OFF -->
<item>2</item> <!-- 1: NIGHT -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 52e5dea..2bd97d9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1873,7 +1873,7 @@
<string name="notification_channel_summary_low">No sound or vibration</string>
<!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
- <string name="notification_conversation_summary_low">No sound or vibration and appears lower in conversation section</string>
+ <string name="notification_conversation_summary_low">No sound or vibration but still appears in the conversation section</string>
<!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary -->
<string name="notification_channel_summary_default">May ring or vibrate based on device settings</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 090033d..5d804cc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -120,7 +120,7 @@
oneway void notifyTaskbarAutohideSuspend(boolean suspend) = 48;
/**
- * Notifies SystemUI to invoke IME Switcher.
+ * Notifies that the IME switcher button has been pressed.
*/
oneway void onImeSwitcherPressed() = 49;
@@ -167,5 +167,10 @@
*/
oneway void toggleQuickSettingsPanel() = 56;
- // Next id = 57
+ /**
+ * Notifies that the IME Switcher button has been long pressed.
+ */
+ oneway void onImeSwitcherLongPress() = 57;
+
+ // Next id = 58
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4ef1f93..484e758 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -124,8 +124,6 @@
public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
// Touchpad gestures are disabled
public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33;
- // PiP animation is running
- public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34;
// Communal hub is showing
public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35;
@@ -177,7 +175,6 @@
SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED,
- SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING,
SYSUI_STATE_COMMUNAL_HUB_SHOWING,
})
public @interface SystemUiStateFlags {}
@@ -283,9 +280,6 @@
if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) {
str.add("touchpad_gestures_disabled");
}
- if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) {
- str.add("disable_gesture_pip_animating");
- }
if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
str.add("communal_hub_showing");
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 56273eb..6e25744 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -33,6 +33,7 @@
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.assist.AssistLogger;
@@ -66,7 +67,7 @@
protected InvocationLightsView mInvocationLightsView;
protected final AssistLogger mAssistLogger;
- private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mWindowManager;
private final MetricsLogger mMetricsLogger;
private final Lazy<AssistManager> mAssistManagerLazy;
private final WindowManager.LayoutParams mLayoutParams;
@@ -80,12 +81,12 @@
@Inject
public DefaultUiController(Context context, AssistLogger assistLogger,
- WindowManager windowManager, MetricsLogger metricsLogger,
- Lazy<AssistManager> assistManagerLazy,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ MetricsLogger metricsLogger, Lazy<AssistManager> assistManagerLazy,
NavigationBarController navigationBarController) {
mAssistLogger = assistLogger;
mRoot = new FrameLayout(context);
- mWindowManager = windowManager;
+ mWindowManager = viewCaptureAwareWindowManager;
mMetricsLogger = metricsLogger;
mAssistManagerLazy = assistManagerLazy;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index a9f985f..468737d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -288,6 +288,7 @@
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
withContext(backgroundDispatcher) {
if (isSuccessful) {
+ lockPatternUtils.userPresent(selectedUserId)
lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
_hasLockoutOccurred.value = false
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt
new file mode 100644
index 0000000..1685f49
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.util.Log
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+
+/**
+ * Debounces face help messages with parameters:
+ * - window: Window of time (in milliseconds) to analyze face acquired messages)
+ * - startWindow: Window of time on start required before showing the first help message
+ * - shownFaceMessageFrequencyBoost: Frequency boost given to messages that are currently shown to
+ * the user
+ */
+class FaceHelpMessageDebouncer(
+ private val window: Long = DEFAULT_WINDOW_MS,
+ private val startWindow: Long = window,
+ private val shownFaceMessageFrequencyBoost: Int = 4,
+) {
+ private val TAG = "FaceHelpMessageDebouncer"
+ private var startTime = 0L
+ private var helpFaceAuthStatuses: MutableList<HelpFaceAuthenticationStatus> = mutableListOf()
+ private var lastMessageIdShown: Int? = null
+
+ /** Remove messages that are outside of the time [window]. */
+ private fun removeOldMessages(currTimestamp: Long) {
+ var numToRemove = 0
+ // This works under the assumption that timestamps are ordered from first to last
+ // in chronological order
+ for (index in helpFaceAuthStatuses.indices) {
+ if ((helpFaceAuthStatuses[index].createdAt + window) >= currTimestamp) {
+ break // all timestamps from here and on are within the window
+ }
+ numToRemove += 1
+ }
+
+ // Remove all outside time window
+ repeat(numToRemove) { helpFaceAuthStatuses.removeFirst() }
+
+ if (numToRemove > 0) {
+ Log.v(TAG, "removedFirst=$numToRemove")
+ }
+ }
+
+ private fun getMostFrequentHelpMessage(): HelpFaceAuthenticationStatus? {
+ // freqMap: msgId => frequency
+ val freqMap = helpFaceAuthStatuses.groupingBy { it.msgId }.eachCount().toMutableMap()
+
+ // Give shownFaceMessageFrequencyBoost to lastMessageIdShown
+ if (lastMessageIdShown != null) {
+ freqMap.computeIfPresent(lastMessageIdShown!!) { _, value ->
+ value + shownFaceMessageFrequencyBoost
+ }
+ }
+ // Go through all msgId keys & find the highest frequency msgId
+ val msgIdWithHighestFrequency =
+ freqMap.entries
+ .maxWithOrNull { (msgId1, freq1), (msgId2, freq2) ->
+ // ties are broken by more recent message
+ if (freq1 == freq2) {
+ helpFaceAuthStatuses
+ .findLast { it.msgId == msgId1 }!!
+ .createdAt
+ .compareTo(
+ helpFaceAuthStatuses.findLast { it.msgId == msgId2 }!!.createdAt
+ )
+ } else {
+ freq1.compareTo(freq2)
+ }
+ }
+ ?.key
+ return helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency }
+ }
+
+ fun addMessage(helpFaceAuthStatus: HelpFaceAuthenticationStatus) {
+ helpFaceAuthStatuses.add(helpFaceAuthStatus)
+ Log.v(TAG, "added message=$helpFaceAuthStatus")
+ }
+
+ fun getMessageToShow(atTimestamp: Long): HelpFaceAuthenticationStatus? {
+ if (helpFaceAuthStatuses.isEmpty() || (atTimestamp - startTime) < startWindow) {
+ // there's not enough time that has passed to determine whether to show anything yet
+ Log.v(TAG, "No message; haven't made initial threshold window OR no messages")
+ return null
+ }
+ removeOldMessages(atTimestamp)
+ val messageToShow = getMostFrequentHelpMessage()
+ if (lastMessageIdShown != messageToShow?.msgId) {
+ Log.v(
+ TAG,
+ "showMessage previousLastMessageId=$lastMessageIdShown" +
+ "\n\tmessageToShow=$messageToShow " +
+ "\n\thelpFaceAuthStatusesSize=${helpFaceAuthStatuses.size}" +
+ "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses"
+ )
+ lastMessageIdShown = messageToShow?.msgId
+ }
+ return messageToShow
+ }
+
+ fun startNewFaceAuthSession(faceAuthStartedTime: Long) {
+ Log.d(TAG, "startNewFaceAuthSession at startTime=$startTime")
+ startTime = faceAuthStartedTime
+ helpFaceAuthStatuses.clear()
+ lastMessageIdShown = null
+ }
+
+ companion object {
+ const val DEFAULT_WINDOW_MS = 200L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index c868d01..430887d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -94,8 +94,16 @@
val textColorError =
view.resources.getColor(R.color.biometric_dialog_error, view.context.theme)
+
+ val attributes =
+ view.context.obtainStyledAttributes(
+ R.style.TextAppearance_AuthCredential_Indicator,
+ intArrayOf(android.R.attr.textColor)
+ )
val textColorHint =
- view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
+ if (constraintBp()) attributes.getColor(0, 0)
+ else view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
+ attributes.recycle()
val logoView = view.requireViewById<ImageView>(R.id.logo)
val logoDescriptionView = view.requireViewById<TextView>(R.id.logo_description)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index 6cb9b16..810b6d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -32,7 +32,7 @@
import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.FaceFailureMessage
import com.android.systemui.deviceentry.shared.model.FaceLockoutMessage
@@ -75,7 +75,7 @@
private val clock: SystemClock,
private val biometricMessageInteractor: BiometricMessageInteractor,
private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
flags: ComposeBouncerFlags,
) {
@@ -119,7 +119,7 @@
}
} else if (authMethod.isSecure) {
combine(
- deviceEntryInteractor.deviceEntryRestrictionReason,
+ deviceUnlockedInteractor.deviceEntryRestrictionReason,
lockoutMessage,
fingerprintInteractor.isFingerprintCurrentlyAllowedOnBouncer,
resetToDefault,
@@ -413,7 +413,7 @@
clock: SystemClock,
biometricMessageInteractor: BiometricMessageInteractor,
faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- deviceEntryInteractor: DeviceEntryInteractor,
+ deviceUnlockedInteractor: DeviceUnlockedInteractor,
fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
flags: ComposeBouncerFlags,
userSwitcherViewModel: UserSwitcherViewModel,
@@ -427,7 +427,7 @@
clock = clock,
biometricMessageInteractor = biometricMessageInteractor,
faceAuthInteractor = faceAuthInteractor,
- deviceEntryInteractor = deviceEntryInteractor,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
fingerprintInteractor = fingerprintInteractor,
flags = flags,
selectedUser = userSwitcherViewModel.selectedUser,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index bde6f42..6b58c07 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -105,7 +105,13 @@
.mapLatest(::determineSceneAfterTransition)
.filterNotNull()
.onEach { (nextScene, nextTransition) ->
- communalSceneInteractor.changeScene(nextScene, nextTransition)
+ if (!communalSceneInteractor.isLaunchingWidget.value) {
+ // When launching a widget, we don't want to animate the scene change or the
+ // Communal Hub will reveal the wallpaper even though it shouldn't. Instead we
+ // snap to the new scene as part of the launch animation, once the activity
+ // launch is done, so we don't change scene here.
+ communalSceneInteractor.changeScene(nextScene, nextTransition)
+ }
}
.launchIn(applicationScope)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index e3ef6bb..748c4fa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -20,6 +20,7 @@
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
import android.content.IntentFilter
import android.content.pm.UserInfo
+import android.os.UserHandle
import android.provider.Settings
import com.android.systemui.Flags.communalHub
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -102,7 +103,10 @@
.broadcastFlow(
filter =
IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- user = user.userHandle
+ // In COPE management mode, the restriction from the managed profile may
+ // propagate to the main profile. Therefore listen to this broadcast across
+ // all users and update the state each time it changes.
+ user = UserHandle.ALL,
)
.emitOnStart()
.map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index fd540c4..122f9647 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -48,6 +48,15 @@
@Application private val applicationScope: CoroutineScope,
private val communalSceneRepository: CommunalSceneRepository,
) {
+ val _isLaunchingWidget = MutableStateFlow(false)
+
+ /** Whether a widget launch is currently in progress. */
+ val isLaunchingWidget: StateFlow<Boolean> = _isLaunchingWidget.asStateFlow()
+
+ fun setIsLaunchingWidget(launching: Boolean) {
+ _isLaunchingWidget.value = launching
+ }
+
/**
* Asks for an asynchronous scene witch to [newScene], which will use the corresponding
* installed transition or the one specified by [transitionKey], if provided.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
index a88b777..4e3d3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
@@ -22,21 +22,25 @@
import android.view.View
import android.widget.RemoteViews
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.util.InteractionHandlerDelegate
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
-/**
- * Handles interactions on smartspace elements on the hub.
- */
-class SmartspaceInteractionHandler @Inject constructor(
+/** Handles interactions on smartspace elements on the hub. */
+class SmartspaceInteractionHandler
+@Inject
+constructor(
private val activityStarter: ActivityStarter,
+ communalSceneInteractor: CommunalSceneInteractor,
) : RemoteViews.InteractionHandler {
- private val delegate = InteractionHandlerDelegate(
- findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
- intentStarter = this::startIntent,
- )
+ private val delegate =
+ InteractionHandlerDelegate(
+ communalSceneInteractor,
+ findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
+ intentStarter = this::startIntent,
+ )
override fun onInteraction(
view: View,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
index 40b182d..51a5fcd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
@@ -24,17 +24,17 @@
import androidx.core.util.component1
import androidx.core.util.component2
import com.android.systemui.animation.ActivityTransitionAnimator
-
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
/** A delegate that can be used to launch activities from [RemoteViews] */
class InteractionHandlerDelegate(
+ private val communalSceneInteractor: CommunalSceneInteractor,
private val findViewToAnimate: (View) -> Boolean,
private val intentStarter: IntentStarter,
) : RemoteViews.InteractionHandler {
- /**
- * Responsible for starting the pending intent for launching activities.
- */
+ /** Responsible for starting the pending intent for launching activities. */
fun interface IntentStarter {
fun startPendingIntent(
intent: PendingIntent,
@@ -57,7 +57,10 @@
// activities.
val hostView = getNearestParent(view)
val animationController =
- hostView?.let(ActivityTransitionAnimator.Controller::fromView)
+ hostView?.let(ActivityTransitionAnimator.Controller::fromView)?.let {
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ CommunalTransitionAnimatorController(it, communalSceneInteractor)
+ }
val (fillInIntent, activityOptions) = launchOptions
intentStarter.startPendingIntent(
pendingIntent,
@@ -66,7 +69,6 @@
animationController
)
}
-
else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
new file mode 100644
index 0000000..4efaf87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DelegateTransitionAnimatorController
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+
+/**
+ * An [ActivityTransitionAnimator.Controller] that takes care of updating the state of the Communal
+ * Hub at the right time.
+ */
+class CommunalTransitionAnimatorController(
+ delegate: ActivityTransitionAnimator.Controller,
+ private val communalSceneInteractor: CommunalSceneInteractor,
+) : DelegateTransitionAnimatorController(delegate) {
+ override fun onIntentStarted(willAnimate: Boolean) {
+ if (!willAnimate) {
+ // Other callbacks won't happen, so reset the state here.
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ }
+ delegate.onIntentStarted(willAnimate)
+ }
+
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
+ }
+
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ communalSceneInteractor.snapToScene(CommunalScenes.Blank)
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index 72f9180..519903e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -22,6 +22,7 @@
import android.view.View
import android.widget.RemoteViews
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.util.InteractionHandlerDelegate
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
@@ -32,10 +33,12 @@
@Inject
constructor(
private val activityStarter: ActivityStarter,
+ private val communalSceneInteractor: CommunalSceneInteractor
) : RemoteViews.InteractionHandler {
private val delegate =
InteractionHandlerDelegate(
+ communalSceneInteractor,
findViewToAnimate = { view -> view is CommunalAppWidgetHostView },
intentStarter = this::startIntent,
)
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index fa52dad..9460eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -57,16 +57,13 @@
import com.android.systemui.log.SessionTracker
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.google.errorprone.annotations.CompileTimeConstant
import java.io.PrintWriter
-import java.util.Arrays
import java.util.concurrent.Executor
-import java.util.stream.Collectors
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -170,7 +167,6 @@
) : DeviceEntryFaceAuthRepository, Dumpable {
private var authCancellationSignal: CancellationSignal? = null
private var detectCancellationSignal: CancellationSignal? = null
- private var faceAcquiredInfoIgnoreList: Set<Int>
private var retryCount = 0
private var pendingAuthenticateRequest = MutableStateFlow<AuthenticationRequest?>(null)
@@ -240,14 +236,6 @@
faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
faceAuthLogger.addLockoutResetCallbackDone()
}
- faceAcquiredInfoIgnoreList =
- Arrays.stream(
- context.resources.getIntArray(
- R.array.config_face_acquire_device_entry_ignorelist
- )
- )
- .boxed()
- .collect(Collectors.toSet())
dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
canRunFaceAuth =
@@ -485,10 +473,8 @@
}
override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) {
- if (faceAcquiredInfoIgnoreList.contains(code)) {
- return
- }
- _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString())
+ _authenticationStatus.value =
+ HelpFaceAuthenticationStatus(code, helpStr?.toString())
}
override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
@@ -731,7 +717,6 @@
pw.println(" _pendingAuthenticateRequest: ${pendingAuthenticateRequest.value}")
pw.println(" authCancellationSignal: $authCancellationSignal")
pw.println(" detectCancellationSignal: $detectCancellationSignal")
- pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
pw.println(" _authenticationStatus: ${_authenticationStatus.value}")
pw.println(" _detectionStatus: ${_detectionStatus.value}")
pw.println(" currentUserId: $currentUserId")
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 0f18978..e2ad774 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -39,12 +39,6 @@
* the lockscreen.
*/
val isBypassEnabled: StateFlow<Boolean>
-
- /**
- * Reports, to system server, that the user is "present" now. This is a signal that system
- * server uses to know that the device has been entered.
- */
- suspend fun reportUserPresent()
}
/** Encapsulates application state for device entry. */
@@ -84,17 +78,6 @@
SharingStarted.Eagerly,
initialValue = keyguardBypassController.bypassEnabled,
)
-
- override suspend fun reportUserPresent() {
- withContext(backgroundDispatcher) {
- val selectedUserId = userRepository.selectedUser.value.userInfo.id
- lockPatternUtils.userPresent(selectedUserId)
- }
- }
-
- companion object {
- private const val TAG = "DeviceEntryRepositoryImpl"
- }
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractor.kt
new file mode 100644
index 0000000..34b1544
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractor.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import android.content.res.Resources
+import android.hardware.biometrics.BiometricFaceConstants
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.res.R
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transform
+
+/**
+ * Process face authentication statuses.
+ * - Ignores face help messages based on R.array.config_face_acquire_device_entry_ignorelist.
+ * - Uses FaceHelpMessageDebouncer to debounce flickery help messages.
+ */
+@SysUISingleton
+class DeviceEntryFaceAuthStatusInteractor
+@Inject
+constructor(
+ repository: DeviceEntryFaceAuthRepository,
+ @Main private val resources: Resources,
+ @Application private val applicationScope: CoroutineScope,
+) {
+ private val faceHelpMessageDebouncer = FaceHelpMessageDebouncer()
+ private var faceAcquiredInfoIgnoreList: Set<Int> =
+ Arrays.stream(resources.getIntArray(R.array.config_face_acquire_device_entry_ignorelist))
+ .boxed()
+ .collect(Collectors.toSet())
+
+ val authenticationStatus: StateFlow<FaceAuthenticationStatus?> =
+ repository.authenticationStatus
+ .transform { authenticationStatus ->
+ if (authenticationStatus is AcquiredFaceAuthenticationStatus) {
+ if (
+ authenticationStatus.acquiredInfo ==
+ BiometricFaceConstants.FACE_ACQUIRED_START
+ ) {
+ faceHelpMessageDebouncer.startNewFaceAuthSession(
+ authenticationStatus.createdAt
+ )
+ }
+ }
+
+ if (authenticationStatus is HelpFaceAuthenticationStatus) {
+ if (!faceAcquiredInfoIgnoreList.contains(authenticationStatus.msgId)) {
+ faceHelpMessageDebouncer.addMessage(authenticationStatus)
+ }
+
+ val messageToShow =
+ faceHelpMessageDebouncer.getMessageToShow(
+ atTimestamp = authenticationStatus.createdAt,
+ )
+ if (messageToShow != null) {
+ emit(messageToShow)
+ }
+
+ return@transform
+ }
+
+ emit(authenticationStatus)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 425bb96..ea0e59b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,33 +16,24 @@
package com.android.systemui.deviceentry.domain.interactor
-import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
-import com.android.systemui.flags.SystemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.util.kotlin.Quad
import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -61,12 +52,7 @@
private val repository: DeviceEntryRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
- faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
- private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
- private val trustInteractor: TrustInteractor,
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
- private val systemPropertiesHelper: SystemPropertiesHelper,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
) {
/**
@@ -109,11 +95,6 @@
false
}
}
- .onEach { isDeviceEntered ->
- if (isDeviceEntered) {
- repository.reportUserPresent()
- }
- }
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
@@ -156,70 +137,6 @@
initialValue = null,
)
- private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
- private val fingerprintEnrolledAndEnabled =
- biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
- private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
-
- private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
- combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
-
- /**
- * Reason why device entry is restricted to certain authentication methods for the current user.
- *
- * Emits null when there are no device entry restrictions active.
- */
- val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
- faceOrFingerprintOrTrustEnabled.flatMapLatest {
- (faceEnabled, fingerprintEnabled, trustEnabled) ->
- if (faceEnabled || fingerprintEnabled || trustEnabled) {
- combine(
- biometricSettingsInteractor.authenticationFlags,
- faceAuthInteractor.isLockedOut,
- fingerprintAuthInteractor.isLockedOut,
- trustInteractor.isTrustAgentCurrentlyAllowed,
- ::Quad
- )
- .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) ->
- when {
- authFlags.isPrimaryAuthRequiredAfterReboot &&
- wasRebootedForMainlineUpdate ->
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
- authFlags.isPrimaryAuthRequiredAfterReboot ->
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
- authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
- DeviceEntryRestrictionReason.PolicyLockdown
- authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
- authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
- DeviceEntryRestrictionReason.UnattendedUpdate
- authFlags.isPrimaryAuthRequiredAfterTimeout ->
- DeviceEntryRestrictionReason.SecurityTimeout
- authFlags.isPrimaryAuthRequiredAfterLockout ->
- DeviceEntryRestrictionReason.BouncerLockedOut
- isFingerprintLockedOut ->
- DeviceEntryRestrictionReason.StrongBiometricsLockedOut
- isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
- DeviceEntryRestrictionReason.StrongBiometricsLockedOut
- isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
- authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
- DeviceEntryRestrictionReason.AdaptiveAuthRequest
- (trustEnabled && !trustManaged) &&
- (authFlags.someAuthRequiredAfterTrustAgentExpired ||
- authFlags.someAuthRequiredAfterUserRequest) ->
- DeviceEntryRestrictionReason.TrustAgentDisabled
- authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
- DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
- else -> null
- }
- }
- } else {
- flowOf(null)
- }
- }
-
- /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
- val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }
-
/**
* Attempt to enter the device and dismiss the lockscreen. If authentication is required to
* unlock the device it will transition to bouncer.
@@ -268,27 +185,6 @@
return repository.isLockscreenEnabled()
}
- fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
- return when (this) {
- DeviceEntryRestrictionReason.UserLockdown -> true
- DeviceEntryRestrictionReason.PolicyLockdown -> true
-
- // Add individual enum value instead of using "else" so new reasons are guaranteed
- // to be added here at compile-time.
- null -> false
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false
- DeviceEntryRestrictionReason.BouncerLockedOut -> false
- DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false
- DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false
- DeviceEntryRestrictionReason.TrustAgentDisabled -> false
- DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false
- DeviceEntryRestrictionReason.SecurityTimeout -> false
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false
- DeviceEntryRestrictionReason.UnattendedUpdate -> false
- DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false
- }
- }
-
/**
* Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
* dismissed once the authentication challenge is completed. For example, completing a biometric
@@ -296,12 +192,4 @@
* lockscreen.
*/
val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
-
- private val wasRebootedForMainlineUpdate
- get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
-
- companion object {
- @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
- @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index 5141690..e17e530 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -16,20 +16,26 @@
package com.android.systemui.deviceentry.domain.interactor
+import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
+import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -49,6 +55,8 @@
faceAuthInteractor: DeviceEntryFaceAuthInteractor,
fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val powerInteractor: PowerInteractor,
+ private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ private val systemPropertiesHelper: SystemPropertiesHelper,
) {
private val deviceUnlockSource =
@@ -69,6 +77,75 @@
.map { DeviceUnlockSource.BouncerInput }
)
+ private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
+ private val fingerprintEnrolledAndEnabled =
+ biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
+ private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
+
+ private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
+ combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
+
+ /**
+ * Reason why device entry is restricted to certain authentication methods for the current user.
+ *
+ * Emits null when there are no device entry restrictions active.
+ */
+ val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
+ faceOrFingerprintOrTrustEnabled.flatMapLatest {
+ (faceEnabled, fingerprintEnabled, trustEnabled) ->
+ if (faceEnabled || fingerprintEnabled || trustEnabled) {
+ combine(
+ biometricSettingsInteractor.authenticationFlags,
+ faceAuthInteractor.isLockedOut,
+ fingerprintAuthInteractor.isLockedOut,
+ trustInteractor.isTrustAgentCurrentlyAllowed,
+ ) { authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged ->
+ when {
+ authFlags.isPrimaryAuthRequiredAfterReboot &&
+ wasRebootedForMainlineUpdate() ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
+ authFlags.isPrimaryAuthRequiredAfterReboot ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+ authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+ DeviceEntryRestrictionReason.PolicyLockdown
+ authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+ authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
+ DeviceEntryRestrictionReason.UnattendedUpdate
+ authFlags.isPrimaryAuthRequiredAfterTimeout ->
+ DeviceEntryRestrictionReason.SecurityTimeout
+ authFlags.isPrimaryAuthRequiredAfterLockout ->
+ DeviceEntryRestrictionReason.BouncerLockedOut
+ isFingerprintLockedOut ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
+ authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest
+ (trustEnabled && !trustManaged) &&
+ (authFlags.someAuthRequiredAfterTrustAgentExpired ||
+ authFlags.someAuthRequiredAfterUserRequest) ->
+ DeviceEntryRestrictionReason.TrustAgentDisabled
+ authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+ else -> null
+ }
+ }
+ } else {
+ biometricSettingsInteractor.authenticationFlags.map { authFlags ->
+ when {
+ authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+ authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+ DeviceEntryRestrictionReason.PolicyLockdown
+ else -> null
+ }
+ }
+ }
+ }
+
+ /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
+ val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }
+
/**
* Whether the device is unlocked or not, along with the information about the authentication
* method that was used to unlock the device.
@@ -90,13 +167,18 @@
// Device is locked if SIM is locked.
flowOf(DeviceUnlockStatus(false, null))
} else {
- powerInteractor.isAsleep.flatMapLatest { isAsleep ->
- if (isAsleep) {
- flowOf(DeviceUnlockStatus(false, null))
- } else {
- deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+ combine(
+ powerInteractor.isAsleep,
+ isInLockdown,
+ ::Pair,
+ )
+ .flatMapLatestConflated { (isAsleep, isInLockdown) ->
+ if (isAsleep || isInLockdown) {
+ flowOf(DeviceUnlockStatus(false, null))
+ } else {
+ deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+ }
}
- }
}
}
.stateIn(
@@ -104,4 +186,34 @@
started = SharingStarted.Eagerly,
initialValue = DeviceUnlockStatus(false, null),
)
+
+ private fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
+ return when (this) {
+ DeviceEntryRestrictionReason.UserLockdown -> true
+ DeviceEntryRestrictionReason.PolicyLockdown -> true
+
+ // Add individual enum value instead of using "else" so new reasons are guaranteed
+ // to be added here at compile-time.
+ null -> false
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false
+ DeviceEntryRestrictionReason.BouncerLockedOut -> false
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false
+ DeviceEntryRestrictionReason.TrustAgentDisabled -> false
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false
+ DeviceEntryRestrictionReason.SecurityTimeout -> false
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false
+ DeviceEntryRestrictionReason.UnattendedUpdate -> false
+ DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false
+ }
+ }
+
+ private fun wasRebootedForMainlineUpdate(): Boolean {
+ return systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
+ }
+
+ companion object {
+ @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
+ @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index d12ea45..c536d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -90,6 +90,7 @@
private val powerInteractor: PowerInteractor,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val trustManager: TrustManager,
+ deviceEntryFaceAuthStatusInteractor: DeviceEntryFaceAuthStatusInteractor,
) : DeviceEntryFaceAuthInteractor {
private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -276,9 +277,13 @@
}
private val faceAuthenticationStatusOverride = MutableStateFlow<FaceAuthenticationStatus?>(null)
+
/** Provide the status of face authentication */
override val authenticationStatus =
- merge(faceAuthenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
+ merge(
+ faceAuthenticationStatusOverride.filterNotNull(),
+ deviceEntryFaceAuthStatusInteractor.authenticationStatus.filterNotNull(),
+ )
/** Provide the status of face detection */
override val detectionStatus = repository.detectionStatus
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 7ae8409..e07b5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -48,6 +48,7 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.plugins.SensorManagerPlugin;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -426,7 +427,11 @@
}
if (!anyListening) {
- mSecureSettings.unregisterContentObserverSync(mSettingsObserver);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mSettingsObserver);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mSettingsObserver);
+ }
} else if (!mSettingRegistered) {
for (TriggerSensor s : mTriggerSensors) {
s.registerSettingsObserver(mSettingsObserver);
@@ -750,8 +755,13 @@
public void registerSettingsObserver(ContentObserver settingsObserver) {
if (mConfigured && !TextUtils.isEmpty(mSetting)) {
- mSecureSettings.registerContentObserverForUserSync(
- mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index e2bcb6b..53b9261 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -17,8 +17,12 @@
package com.android.systemui.education.dagger
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.repository.ContextualEducationRepository
+import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
+import dagger.Binds
import dagger.Module
import dagger.Provides
+import java.time.Clock
import javax.inject.Qualifier
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -26,8 +30,15 @@
@Module
interface ContextualEducationModule {
+ @Binds
+ fun bindContextualEducationRepository(
+ impl: ContextualEducationRepositoryImpl
+ ): ContextualEducationRepository
+
@Qualifier annotation class EduDataStoreScope
+ @Qualifier annotation class EduClock
+
companion object {
@EduDataStoreScope
@Provides
@@ -36,5 +47,11 @@
): CoroutineScope {
return CoroutineScope(bgDispatcher + SupervisorJob())
}
+
+ @EduClock
+ @Provides
+ fun provideEduClock(): Clock {
+ return Clock.systemUTC()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
index af35e8c..9f6cb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -16,11 +16,14 @@
package com.android.systemui.education.data.model
+import java.time.Instant
+
/**
* Model to store education data related to each gesture (e.g. Back, Home, All Apps, Overview). Each
* gesture stores its own model separately.
*/
data class GestureEduModel(
- val signalCount: Int,
- val educationShownCount: Int,
+ val signalCount: Int = 0,
+ val educationShownCount: Int = 0,
+ val lastShortcutTriggeredTime: Instant? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
index c9dd833..248b7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
@@ -17,26 +17,50 @@
package com.android.systemui.education.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
+import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.shared.education.GestureType
+import java.time.Clock
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates the functions of ContextualEducationRepository. */
+interface ContextualEducationRepository {
+ fun setUser(userId: Int)
+
+ fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
+
+ suspend fun incrementSignalCount(gestureType: GestureType)
+
+ suspend fun updateShortcutTriggerTime(gestureType: GestureType)
+}
/**
* Provide methods to read and update on field level and allow setting datastore when user is
* changed
*/
@SysUISingleton
-class ContextualEducationRepository
+class ContextualEducationRepositoryImpl
@Inject
-constructor(private val userEduRepository: UserContextualEducationRepository) {
+constructor(
+ @EduClock private val clock: Clock,
+ private val userEduRepository: UserContextualEducationRepository
+) : ContextualEducationRepository {
/** To change data store when user is changed */
- fun setUser(userId: Int) = userEduRepository.setUser(userId)
+ override fun setUser(userId: Int) = userEduRepository.setUser(userId)
- fun readGestureEduModelFlow(gestureType: GestureType) =
+ override fun readGestureEduModelFlow(gestureType: GestureType) =
userEduRepository.readGestureEduModelFlow(gestureType)
- suspend fun incrementSignalCount(gestureType: GestureType) {
+ override suspend fun incrementSignalCount(gestureType: GestureType) {
userEduRepository.updateGestureEduModel(gestureType) {
it.copy(signalCount = it.signalCount + 1)
}
}
+
+ override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
+ userEduRepository.updateGestureEduModel(gestureType) {
+ it.copy(lastShortcutTriggeredTime = clock.instant())
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 229511a..b7fc773 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -18,16 +18,19 @@
import android.content.Context
import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.MutablePreferences
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.preferencesDataStoreFile
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.shared.education.GestureType
+import java.time.Instant
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
@@ -55,6 +58,7 @@
companion object {
const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT"
const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN"
+ const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
}
@@ -91,6 +95,10 @@
return GestureEduModel(
signalCount = preferences[getSignalCountKey(gestureType)] ?: 0,
educationShownCount = preferences[getEducationShownCountKey(gestureType)] ?: 0,
+ lastShortcutTriggeredTime =
+ preferences[getLastShortcutTriggeredTimeKey(gestureType)]?.let {
+ Instant.ofEpochMilli(it)
+ },
)
}
@@ -103,6 +111,11 @@
val updatedModel = transform(currentModel)
preferences[getSignalCountKey(gestureType)] = updatedModel.signalCount
preferences[getEducationShownCountKey(gestureType)] = updatedModel.educationShownCount
+ updateTimeByInstant(
+ preferences,
+ updatedModel.lastShortcutTriggeredTime,
+ getLastShortcutTriggeredTimeKey(gestureType)
+ )
}
}
@@ -111,4 +124,19 @@
private fun getEducationShownCountKey(gestureType: GestureType): Preferences.Key<Int> =
intPreferencesKey(gestureType.name + NUMBER_OF_EDU_SHOWN_SUFFIX)
+
+ private fun getLastShortcutTriggeredTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+ longPreferencesKey(gestureType.name + LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX)
+
+ private fun updateTimeByInstant(
+ preferences: MutablePreferences,
+ instant: Instant?,
+ key: Preferences.Key<Long>
+ ) {
+ if (instant != null) {
+ preferences[key] = instant.toEpochMilli()
+ } else {
+ preferences.remove(key)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 1e4fb4f..493afde 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -64,7 +64,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.dreams.IDreamManager;
import android.sysprop.TelephonyProperties;
import android.telecom.TelecomManager;
import android.telephony.ServiceState;
@@ -197,7 +196,6 @@
private final Context mContext;
private final GlobalActionsManager mWindowManagerFuncs;
private final AudioManager mAudioManager;
- private final IDreamManager mDreamManager;
private final DevicePolicyManager mDevicePolicyManager;
private final LockPatternUtils mLockPatternUtils;
private final SelectedUserInteractor mSelectedUserInteractor;
@@ -345,7 +343,6 @@
Context context,
GlobalActionsManager windowManagerFuncs,
AudioManager audioManager,
- IDreamManager iDreamManager,
DevicePolicyManager devicePolicyManager,
LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
@@ -382,7 +379,6 @@
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
- mDreamManager = iDreamManager;
mDevicePolicyManager = devicePolicyManager;
mLockPatternUtils = lockPatternUtils;
mTelephonyListenerManager = telephonyListenerManager;
@@ -510,20 +506,7 @@
mHandler.sendEmptyMessage(MESSAGE_DISMISS);
}
- protected void awakenIfNecessary() {
- if (mDreamManager != null) {
- try {
- if (mDreamManager.isDreaming()) {
- mDreamManager.awaken();
- }
- } catch (RemoteException e) {
- // we tried
- }
- }
- }
-
protected void handleShow(@Nullable Expandable expandable) {
- awakenIfNecessary();
mDialog = createDialog();
prepareDialog();
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 9e9368d..b482862 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -120,6 +120,7 @@
ShortcutHelperSinglePane(
modifier,
shortcutsUiState.shortcutCategories,
+ shortcutsUiState.defaultSelectedCategory,
onKeyboardSettingsClicked
)
} else {
@@ -146,6 +147,7 @@
private fun ShortcutHelperSinglePane(
modifier: Modifier = Modifier,
categories: List<ShortcutCategory>,
+ defaultSelectedCategory: ShortcutCategoryType,
onKeyboardSettingsClicked: () -> Unit,
) {
Column(
@@ -159,7 +161,7 @@
Spacer(modifier = Modifier.height(6.dp))
ShortcutsSearchBar()
Spacer(modifier = Modifier.height(16.dp))
- CategoriesPanelSinglePane(categories)
+ CategoriesPanelSinglePane(categories, defaultSelectedCategory)
Spacer(modifier = Modifier.weight(1f))
KeyboardSettings(onClick = onKeyboardSettingsClicked)
}
@@ -168,8 +170,10 @@
@Composable
private fun CategoriesPanelSinglePane(
categories: List<ShortcutCategory>,
+ defaultSelectedCategory: ShortcutCategoryType,
) {
- var expandedCategory by remember { mutableStateOf<ShortcutCategory?>(null) }
+ val selectedCategory = categories.firstOrNull { it.type == defaultSelectedCategory }
+ var expandedCategory by remember { mutableStateOf(selectedCategory) }
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
categories.fastForEachIndexed { index, category ->
val isExpanded = expandedCategory == category
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index ad258f4..25574ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -19,6 +19,9 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -52,7 +55,7 @@
} else {
ShortcutsUiState.Active(
shortcutCategories = it,
- defaultSelectedCategory = it.first().type,
+ defaultSelectedCategory = getDefaultSelectedCategory(it),
)
}
}
@@ -62,6 +65,13 @@
initialValue = ShortcutsUiState.Inactive
)
+ private fun getDefaultSelectedCategory(
+ categories: List<ShortcutCategory>
+ ): ShortcutCategoryType {
+ val currentAppShortcuts = categories.firstOrNull { it.type is CurrentApp }
+ return currentAppShortcuts?.type ?: categories.first().type
+ }
+
fun onViewClosed() {
stateInteractor.onViewClosed()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2f872b6..e46a7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -78,7 +78,6 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
@@ -151,6 +150,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
@@ -360,6 +360,7 @@
private final SecureSettings mSecureSettings;
private final SystemSettings mSystemSettings;
private final SystemClock mSystemClock;
+ private final ProcessWrapper mProcessWrapper;
private final SystemPropertiesHelper mSystemPropertiesHelper;
/**
@@ -1459,10 +1460,12 @@
Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
Lazy<ScrimController> scrimControllerLazy,
IActivityTaskManager activityTaskManagerService,
+ IStatusBarService statusBarService,
FeatureFlags featureFlags,
SecureSettings secureSettings,
SystemSettings systemSettings,
SystemClock systemClock,
+ ProcessWrapper processWrapper,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamViewModel> dreamViewModel,
Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
@@ -1487,9 +1490,9 @@
mSecureSettings = secureSettings;
mSystemSettings = systemSettings;
mSystemClock = systemClock;
+ mProcessWrapper = processWrapper;
mSystemPropertiesHelper = systemPropertiesHelper;
- mStatusBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mStatusBarService = statusBarService;
mKeyguardDisplayManager = keyguardDisplayManager;
mShadeController = shadeControllerLazy;
dumpManager.registerDumpable(this);
@@ -3511,12 +3514,20 @@
// TODO (b/155663717) After restart, status bar will not properly hide home button
// unless disable is called to show un-hide it once first
if (forceClearFlags) {
- try {
- mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId(true));
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to force clear flags", e);
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && !mProcessWrapper.isSystemUser() && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ } else {
+ try {
+ mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId(true));
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to force clear flags", e);
+ }
}
}
@@ -3540,6 +3551,14 @@
}
if (!SceneContainerFlag.isEnabled()) {
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && !mProcessWrapper.isSystemUser() && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
try {
mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
mContext.getPackageName(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 15dac09..a43bfd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -23,6 +23,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -64,6 +65,7 @@
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -158,10 +160,12 @@
Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
Lazy<ScrimController> scrimControllerLazy,
IActivityTaskManager activityTaskManagerService,
+ IStatusBarService statusBarService,
FeatureFlags featureFlags,
SecureSettings secureSettings,
SystemSettings systemSettings,
SystemClock systemClock,
+ ProcessWrapper processWrapper,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamViewModel> dreamViewModel,
Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
@@ -206,10 +210,12 @@
activityTransitionAnimator,
scrimControllerLazy,
activityTaskManagerService,
+ statusBarService,
featureFlags,
secureSettings,
systemSettings,
systemClock,
+ processWrapper,
mainDispatcher,
dreamViewModel,
communalTransitionViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index bbc3d76..89c7178 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -168,8 +168,7 @@
*/
@Deprecated("Will be merged into maybeStartTransitionToOccludedOrInsecureCamera")
suspend fun maybeHandleInsecurePowerGesture(): Boolean {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
- if (SceneContainerFlag.isEnabled) return true
+ if (SceneContainerFlag.isEnabled) return false
if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
if (keyguardInteractor.isKeyguardDismissible.value) {
startTransitionTo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
index 853f176..1fd609d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -26,6 +26,7 @@
import android.view.WindowManager
import android.widget.ProgressBar
import androidx.core.view.isGone
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import javax.inject.Inject
@@ -37,7 +38,7 @@
@Inject
constructor(
private val layoutInflater: LayoutInflater,
- private val windowManager: WindowManager,
+ private val windowManager: ViewCaptureAwareWindowManager,
) {
private var overlayView: View? = null
@@ -90,7 +91,7 @@
) {
if (overlayView == null) {
overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
- windowManager.addView(overlayView, overlayViewParams)
+ windowManager.addView(requireNotNull(overlayView), overlayViewParams)
progressBar?.pivotX = 0.0f
progressBar?.pivotY = 0.0f
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
index 754ed6c..1ee0368 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
@@ -25,6 +25,7 @@
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@@ -45,6 +46,13 @@
edge = Edge.create(from = DREAMING, to = AOD),
)
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 300.milliseconds,
+ onStep = { it },
+ )
+
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 7c46807..350ceb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -91,8 +91,9 @@
private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
- private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ private val dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel,
private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel,
+ private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
@@ -243,6 +244,7 @@
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ dreamingToAodTransitionViewModel.lockscreenAlpha,
dreamingToGoneTransitionViewModel.lockscreenAlpha,
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 3b337fc..4bfefda 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.res.Resources
+import com.android.compose.animation.scene.SceneKey
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
@@ -26,14 +27,17 @@
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -57,19 +61,6 @@
val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide
- val areNotificationsVisible: StateFlow<Boolean> =
- combine(
- clockSize,
- shadeInteractor.isShadeLayoutWide,
- ) { clockSize, isShadeLayoutWide ->
- clockSize == ClockSize.SMALL || isShadeLayoutWide
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
-
/** Amount of horizontal translation that should be applied to elements in the scene. */
val unfoldTranslations: StateFlow<UnfoldTranslations> =
combine(
@@ -97,6 +88,25 @@
initialValue = true,
)
+ /**
+ * Returns a flow that indicates whether lockscreen notifications should be rendered in the
+ * given [sceneKey].
+ */
+ fun areNotificationsVisible(sceneKey: SceneKey): Flow<Boolean> {
+ // `Scenes.NotificationsShade` renders its own separate notifications stack, so when it's
+ // open we avoid rendering the lockscreen notifications stack.
+ if (sceneKey == Scenes.NotificationsShade) {
+ return flowOf(false)
+ }
+
+ return combine(
+ clockSize,
+ shadeInteractor.isShadeLayoutWide,
+ ) { clockSize, isShadeLayoutWide ->
+ clockSize == ClockSize.SMALL || isShadeLayoutWide
+ }
+ }
+
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (clockSize.value == ClockSize.LARGE) {
resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index d848b43..e8ded03 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -36,6 +37,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
@@ -63,6 +65,7 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardStateController mKeyguardStateController;
private final UiEventLogger mUiEventLogger;
+ private final ProcessWrapper mProcessWrapper;
private final Map<Integer, InstanceId> mSessionToInstanceId = new HashMap<>();
private boolean mKeyguardSessionStarted;
@@ -73,13 +76,15 @@
AuthController authController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardStateController keyguardStateController,
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ ProcessWrapper processWrapper
) {
mStatusBarManagerService = statusBarService;
mAuthController = authController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardStateController = keyguardStateController;
mUiEventLogger = uiEventLogger;
+ mProcessWrapper = processWrapper;
}
@Override
@@ -109,6 +114,16 @@
final InstanceId instanceId = mInstanceIdGenerator.newInstanceId();
mSessionToInstanceId.put(type, instanceId);
+
+ if (UserManager.isVisibleBackgroundUsersEnabled() && !mProcessWrapper.isSystemUser()
+ && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
+
try {
if (DEBUG) {
Log.d(TAG, "Session start for [" + getString(type) + "] id=" + instanceId);
@@ -139,6 +154,14 @@
if (endSessionUiEvent != null) {
mUiEventLogger.log(endSessionUiEvent, instanceId);
}
+ if (UserManager.isVisibleBackgroundUsersEnabled() && !mProcessWrapper.isSystemUser()
+ && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
mStatusBarManagerService.onSessionEnded(type, instanceId);
} catch (RemoteException e) {
Log.e(TAG, "Unable to send onSessionEnded for session="
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index bda0069..e7c2a45 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -17,7 +17,6 @@
package com.android.systemui.media;
import android.annotation.Nullable;
-import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -124,13 +123,9 @@
boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
throws RemoteException {
if (LOGD) {
- Log.d(TAG, "play(token=" + token + ", uri=" + uri
- + ", uid=" + Binder.getCallingUid()
- + ") uriUserId=" + ContentProvider.getUserIdFromUri(uri)
- + " callingUserId=" + Binder.getCallingUserHandle().getIdentifier());
+ Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
+ + Binder.getCallingUid() + ")");
}
- enforceUriUserId(uri);
-
Client client;
synchronized (mClients) {
client = mClients.get(token);
@@ -212,7 +207,6 @@
@Override
public String getTitle(Uri uri) {
- enforceUriUserId(uri);
final UserHandle user = Binder.getCallingUserHandle();
return Ringtone.getTitle(getContextForUser(user), uri,
false /*followSettingsUri*/, false /*allowRemote*/);
@@ -245,25 +239,6 @@
}
throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri);
}
-
- /**
- * Must be called from the Binder calling thread.
- * Ensures caller is from the same userId as the content they're trying to access.
- * @param uri the URI to check
- * @throws SecurityException when non-system call or userId in uri differs from the
- * caller's userId
- */
- private void enforceUriUserId(Uri uri) throws SecurityException {
- final int uriUserId = ContentProvider.getUserIdFromUri(uri);
- final int callerUserId = Binder.getCallingUserHandle().getIdentifier();
- // for a non-system call, verify the URI to play belongs to the same user as the caller
- if (UserHandle.isApp(Binder.getCallingUid()) && uriUserId != callerUserId) {
- throw new SecurityException("Illegal access to uri=" + uri
- + " content associated with user=" + uriUserId
- + ", request originates from user=" + callerUserId);
- }
- }
-
};
private Context getContextForUser(UserHandle user) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionLog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionLog.kt
new file mode 100644
index 0000000..a80bc09
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection
+
+import javax.inject.Qualifier
+
+/** Logs for media projection related events. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class MediaProjectionLog
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt
index 3489459..7fd77a9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt
@@ -16,12 +16,25 @@
package com.android.systemui.mediaprojection
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.mediaprojection.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module
interface MediaProjectionModule {
@Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @MediaProjectionLog
+ fun provideMediaProjectionLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("MediaProjection", 50)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
index 760ff7d..5704e80 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
@@ -21,7 +21,6 @@
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
import android.os.Handler
-import android.util.Log
import android.view.ContentRecordingSession
import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -29,6 +28,9 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.mediaprojection.MediaProjectionLog
import com.android.systemui.mediaprojection.MediaProjectionServiceHelper
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
@@ -56,20 +58,29 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val tasksRepository: TasksRepository,
private val mediaProjectionServiceHelper: MediaProjectionServiceHelper,
+ @MediaProjectionLog private val logger: LogBuffer,
) : MediaProjectionRepository {
override suspend fun switchProjectedTask(task: RunningTaskInfo) {
withContext(backgroundDispatcher) {
if (mediaProjectionServiceHelper.updateTaskRecordingSession(task.token)) {
- Log.d(TAG, "Successfully switched projected task")
+ logger.log(TAG, LogLevel.DEBUG, {}, { "Successfully switched projected task" })
} else {
- Log.d(TAG, "Failed to switch projected task")
+ logger.log(TAG, LogLevel.WARNING, {}, { "Failed to switch projected task" })
}
}
}
override suspend fun stopProjecting() {
- withContext(backgroundDispatcher) { mediaProjectionManager.stopActiveProjection() }
+ withContext(backgroundDispatcher) {
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {},
+ { "Requesting MediaProjectionManager#stopActiveProjection" },
+ )
+ mediaProjectionManager.stopActiveProjection()
+ }
}
override val mediaProjectionState: Flow<MediaProjectionState> =
@@ -77,12 +88,22 @@
val callback =
object : MediaProjectionManager.Callback() {
override fun onStart(info: MediaProjectionInfo?) {
- Log.d(TAG, "MediaProjectionManager.Callback#onStart")
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {},
+ { "MediaProjectionManager.Callback#onStart" },
+ )
trySendWithFailureLogging(CallbackEvent.OnStart, TAG)
}
override fun onStop(info: MediaProjectionInfo?) {
- Log.d(TAG, "MediaProjectionManager.Callback#onStop")
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {},
+ { "MediaProjectionManager.Callback#onStop" },
+ )
trySendWithFailureLogging(CallbackEvent.OnStop, TAG)
}
@@ -90,7 +111,12 @@
info: MediaProjectionInfo,
session: ContentRecordingSession?
) {
- Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session")
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = session.toString() },
+ { "MediaProjectionManager.Callback#onSessionStarted: $str1" },
+ )
trySendWithFailureLogging(
CallbackEvent.OnRecordingSessionSet(info, session),
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterLog.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterLog.kt
new file mode 100644
index 0000000..16bf0ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediarouter
+
+import javax.inject.Qualifier
+
+/** Logs for events related to MediaRouter APIs. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class MediaRouterLog
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
index c07e3a0..df5dae4 100644
--- a/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
@@ -16,12 +16,25 @@
package com.android.systemui.mediarouter
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.mediarouter.data.repository.MediaRouterRepository
import com.android.systemui.mediarouter.data.repository.MediaRouterRepositoryImpl
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module
interface MediaRouterModule {
@Binds fun mediaRouterRepository(impl: MediaRouterRepositoryImpl): MediaRouterRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @MediaRouterLog
+ fun provideMediaRouterLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("MediaRouter", 50)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
index 998d76c..debb667 100644
--- a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
@@ -18,6 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.mediarouter.MediaRouterLog
import com.android.systemui.statusbar.policy.CastController
import com.android.systemui.statusbar.policy.CastDevice
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -26,6 +29,9 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** A repository for data coming from MediaRouter APIs. */
@@ -43,23 +49,29 @@
constructor(
@Application private val scope: CoroutineScope,
private val castController: CastController,
+ @MediaRouterLog private val logger: LogBuffer,
) : MediaRouterRepository {
override val castDevices: StateFlow<List<CastDevice>> =
conflatedCallbackFlow {
- val callback =
- CastController.Callback {
- val mediaRouterCastDevices =
- castController.castDevices.filter {
- it.origin == CastDevice.CastOrigin.MediaRouter
- }
- trySend(mediaRouterCastDevices)
- }
+ val callback = CastController.Callback { trySend(castController.castDevices) }
castController.addCallback(callback)
awaitClose { castController.removeCallback(callback) }
}
+ // The CastController.Callback is pretty noisy and sends the same values multiple times
+ // in a row, so use a distinctUntilChanged before logging.
+ .distinctUntilChanged()
+ .onEach { allDevices ->
+ val logString = allDevices.map { it.shortLogString }.toString()
+ logger.log(TAG, LogLevel.INFO, { str1 = logString }, { "All cast devices: $str1" })
+ }
+ .map { it.filter { device -> device.origin == CastDevice.CastOrigin.MediaRouter } }
.stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
override fun stopCasting(device: CastDevice) {
castController.stopCasting(device)
}
+
+ companion object {
+ private const val TAG = "MediaRouterRepo"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 18358a7..d8c13b6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -33,6 +33,7 @@
import androidx.core.os.postDelayed
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.util.LatencyTracker
@@ -83,7 +84,7 @@
class BackPanelController
internal constructor(
context: Context,
- private val windowManager: WindowManager,
+ private val windowManager: ViewCaptureAwareWindowManager,
private val viewConfiguration: ViewConfiguration,
private val mainHandler: Handler,
private val systemClock: SystemClock,
@@ -102,7 +103,7 @@
class Factory
@Inject
constructor(
- private val windowManager: WindowManager,
+ private val windowManager: ViewCaptureAwareWindowManager,
private val viewConfiguration: ViewConfiguration,
@BackPanelUiThread private val uiThreadContext: UiThreadContext,
private val systemClock: SystemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index c9be993..947336d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -91,6 +91,7 @@
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.util.concurrency.BackPanelUiThread;
import com.android.systemui.util.concurrency.UiThreadContext;
@@ -297,6 +298,7 @@
private Date mTmpLogDate = new Date();
private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
new NavigationEdgeBackPlugin.BackCallback() {
@@ -423,7 +425,8 @@
Optional<DesktopMode> desktopModeOptional,
FalsingManager falsingManager,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
- Provider<LightBarController> lightBarControllerProvider) {
+ Provider<LightBarController> lightBarControllerProvider,
+ NotificationShadeWindowController notificationShadeWindowController) {
mContext = context;
mDisplayId = context.getDisplayId();
mUiThreadContext = uiThreadContext;
@@ -479,6 +482,7 @@
this::onNavigationSettingsChanged);
updateCurrentUserResources();
+ mNotificationShadeWindowController = notificationShadeWindowController;
}
public void setStateChangeCallback(Runnable callback) {
@@ -1297,6 +1301,9 @@
mBackAnimation.setPilferPointerCallback(() -> {
pilferPointers();
});
+ mBackAnimation.setTopUiRequestCallback(
+ (requestTopUi, tag) -> mUiThreadContext.getExecutor().execute(() ->
+ mNotificationShadeWindowController.setRequestTopUi(requestTopUi, tag)));
updateBackAnimationThresholds();
if (mLightBarControllerProvider.get() != null) {
mBackAnimation.setStatusBarCustomizer((appearance) -> {
@@ -1333,6 +1340,7 @@
private final Provider<BackGestureTfClassifierProvider>
mBackGestureTfClassifierProviderProvider;
private final Provider<LightBarController> mLightBarControllerProvider;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
@Inject
public Factory(OverviewProxyService overviewProxyService,
@@ -1353,7 +1361,8 @@
FalsingManager falsingManager,
Provider<BackGestureTfClassifierProvider>
backGestureTfClassifierProviderProvider,
- Provider<LightBarController> lightBarControllerProvider) {
+ Provider<LightBarController> lightBarControllerProvider,
+ NotificationShadeWindowController notificationShadeWindowController) {
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
@@ -1372,6 +1381,7 @@
mFalsingManager = falsingManager;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
mLightBarControllerProvider = lightBarControllerProvider;
+ mNotificationShadeWindowController = notificationShadeWindowController;
}
/** Construct a {@link EdgeBackGestureHandler}. */
@@ -1396,7 +1406,8 @@
mDesktopModeOptional,
mFalsingManager,
mBackGestureTfClassifierProviderProvider,
- mLightBarControllerProvider));
+ mLightBarControllerProvider,
+ mNotificationShadeWindowController));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index e832abb..afdfa59 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -40,6 +40,8 @@
import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
import static com.android.systemui.shared.rotation.RotationButtonController.DEBUG_ROTATION;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
@@ -48,8 +50,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
-import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE;
-import static com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_WINDOW_STATE;
import static com.android.systemui.statusbar.phone.CentralSurfaces.dumpBarTransitions;
import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
@@ -97,6 +97,7 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.Nullable;
@@ -117,17 +118,17 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger;
import com.android.systemui.navigationbar.NavBarHelper;
import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
+import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.views.buttons.DeadZone;
import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger;
import com.android.systemui.navigationbar.views.buttons.NavbarOrientationTrackingLogger;
-import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
-import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
@@ -1348,6 +1349,9 @@
ButtonDispatcher imeSwitcherButton = mView.getImeSwitchButton();
imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick);
+ if (Flags.imeSwitcherRevamp()) {
+ imeSwitcherButton.setOnLongClickListener(this::onImeSwitcherLongClick);
+ }
updateScreenPinningGestures();
}
@@ -1501,12 +1505,29 @@
mCommandQueue.toggleRecentApps();
}
- private void onImeSwitcherClick(View v) {
+ @VisibleForTesting
+ void onImeSwitcherClick(View v) {
+ mNavBarButtonClickLogger.logImeSwitcherClick();
+ if (Flags.imeSwitcherRevamp()) {
+ mInputMethodManager.onImeSwitchButtonClickFromSystem(mDisplayId);
+ } else {
+ mInputMethodManager.showInputMethodPickerFromSystem(
+ true /* showAuxiliarySubtypes */, mDisplayId);
+ }
+ mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
+ }
+
+ @VisibleForTesting
+ boolean onImeSwitcherLongClick(View v) {
+ if (!Flags.imeSwitcherRevamp()) {
+ return false;
+ }
mNavBarButtonClickLogger.logImeSwitcherClick();
mInputMethodManager.showInputMethodPickerFromSystem(
true /* showAuxiliarySubtypes */, mDisplayId);
- mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
- };
+ mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ return true;
+ }
private boolean onLongPressBackHome(View v) {
return onLongPressNavigationButtons(v, R.id.back, R.id.home);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
index 0f36097..1dbd500 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
@@ -19,7 +19,6 @@
import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED;
@@ -38,7 +37,6 @@
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
-import android.media.permission.SafeCloseable;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.AttributeSet;
@@ -61,19 +59,18 @@
import androidx.annotation.Nullable;
import com.android.app.animation.Interpolators;
-import com.android.app.viewcapture.ViewCaptureFactory;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.ScreenPinningNotify;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.views.buttons.ContextualButton;
import com.android.systemui.navigationbar.views.buttons.ContextualButtonGroup;
import com.android.systemui.navigationbar.views.buttons.DeadZone;
import com.android.systemui.navigationbar.views.buttons.KeyButtonDrawable;
import com.android.systemui.navigationbar.views.buttons.NearestTouchFrame;
-import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.Recents;
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
@@ -181,7 +178,6 @@
private boolean mOverviewProxyEnabled;
private boolean mShowSwipeUpUi;
private UpdateActiveTouchRegionsCallback mUpdateActiveTouchRegionsCallback;
- private SafeCloseable mViewCaptureCloseable;
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
@@ -1082,10 +1078,6 @@
}
updateNavButtonIcons();
- if (enableViewCaptureTracing()) {
- mViewCaptureCloseable = ViewCaptureFactory.getInstance(getContext())
- .startCapture(getRootView(), ".NavigationBarView");
- }
}
@Override
@@ -1098,9 +1090,6 @@
mFloatingRotationButton.hide();
mRotationButtonController.unregisterListeners();
}
- if (mViewCaptureCloseable != null) {
- mViewCaptureCloseable.close();
- }
}
void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
index 1e85d6c..133d14d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
@@ -112,6 +112,9 @@
@UiEvent(doc = "The overview button was long-pressed in the navigation bar.")
NAVBAR_OVERVIEW_BUTTON_LONGPRESS(538),
+ @UiEvent(doc = "The ime switcher button was long-pressed in the navigation bar.")
+ NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS(1799),
+
NONE(0); // an event we should not log
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
index b4cc196..294d0c7 100644
--- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -16,6 +16,7 @@
package com.android.systemui.process;
+import android.app.ActivityManager;
import android.os.Process;
import android.os.UserHandle;
@@ -37,6 +38,13 @@
}
/**
+ * Returns {@code true} if the foreground user is running the current process.
+ */
+ public boolean isForegroundUser() {
+ return ActivityManager.getCurrentUser() == myUserHandle().getIdentifier();
+ }
+
+ /**
* Returns {@link UserHandle} as returned statically by {@link Process#myUserHandle()}.
*
* This should not be used to get the "current" user. This information only applies to the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
index d68b22b..4d6cf78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
@@ -29,7 +29,6 @@
import androidx.annotation.NonNull;
-import com.android.server.display.feature.flags.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.UserTracker;
@@ -81,10 +80,17 @@
mAvailable = true;
synchronized (mListeners) {
if (mListeners.size() > 0) {
- mSecureSettings.unregisterContentObserverSync(mContentObserver);
- mSecureSettings.registerContentObserverForUserSync(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, newUser);
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mContentObserver);
+ mSecureSettings.registerContentObserverForUserAsync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, newUser);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ mSecureSettings.registerContentObserverForUserSync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, newUser);
+ }
}
}
}
@@ -98,9 +104,15 @@
if (!mListeners.contains(listener)) {
mListeners.add(listener);
if (mListeners.size() == 1) {
- mSecureSettings.registerContentObserverForUserSync(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, mUserTracker.getUserId());
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, mUserTracker.getUserId());
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, mUserTracker.getUserId());
+ }
}
}
}
@@ -110,7 +122,11 @@
public void removeCallback(@androidx.annotation.NonNull Listener listener) {
synchronized (mListeners) {
if (mListeners.remove(listener) && mListeners.size() == 0) {
- mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mContentObserver);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ }
}
}
}
@@ -139,7 +155,8 @@
@Override
public boolean isInUpgradeMode(Resources resources) {
- return Flags.evenDimmer() && resources.getBoolean(
+ return com.android.server.display.feature.flags.Flags.evenDimmer()
+ && resources.getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 295a998..782fb2a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -48,6 +48,9 @@
val sourceSpec: MutableState<TileSpec?>,
private val listState: EditTileListState
) {
+ val dragInProgress: Boolean
+ get() = sourceSpec.value != null
+
/** Returns index of the dragged tile if it's present in the list. Returns -1 if not. */
fun currentPosition(): Int {
return sourceSpec.value?.let { listState.indexOf(it) } ?: -1
@@ -65,6 +68,12 @@
sourceSpec.value?.let { listState.move(it, targetSpec) }
}
+ fun movedOutOfBounds() {
+ // Removing the tiles from the current tile grid if it moves out of bounds. This clears
+ // the spacer and makes it apparent that dropping the tile at that point would remove it.
+ sourceSpec.value?.let { listState.removeFromCurrent(it) }
+ }
+
fun onDrop() {
sourceSpec.value = null
}
@@ -112,6 +121,42 @@
}
/**
+ * Registers a composable as a [DragAndDropTarget] to receive drop events. Use this outside the tile
+ * grid to catch out of bounds drops.
+ *
+ * @param dragAndDropState The [DragAndDropState] using the tiles list
+ * @param onDrop Action to be executed when a [TileSpec] is dropped on the composable
+ */
+@Composable
+fun Modifier.dragAndDropRemoveZone(
+ dragAndDropState: DragAndDropState,
+ onDrop: (TileSpec) -> Unit,
+): Modifier {
+ val target =
+ remember(dragAndDropState) {
+ object : DragAndDropTarget {
+ override fun onDrop(event: DragAndDropEvent): Boolean {
+ return dragAndDropState.sourceSpec.value?.let {
+ onDrop(it)
+ dragAndDropState.onDrop()
+ true
+ } ?: false
+ }
+
+ override fun onEntered(event: DragAndDropEvent) {
+ dragAndDropState.movedOutOfBounds()
+ }
+ }
+ }
+ return dragAndDropTarget(
+ shouldStartDragAndDrop = { event ->
+ event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE)
+ },
+ target = target,
+ )
+}
+
+/**
* Registers a tile list as a [DragAndDropTarget] to receive drop events. Use this on list
* containers to catch drops outside of tiles.
*
@@ -128,6 +173,10 @@
val target =
remember(dragAndDropState) {
object : DragAndDropTarget {
+ override fun onEnded(event: DragAndDropEvent) {
+ dragAndDropState.onDrop()
+ }
+
override fun onDrop(event: DragAndDropEvent): Boolean {
return dragAndDropState.sourceSpec.value?.let {
onDrop(it, dragAndDropState.currentPosition())
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index 482c498..34876c4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -46,6 +46,18 @@
tiles.apply { add(toIndex, removeAt(fromIndex).copy(isCurrent = isMovingToCurrent)) }
}
+ /**
+ * Sets the [TileSpec] as a non-current tile. Use this when a tile is dragged out of the current
+ * tile grid.
+ */
+ fun removeFromCurrent(tileSpec: TileSpec) {
+ val fromIndex = indexOf(tileSpec)
+ if (fromIndex >= 0 && fromIndex < tiles.size) {
+ // Mark the moving tile as non-current
+ tiles[fromIndex] = tiles[fromIndex].copy(isCurrent = false)
+ }
+ }
+
fun indexOf(tileSpec: TileSpec): Int {
return tiles.indexOfFirst { it.tileSpec == tileSpec }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index ada774d..add830e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -84,7 +84,7 @@
DefaultEditTileGrid(
tiles = tiles,
isIconOnly = isIcon,
- columns = GridCells.Fixed(columns),
+ columns = columns,
modifier = modifier,
onAddTile = onAddTile,
onRemoveTile = onRemoveTile,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
index 8ca91d8..6c84edd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
@@ -305,9 +305,9 @@
largeTiles,
ClickAction.ADD,
addTileToEnd,
- onDoubleTap,
isIconOnly,
dragAndDropState,
+ onDoubleTap = onDoubleTap,
acceptDrops = { true },
onDrop = onDrop,
)
@@ -318,10 +318,10 @@
smallTiles,
ClickAction.ADD,
addTileToEnd,
- onDoubleTap,
isIconOnly,
+ dragAndDropState,
+ onDoubleTap = onDoubleTap,
showLabels = showLabels,
- dragAndDropState = dragAndDropState,
acceptDrops = { true },
onDrop = onDrop,
)
@@ -332,10 +332,10 @@
tilesCustom,
ClickAction.ADD,
addTileToEnd,
- onDoubleTap,
isIconOnly,
+ dragAndDropState,
+ onDoubleTap = onDoubleTap,
showLabels = showLabels,
- dragAndDropState = dragAndDropState,
acceptDrops = { true },
onDrop = onDrop,
)
@@ -372,11 +372,6 @@
}
}
- private fun gridHeight(nTiles: Int, tileHeight: Dp, columns: Int, padding: Dp): Dp {
- val rows = (nTiles + columns - 1) / columns
- return ((tileHeight + padding) * rows) - padding
- }
-
/** Fill up the rest of the row if it's not complete. */
private fun LazyGridScope.fillUpRow(nTiles: Int, columns: Int) {
if (nTiles % columns != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
index 770d4412..3e48245 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
@@ -100,7 +100,7 @@
DefaultEditTileGrid(
tiles = tiles,
isIconOnly = iconTilesViewModel::isIconTile,
- columns = GridCells.Fixed(columns),
+ columns = columns,
modifier = modifier,
onAddTile = onAddTile,
onRemoveTile = onRemoveTile,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 3fdd7f7..bd7956d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -23,13 +23,19 @@
import android.service.quicksettings.Tile.STATE_INACTIVE
import android.text.TextUtils
import androidx.appcompat.content.res.AppCompatResources
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
+import androidx.compose.foundation.LocalOverscrollConfiguration
import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Arrangement.spacedBy
@@ -37,20 +43,31 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Clear
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -80,6 +97,8 @@
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.TileRow
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.toUiState
@@ -269,7 +288,7 @@
fun DefaultEditTileGrid(
tiles: List<EditTileViewModel>,
isIconOnly: (TileSpec) -> Boolean,
- columns: GridCells,
+ columns: Int,
modifier: Modifier,
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit,
@@ -277,84 +296,264 @@
) {
val currentListState = rememberEditListState(tiles)
val dragAndDropState = rememberDragAndDropState(currentListState)
-
val (currentTiles, otherTiles) = currentListState.tiles.partition { it.isCurrent }
- val (otherTilesStock, otherTilesCustom) =
- otherTiles
- .filter { !dragAndDropState.isMoving(it.tileSpec) }
- .partition { it.appName == null }
+
val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
}
-
val onDropAdd: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, position ->
onAddTile(tileSpec, position)
}
- val onDropRemove: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, _ ->
- onRemoveTile(tileSpec)
- }
val onDoubleTap: (TileSpec) -> Unit by rememberUpdatedState { tileSpec ->
onResize(tileSpec, !isIconOnly(tileSpec))
}
+ val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
- TileLazyGrid(
- modifier = modifier.dragAndDropTileList(dragAndDropState, { true }, onDropAdd),
- columns = columns
+ CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
+ Column(
+ verticalArrangement =
+ spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
+ modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState())
+ ) {
+ AnimatedContent(
+ targetState = dragAndDropState.dragInProgress,
+ modifier = Modifier.wrapContentSize()
+ ) { dragIsInProgress ->
+ EditGridHeader(Modifier.dragAndDropRemoveZone(dragAndDropState, onRemoveTile)) {
+ if (dragIsInProgress) {
+ RemoveTileTarget()
+ } else {
+ Text(text = "Hold and drag to rearrange tiles.")
+ }
+ }
+ }
+
+ CurrentTilesGrid(
+ currentTiles,
+ columns,
+ tilePadding,
+ isIconOnly,
+ onRemoveTile,
+ onDoubleTap,
+ dragAndDropState,
+ onDropAdd,
+ )
+
+ // Hide available tiles when dragging
+ AnimatedVisibility(
+ visible = !dragAndDropState.dragInProgress,
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ Column(
+ verticalArrangement =
+ spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
+ modifier = modifier.fillMaxSize()
+ ) {
+ EditGridHeader { Text(text = "Hold and drag to add tiles.") }
+
+ AvailableTileGrid(
+ otherTiles,
+ columns,
+ tilePadding,
+ addTileToEnd,
+ dragAndDropState,
+ )
+ }
+ }
+
+ // Drop zone to remove tiles dragged out of the tile grid
+ Spacer(
+ modifier =
+ Modifier.fillMaxWidth()
+ .weight(1f)
+ .dragAndDropRemoveZone(dragAndDropState, onRemoveTile)
+ )
+ }
+ }
+}
+
+@Composable
+private fun EditGridHeader(
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit
+) {
+ CompositionLocalProvider(
+ LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f)
) {
- // These Text are just placeholders to see the different sections. Not final UI.
- item(span = { GridItemSpan(maxLineSpan) }) { Text("Current tiles", color = Color.White) }
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = modifier.fillMaxWidth().height(TileDefaults.EditGridHeaderHeight)
+ ) {
+ content()
+ }
+ }
+}
- editTiles(
- currentTiles,
- ClickAction.REMOVE,
- onRemoveTile,
- onDoubleTap,
- isIconOnly,
- indicatePosition = true,
- dragAndDropState = dragAndDropState,
- acceptDrops = { true },
- onDrop = onDropAdd,
- )
+@Composable
+private fun RemoveTileTarget() {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(),
+ modifier =
+ Modifier.fillMaxHeight()
+ .border(1.dp, LocalContentColor.current, shape = TileDefaults.TileShape)
+ .padding(10.dp)
+ ) {
+ Icon(imageVector = Icons.Default.Clear, contentDescription = null)
+ Text(text = "Remove")
+ }
+}
- item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) }
+@Composable
+private fun CurrentTilesContainer(content: @Composable () -> Unit) {
+ Box(
+ Modifier.fillMaxWidth()
+ .border(
+ width = 1.dp,
+ color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f),
+ shape = RoundedCornerShape(48.dp),
+ )
+ .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
+ ) {
+ content()
+ }
+}
+@Composable
+private fun CurrentTilesGrid(
+ tiles: List<EditTileViewModel>,
+ columns: Int,
+ tilePadding: Dp,
+ isIconOnly: (TileSpec) -> Boolean,
+ onClick: (TileSpec) -> Unit,
+ onDoubleTap: (TileSpec) -> Unit,
+ dragAndDropState: DragAndDropState,
+ onDrop: (TileSpec, Int) -> Unit
+) {
+ val tileHeight = tileHeight()
+ val currentRows =
+ remember(tiles) {
+ calculateRows(
+ tiles.map {
+ SizedTile(
+ it,
+ if (isIconOnly(it.tileSpec)) {
+ 1
+ } else {
+ 2
+ }
+ )
+ },
+ columns
+ )
+ }
+ val currentGridHeight = gridHeight(currentRows, tileHeight, tilePadding)
+ // Current tiles
+ CurrentTilesContainer {
+ TileLazyGrid(
+ modifier =
+ Modifier.height(currentGridHeight)
+ .dragAndDropTileList(dragAndDropState, { true }, onDrop),
+ columns = GridCells.Fixed(columns)
+ ) {
+ editTiles(
+ tiles,
+ ClickAction.REMOVE,
+ onClick,
+ isIconOnly,
+ dragAndDropState,
+ onDoubleTap = onDoubleTap,
+ indicatePosition = true,
+ acceptDrops = { true },
+ onDrop = onDrop,
+ )
+ }
+ }
+}
+
+@Composable
+private fun AvailableTileGrid(
+ tiles: List<EditTileViewModel>,
+ columns: Int,
+ tilePadding: Dp,
+ onClick: (TileSpec) -> Unit,
+ dragAndDropState: DragAndDropState,
+) {
+ val (otherTilesStock, otherTilesCustom) =
+ tiles.filter { !dragAndDropState.isMoving(it.tileSpec) }.partition { it.appName == null }
+ val availableTileHeight = tileHeight(true)
+ val availableGridHeight = gridHeight(tiles.size, availableTileHeight, columns, tilePadding)
+
+ // Available tiles
+ TileLazyGrid(
+ modifier =
+ Modifier.height(availableGridHeight)
+ .dragAndDropTileList(dragAndDropState, { false }, { _, _ -> }),
+ columns = GridCells.Fixed(columns)
+ ) {
editTiles(
otherTilesStock,
ClickAction.ADD,
- addTileToEnd,
- onDoubleTap,
- isIconOnly,
+ onClick,
+ isIconOnly = { true },
dragAndDropState = dragAndDropState,
- acceptDrops = { true },
- onDrop = onDropRemove,
+ acceptDrops = { false },
+ showLabels = true,
)
-
- item(span = { GridItemSpan(maxLineSpan) }) {
- Text("Custom tiles to add", color = Color.White)
- }
-
editTiles(
otherTilesCustom,
ClickAction.ADD,
- addTileToEnd,
- onDoubleTap,
- isIconOnly,
+ onClick,
+ isIconOnly = { true },
dragAndDropState = dragAndDropState,
- acceptDrops = { true },
- onDrop = onDropRemove,
+ acceptDrops = { false },
+ showLabels = true,
)
}
}
+fun gridHeight(nTiles: Int, tileHeight: Dp, columns: Int, padding: Dp): Dp {
+ val rows = (nTiles + columns - 1) / columns
+ return gridHeight(rows, tileHeight, padding)
+}
+
+fun gridHeight(rows: Int, tileHeight: Dp, padding: Dp): Dp {
+ return ((tileHeight + padding) * rows) - padding
+}
+
+private fun calculateRows(tiles: List<SizedTile<EditTileViewModel>>, columns: Int): Int {
+ val row = TileRow<EditTileViewModel>(columns)
+ var count = 0
+
+ for (tile in tiles) {
+ if (row.maybeAddTile(tile)) {
+ if (row.isFull()) {
+ // Row is full, no need to stretch tiles
+ count += 1
+ row.clear()
+ }
+ } else {
+ count += 1
+ row.clear()
+ row.maybeAddTile(tile)
+ }
+ }
+ if (row.tiles.isNotEmpty()) {
+ count += 1
+ }
+ return count
+}
+
fun LazyGridScope.editTiles(
tiles: List<EditTileViewModel>,
clickAction: ClickAction,
onClick: (TileSpec) -> Unit,
- onDoubleTap: (TileSpec) -> Unit,
isIconOnly: (TileSpec) -> Boolean,
dragAndDropState: DragAndDropState,
acceptDrops: (TileSpec) -> Boolean,
- onDrop: (TileSpec, Int) -> Unit,
+ onDoubleTap: (TileSpec) -> Unit = {},
+ onDrop: (TileSpec, Int) -> Unit = { _, _ -> },
showLabels: Boolean = false,
indicatePosition: Boolean = false,
) {
@@ -534,6 +733,7 @@
private object TileDefaults {
val TileShape = CircleShape
val IconTileWithLabelHeight = 140.dp
+ val EditGridHeaderHeight = 60.dp
@Composable
fun activeTileColors(): TileColors =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index 62bfc72..ef2c8bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -151,12 +151,27 @@
* present, it will be moved to the new position.
*/
fun addTile(tileSpec: TileSpec, position: Int = POSITION_AT_END) {
- // Removing tile if it's already present to insert it at the new index.
- if (currentTilesInteractor.currentTilesSpecs.contains(tileSpec)) {
- removeTile(tileSpec)
+ val specs = currentTilesInteractor.currentTilesSpecs.toMutableList()
+ val currentPosition = specs.indexOf(tileSpec)
+
+ if (currentPosition != -1) {
+ // No operation needed if the element is already in the list at the right position
+ if (currentPosition == position) {
+ return
+ }
+ // Removing tile if it's present at a different position to insert it at the new index.
+ specs.removeAt(currentPosition)
}
- currentTilesInteractor.addTile(tileSpec, position)
+ if (position >= 0 && position < specs.size) {
+ specs.add(position, tileSpec)
+ } else {
+ specs.add(tileSpec)
+ }
+
+ // Setting the new tiles as one operation to avoid UI jank with tiles disappearing and
+ // reappearing
+ currentTilesInteractor.setTiles(specs)
}
/** Immediately removes [tileSpec] from the current tiles. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index df7430a..158eb6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -65,6 +65,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -184,7 +185,7 @@
private GlobalSettings mGlobalSettings;
private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private ConnectivityManager.NetworkCallback mConnectivityManagerNetworkCallback;
- private WindowManager mWindowManager;
+ private ViewCaptureAwareWindowManager mWindowManager;
private ToastFactory mToastFactory;
private SignalDrawable mSignalDrawable;
private SignalDrawable mSecondarySignalDrawable; // For the secondary mobile data sub in DSDS
@@ -246,7 +247,7 @@
@Main Handler handler, @Main Executor mainExecutor,
BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor,
GlobalSettings globalSettings, KeyguardStateController keyguardStateController,
- WindowManager windowManager, ToastFactory toastFactory,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, ToastFactory toastFactory,
@Background Handler workerHandler,
CarrierConfigTracker carrierConfigTracker,
LocationController locationController,
@@ -278,7 +279,7 @@
mAccessPointController = accessPointController;
mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext);
mConnectivityManagerNetworkCallback = new DataConnectivityListener();
- mWindowManager = windowManager;
+ mWindowManager = viewCaptureAwareWindowManager;
mToastFactory = toastFactory;
mSignalDrawable = new SignalDrawable(mContext);
mSecondarySignalDrawable = new SignalDrawable(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index da4d2f1..930109a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -18,7 +18,6 @@
import android.app.Flags
import android.os.UserHandle
-import android.provider.Settings.Global.ZEN_MODE_OFF
import com.android.settingslib.notification.data.repository.ZenModeRepository
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
@@ -31,9 +30,10 @@
class ModesTileDataInteractor @Inject constructor(val zenModeRepository: ZenModeRepository) :
QSTileDataInteractor<ModesTileModel> {
- // TODO(b/346519570): This should be checking for any enabled modes.
private val zenModeActive =
- zenModeRepository.globalZenMode.map { it != ZEN_MODE_OFF }.distinctUntilChanged()
+ zenModeRepository.modes
+ .map { modes -> modes.any { mode -> mode.isActive } }
+ .distinctUntilChanged()
override fun tileData(
user: UserHandle,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index b3624ad..371707d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -72,6 +72,7 @@
import android.view.MotionEvent;
import android.view.Surface;
import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
@@ -302,10 +303,29 @@
public void onImeSwitcherPressed() {
// TODO(b/204901476) We're intentionally using the default display for now since
// Launcher/Taskbar isn't display aware.
+ if (Flags.imeSwitcherRevamp()) {
+ mContext.getSystemService(InputMethodManager.class)
+ .onImeSwitchButtonClickFromSystem(mDisplayTracker.getDefaultDisplayId());
+ } else {
+ mContext.getSystemService(InputMethodManager.class)
+ .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
+ mDisplayTracker.getDefaultDisplayId());
+ }
+ mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
+ }
+
+ @Override
+ public void onImeSwitcherLongPress() {
+ if (!Flags.imeSwitcherRevamp()) {
+ return;
+ }
+ // TODO(b/204901476) We're intentionally using the default display for now since
+ // Launcher/Taskbar isn't display aware.
mContext.getSystemService(InputMethodManager.class)
.showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
mDisplayTracker.getDefaultDisplayId());
- mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
+ mUiEventLogger.log(
+ KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 1f4820a..8711e88 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -665,7 +665,7 @@
keyguardEnabledInteractor.isKeyguardEnabled
.sample(
combine(
- deviceEntryInteractor.isInLockdown,
+ deviceUnlockedInteractor.isInLockdown,
deviceEntryInteractor.isDeviceEntered,
::Pair,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 3dc2070..46c5861 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -27,7 +27,6 @@
import android.os.CountDownTimer;
import android.os.Process;
import android.os.UserHandle;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -60,8 +59,6 @@
@SysUISingleton
public class RecordingController
implements CallbackController<RecordingController.RecordingStateChangeCallback> {
- private static final String TAG = "RecordingController";
-
private boolean mIsStarting;
private boolean mIsRecording;
private PendingIntent mStopIntent;
@@ -71,6 +68,7 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final FeatureFlags mFlags;
private final UserTracker mUserTracker;
+ private final RecordingControllerLogger mRecordingControllerLogger;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
private final ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
@@ -102,9 +100,10 @@
if (intent != null && INTENT_UPDATE_STATE.equals(intent.getAction())) {
if (intent.hasExtra(EXTRA_STATE)) {
boolean state = intent.getBooleanExtra(EXTRA_STATE, false);
+ mRecordingControllerLogger.logIntentStateUpdated(state);
updateState(state);
} else {
- Log.e(TAG, "Received update intent with no state");
+ mRecordingControllerLogger.logIntentMissingState();
}
}
}
@@ -120,6 +119,7 @@
FeatureFlags flags,
Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
UserTracker userTracker,
+ RecordingControllerLogger recordingControllerLogger,
MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate,
ScreenRecordDialogDelegate.Factory screenRecordDialogFactory,
@@ -130,6 +130,7 @@
mDevicePolicyResolver = devicePolicyResolver;
mBroadcastDispatcher = broadcastDispatcher;
mUserTracker = userTracker;
+ mRecordingControllerLogger = recordingControllerLogger;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
mScreenRecordDialogFactory = screenRecordDialogFactory;
@@ -212,9 +213,9 @@
IntentFilter stateFilter = new IntentFilter(INTENT_UPDATE_STATE);
mBroadcastDispatcher.registerReceiver(mStateChangeReceiver, stateFilter, null,
UserHandle.ALL);
- Log.d(TAG, "sent start intent");
+ mRecordingControllerLogger.logSentStartIntent();
} catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
+ mRecordingControllerLogger.logPendingIntentCancelled(e);
}
}
};
@@ -227,9 +228,10 @@
*/
public void cancelCountdown() {
if (mCountDownTimer != null) {
+ mRecordingControllerLogger.logCountdownCancelled();
mCountDownTimer.cancel();
} else {
- Log.e(TAG, "Timer was null");
+ mRecordingControllerLogger.logCountdownCancelErrorNoTimer();
}
mIsStarting = false;
@@ -260,13 +262,14 @@
public void stopRecording() {
try {
if (mStopIntent != null) {
+ mRecordingControllerLogger.logRecordingStopped();
mStopIntent.send(mInteractiveBroadcastOption);
} else {
- Log.e(TAG, "Stop intent was null");
+ mRecordingControllerLogger.logRecordingStopErrorNoStopIntent();
}
updateState(false);
} catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Error stopping: " + e.getMessage());
+ mRecordingControllerLogger.logRecordingStopError(e);
}
}
@@ -275,6 +278,7 @@
* @param isRecording
*/
public synchronized void updateState(boolean isRecording) {
+ mRecordingControllerLogger.logStateUpdated(isRecording);
if (!isRecording && mIsRecording) {
// Unregister receivers if we have stopped recording
mUserTracker.removeCallback(mUserChangedCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLog.kt
new file mode 100644
index 0000000..dd2baef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLog.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord
+
+import javax.inject.Qualifier
+
+/**
+ * Logs for screen record events. See [com.android.systemui.screenrecord.RecordingController] and
+ * [com.android.systemui.screenrecord.RecordingControllerLogger].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class RecordingControllerLog
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLogger.kt
new file mode 100644
index 0000000..e16c010
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLogger.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import javax.inject.Inject
+
+/** Helper class for logging events to [RecordingControllerLog] from Java. */
+@SysUISingleton
+class RecordingControllerLogger
+@Inject
+constructor(
+ @RecordingControllerLog private val logger: LogBuffer,
+) {
+ fun logStateUpdated(isRecording: Boolean) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isRecording },
+ { "Updating state. isRecording=$bool1" },
+ )
+
+ fun logIntentStateUpdated(isRecording: Boolean) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isRecording },
+ { "Update intent has state. isRecording=$bool1" },
+ )
+
+ fun logIntentMissingState() =
+ logger.log(TAG, LogLevel.ERROR, {}, { "Received update intent with no state" })
+
+ fun logSentStartIntent() = logger.log(TAG, LogLevel.DEBUG, {}, { "Sent start intent" })
+
+ fun logPendingIntentCancelled(e: Exception) =
+ logger.log(TAG, LogLevel.ERROR, {}, { "Pending intent was cancelled" }, e)
+
+ fun logCountdownCancelled() =
+ logger.log(TAG, LogLevel.DEBUG, {}, { "Record countdown cancelled" })
+
+ fun logCountdownCancelErrorNoTimer() =
+ logger.log(TAG, LogLevel.ERROR, {}, { "Couldn't cancel countdown because timer was null" })
+
+ fun logRecordingStopped() = logger.log(TAG, LogLevel.DEBUG, {}, { "Stopping recording" })
+
+ fun logRecordingStopErrorNoStopIntent() =
+ logger.log(
+ TAG,
+ LogLevel.ERROR,
+ {},
+ { "Couldn't stop recording because stop intent was null" },
+ )
+
+ fun logRecordingStopError(e: Exception) =
+ logger.log(TAG, LogLevel.DEBUG, {}, { "Couldn't stop recording" }, e)
+
+ companion object {
+ private const val TAG = "RecordingController"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
index d7ddc50..a830e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
@@ -16,6 +16,9 @@
package com.android.systemui.screenrecord
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -53,7 +56,7 @@
@IntoMap
@StringKey(SCREEN_RECORD_TILE_SPEC)
fun provideScreenRecordAvailabilityInteractor(
- impl: ScreenRecordTileDataInteractor
+ impl: ScreenRecordTileDataInteractor
): QSTileAvailabilityInteractor
companion object {
@@ -89,5 +92,12 @@
stateInteractor,
mapper,
)
+
+ @Provides
+ @SysUISingleton
+ @RecordingControllerLog
+ fun provideRecordingControllerLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("RecordingControllerLog", 50)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 49810762..8e53949 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -137,14 +137,24 @@
public void startObserving() {
if (!mObserving) {
mObserving = true;
- mSecureSettings.registerContentObserverForUserSync(
- BRIGHTNESS_MODE_URI,
- false, this, UserHandle.USER_ALL);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ BRIGHTNESS_MODE_URI,
+ false, this, UserHandle.USER_ALL);
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ BRIGHTNESS_MODE_URI,
+ false, this, UserHandle.USER_ALL);
+ }
}
}
public void stopObserving() {
- mSecureSettings.unregisterContentObserverSync(this);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(this);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(this);
+ }
mObserving = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index abf258c..693cc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -485,9 +485,8 @@
final boolean intersectsTopCutout = topDisplayCutout.intersects(
width - (windowWidth / 2), 0,
width + (windowWidth / 2), topDisplayCutout.bottom);
- if (mClingWindow != null &&
- (windowWidth < 0 || (width > 0 && intersectsTopCutout))) {
- final View iconView = mClingWindow.findViewById(R.id.immersive_cling_icon);
+ if (windowWidth < 0 || (width > 0 && intersectsTopCutout)) {
+ final View iconView = findViewById(R.id.immersive_cling_icon);
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)
iconView.getLayoutParams();
lp.topMargin = topDisplayCutout.bottom;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index da89eea..766c391 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -526,7 +526,7 @@
keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
assistIcon,
- KeyEvent.KEYCODE_UNKNOWN,
+ KeyEvent.KEYCODE_A,
KeyEvent.META_META_ON));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index ed1756a..11ccdff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -23,8 +23,11 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -48,6 +51,7 @@
interactor: CallChipInteractor,
systemClock: SystemClock,
private val activityStarter: ActivityStarter,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
interactor.ongoingCallState
@@ -86,9 +90,9 @@
}
return View.OnClickListener { view ->
+ logger.log(TAG, LogLevel.INFO, {}, { "Chip clicked" })
val backgroundView =
view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
- // TODO(b/332662551): Log the click event.
// This mimics OngoingCallController#updateChipClickListener.
activityStarter.postStartActivityDismissingKeyguard(
state.intent,
@@ -108,5 +112,6 @@
R.string.ongoing_phone_call_content_description,
),
)
+ private const val TAG = "CallVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
index 6917f46..7c95f1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
@@ -18,7 +18,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.mediarouter.data.repository.MediaRouterRepository
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
import com.android.systemui.statusbar.policy.CastDevice
import javax.inject.Inject
@@ -38,6 +41,7 @@
constructor(
@Application private val scope: CoroutineScope,
private val mediaRouterRepository: MediaRouterRepository,
+ @StatusBarChipsLog private val logger: LogBuffer,
) {
private val activeCastDevice: StateFlow<CastDevice?> =
mediaRouterRepository.castDevices
@@ -49,8 +53,10 @@
activeCastDevice
.map {
if (it != null) {
+ logger.log(TAG, LogLevel.INFO, { str1 = it.name }, { "State: Casting($str1)" })
MediaRouterCastModel.Casting(deviceName = it.name)
} else {
+ logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" })
MediaRouterCastModel.DoingNothing
}
}
@@ -60,4 +66,8 @@
fun stopCasting() {
activeCastDevice.value?.let { mediaRouterRepository.stopCasting(it) }
}
+
+ companion object {
+ private const val TAG = "MediaRouter"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index f5e17df..afa9cce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -22,7 +22,10 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.MediaRouterChipInteractor
import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndCastScreenToOtherDeviceDialogDelegate
@@ -58,6 +61,7 @@
private val mediaRouterChipInteractor: MediaRouterChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
/**
* The cast chip to show, based only on MediaProjection API events.
@@ -123,6 +127,16 @@
override val chip: StateFlow<OngoingActivityChipModel> =
combine(projectionChip, routerChip) { projection, router ->
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = projection.logName
+ str2 = router.logName
+ },
+ { "projectionChip=$str1 > routerChip=$str2" }
+ )
+
// A consequence of b/269975671 is that MediaRouter and MediaProjection APIs fire at
// different times when *screen* casting:
//
@@ -149,10 +163,13 @@
/** Stops the currently active projection. */
private fun stopProjecting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (projection)" })
mediaProjectionChipInteractor.stopProjecting()
}
+ /** Stops the currently active media route. */
private fun stopMediaRouterCasting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (router)" })
mediaRouterChipInteractor.stopCasting()
}
@@ -173,6 +190,8 @@
startTimeMs = systemClock.elapsedRealtime(),
createDialogLaunchOnClickListener(
createCastScreenToOtherDeviceDialogDelegate(state),
+ logger,
+ TAG,
),
)
}
@@ -188,6 +207,8 @@
colors = ColorsModel.Red,
createDialogLaunchOnClickListener(
createGenericCastToOtherDeviceDialogDelegate(deviceName),
+ logger,
+ TAG,
),
)
}
@@ -212,5 +233,6 @@
companion object {
@DrawableRes val CAST_TO_OTHER_DEVICE_ICON = R.drawable.ic_cast_connected
+ private const val TAG = "CastToOtherVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index e201652..0c34981 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -23,8 +23,11 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
@@ -51,6 +54,7 @@
private val interactor: ScreenRecordChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
interactor.screenRecordState
@@ -76,6 +80,8 @@
startTimeMs = systemClock.elapsedRealtime(),
createDialogLaunchOnClickListener(
createDelegate(state.recordedTask),
+ logger,
+ TAG,
),
)
}
@@ -90,12 +96,18 @@
return EndScreenRecordingDialogDelegate(
endMediaProjectionDialogHelper,
context,
- stopAction = interactor::stopRecording,
+ stopAction = this::stopRecording,
recordedTask,
)
}
+ private fun stopRecording() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested" })
+ interactor.stopRecording()
+ }
+
companion object {
@DrawableRes val ICON = R.drawable.ic_screenrecord
+ private const val TAG = "ScreenRecordVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 45260e18..ddebd3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -22,7 +22,10 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
@@ -52,6 +55,7 @@
private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
mediaProjectionChipInteractor.projection
@@ -72,6 +76,7 @@
/** Stops the currently active projection. */
private fun stopProjecting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested" })
mediaProjectionChipInteractor.stopProjecting()
}
@@ -87,7 +92,7 @@
colors = ColorsModel.Red,
// TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
startTimeMs = systemClock.elapsedRealtime(),
- createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state)),
+ createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state), logger, TAG),
)
}
@@ -101,5 +106,6 @@
companion object {
@DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all
+ private const val TAG = "ShareToAppVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index 65f94ac..ee010f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -17,6 +17,9 @@
package com.android.systemui.statusbar.chips.ui.viewmodel
import android.view.View
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.phone.SystemUIDialog
import kotlinx.coroutines.flow.StateFlow
@@ -33,8 +36,11 @@
/** Creates a chip click listener that launches a dialog created by [dialogDelegate]. */
fun createDialogLaunchOnClickListener(
dialogDelegate: SystemUIDialog.Delegate,
+ @StatusBarChipsLog logger: LogBuffer,
+ tag: String,
): View.OnClickListener {
- return View.OnClickListener { view ->
+ return View.OnClickListener { _ ->
+ logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
val dialog = dialogDelegate.createDialog()
dialog.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index b63ee4c..ca5f49d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -25,6 +25,7 @@
import com.android.settingslib.notification.data.repository.ZenModeRepository;
import com.android.settingslib.notification.data.repository.ZenModeRepositoryImpl;
import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
@@ -288,6 +289,7 @@
@Background Handler handler
) {
return new ZenModeRepositoryImpl(context, notificationManager,
+ ZenModesBackend.getInstance(context), context.getContentResolver(),
coroutineScope, coroutineContext, handler);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 1789ad6..2081adc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3519,7 +3519,7 @@
// Only when scene container is enabled, mark that we are being dragged so that we start
// dispatching the rest of the gesture to scene container.
void startOverscrollAfterExpanding() {
- SceneContainerFlag.isUnexpectedlyInLegacyMode();
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
getExpandHelper().finishExpanding();
setIsBeingDragged(true);
}
@@ -3527,7 +3527,7 @@
// Only when scene container is enabled, mark that we are being dragged so that we start
// dispatching the rest of the gesture to scene container.
void startDraggingOnHun() {
- SceneContainerFlag.isUnexpectedlyInLegacyMode();
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
setIsBeingDragged(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 726fdee..a072ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -2034,6 +2034,7 @@
hunWantsIt = mHeadsUpTouchHelper.onInterceptTouchEvent(ev);
if (hunWantsIt) {
mView.startDraggingOnHun();
+ mHeadsUpManager.unpinAll(true);
}
}
boolean swipeWantsIt = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index b54f9c4..5fba615 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -275,13 +275,7 @@
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- combine(
- notificationStackInteractor.isShowingOnLockscreen,
- shadeInteractor.isShadeFullyCollapsed
- ) { (isKeyguardShowing, isShadeFullyCollapsed) ->
- !isKeyguardShowing && isShadeFullyCollapsed
- }
- .dumpWhileCollecting("headsUpAnimationsEnabled")
+ flowOf(true).dumpWhileCollecting("headsUpAnimationsEnabled")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index eb1f778..1a7bc16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -110,6 +110,11 @@
interactor.setScrolledToTop(scrolledToTop)
}
+ /** Sets whether the heads up notification is animating away. */
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ headsUpNotificationInteractor.setHeadsUpAnimatingAway(animatingAway)
+ }
+
/** Snooze the currently pinned HUN. */
fun snoozeHun() {
headsUpNotificationInteractor.snooze()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 6d76200..6f29f61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -369,10 +369,12 @@
private void releaseBiometricWakeLock() {
if (mWakeLock != null) {
+ Trace.beginSection("release wake-and-unlock");
mHandler.removeCallbacks(mReleaseBiometricWakeLockRunnable);
mLogger.i("releasing biometric wakelock");
mWakeLock.release();
mWakeLock = null;
+ Trace.endSection();
}
}
@@ -398,7 +400,7 @@
}
mWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BIOMETRIC_WAKE_LOCK_NAME);
- Trace.beginSection("acquiring wake-and-unlock");
+ Trace.beginSection("acquire wake-and-unlock");
mWakeLock.acquire();
Trace.endSection();
mLogger.i("biometric acquired, grabbing biometric wakelock");
@@ -412,14 +414,13 @@
public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
Trace.beginSection("BiometricUnlockController#onBiometricDetected");
- if (mUpdateMonitor.isGoingToSleep()) {
- Trace.endSection();
- return;
+ if (!mUpdateMonitor.isGoingToSleep()) {
+ startWakeAndUnlock(
+ MODE_SHOW_BOUNCER,
+ BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+ );
}
- startWakeAndUnlock(
- MODE_SHOW_BOUNCER,
- BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
- );
+ Trace.endSection();
}
@Override
@@ -451,6 +452,7 @@
} else {
mLogger.d("onBiometricUnlocked aborted by bypass controller");
}
+ Trace.endSection();
}
/**
@@ -479,6 +481,7 @@
@WakeAndUnlockMode int mode,
BiometricUnlockSource biometricUnlockSource
) {
+ Trace.beginSection("BiometricUnlockController#startWakeAndUnlock");
mLogger.logStartWakeAndUnlock(mode);
boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
mMode = mode;
@@ -501,9 +504,7 @@
"android.policy:BIOMETRIC"
);
}
- Trace.beginSection("release wake-and-unlock");
releaseBiometricWakeLock();
- Trace.endSection();
};
final boolean wakeInKeyguard = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 7f16e18..26bd7ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -124,7 +124,6 @@
mPanel.setHeadsUpDraggingStartingHeight(startHeight);
mPanel.startExpand(x, y, true /* startTracking */, startHeight);
- // TODO(b/340514839): Figure out where to move this side effect in flexiglass
if (!SceneContainerFlag.isEnabled()) {
// This call needs to be after the expansion start otherwise we will get a
// flicker of one frame as it's not expanded yet.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 97791ac..316e1f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricSourceType
import android.provider.Settings
import com.android.app.tracing.ListenersTracing.forEachTraced
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -159,7 +160,7 @@
}
fun listenForQsExpandedChange() =
- applicationScope.launch {
+ applicationScope.launch("listenForQsExpandedChange") {
shadeInteractorLazy.get().qsExpansion.map { it > 0f }.distinctUntilChanged()
.collect { isQsExpanded ->
val changed = qsExpanded != isQsExpanded
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index bcb613f..de76b10d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -276,10 +276,11 @@
statusBarController
}
+ val isCommunalDismissLaunch = isCommunalWidgetLaunch() && !actuallyShowOverLockscreen
// If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
// run the animation on the keyguard). The animation will take care of (instantly)
// collapsing the shade and hiding the keyguard once it is done.
- val collapse = dismissShade && !animate
+ val collapse = (dismissShade || isCommunalDismissLaunch) && !animate
val runnable = Runnable {
try {
activityTransitionAnimator.startPendingIntentWithAnimation(
@@ -338,8 +339,9 @@
postOnUiThread(delay = 0) {
executeRunnableDismissingKeyguard(
runnable = runnable,
- afterKeyguardGone = willLaunchResolverActivity,
dismissShade = collapse,
+ afterKeyguardGone = willLaunchResolverActivity,
+ deferred = isCommunalDismissLaunch,
willAnimateOnKeyguard = animate,
customMessage = customMessage,
)
@@ -461,7 +463,9 @@
override fun onDismiss(): Boolean {
if (runnable != null) {
if (
- keyguardStateController.isShowing && keyguardStateController.isOccluded
+ keyguardStateController.isShowing &&
+ keyguardStateController.isOccluded &&
+ !isCommunalWidgetLaunch()
) {
statusBarKeyguardViewManagerLazy
.get()
@@ -473,17 +477,10 @@
if (dismissShade) {
shadeControllerLazy.get().collapseShadeForActivityStart()
}
- if (communalHub()) {
- communalSceneInteractor.changeSceneForActivityStartOnDismissKeyguard()
- }
return deferred
}
override fun willRunAnimationOnKeyguard(): Boolean {
- if (communalHub() && communalSceneInteractor.isIdleOnCommunal.value) {
- // Override to false when launching activity over the hub that requires auth
- return false
- }
return willAnimateOnKeyguard
}
}
@@ -639,7 +636,8 @@
showOverLockscreen: Boolean,
): Boolean {
// TODO(b/294418322): always support launch animations when occluded.
- val ignoreOcclusion = showOverLockscreen && mediaLockscreenLaunchAnimation()
+ val ignoreOcclusion =
+ (showOverLockscreen && mediaLockscreenLaunchAnimation()) || isCommunalWidgetLaunch()
if (keyguardStateController.isOccluded && !ignoreOcclusion) {
return false
}
@@ -659,6 +657,12 @@
return shouldAnimateLaunch(isActivityIntent, false)
}
+ private fun isCommunalWidgetLaunch(): Boolean {
+ return communalHub() &&
+ communalSceneInteractor.isCommunalVisible.value &&
+ communalSceneInteractor.isLaunchingWidget.value
+ }
+
private fun postOnUiThread(delay: Int = 0, runnable: Runnable) {
mainExecutor.executeDelayed(runnable, delay.toLong())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 43ab337..40799583 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -91,7 +91,11 @@
}
/** Run or delay Runnable for given HeadsUpEntry */
- fun update(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
+ fun update(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ if (runnable == null) {
+ log { "Runnable is NULL, stop update." }
+ return
+ }
if (!NotificationThrottleHun.isEnabled) {
runnable.run()
return
@@ -147,7 +151,11 @@
* Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
* all Runnables associated with that entry.
*/
- fun delete(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
+ fun delete(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ if (runnable == null) {
+ log { "Runnable is NULL, stop delete." }
+ return
+ }
if (!NotificationThrottleHun.isEnabled) {
runnable.run()
return
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 45cb52a..7b82b56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -26,7 +26,6 @@
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.util.ArrayMap;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -46,11 +45,11 @@
/** Platform implementation of the cast controller. **/
@SysUISingleton
public class CastControllerImpl implements CastController {
- public static final String TAG = "CastController";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String TAG = "CastController";
private final Context mContext;
private final PackageManager mPackageManager;
+ private final CastControllerLogger mLogger;
@GuardedBy("mCallbacks")
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final MediaRouter mMediaRouter;
@@ -67,20 +66,22 @@
public CastControllerImpl(
Context context,
PackageManager packageManager,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ CastControllerLogger logger) {
mContext = context;
mPackageManager = packageManager;
+ mLogger = logger;
mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mMediaRouter.setRouterGroupId(MediaRouter.MIRRORING_GROUP_ID);
mProjectionManager = (MediaProjectionManager)
context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
mProjection = mProjectionManager.getActiveProjectionInfo();
mProjectionManager.addCallback(mProjectionCallback, new Handler());
- dumpManager.registerDumpable(TAG, this);
- if (DEBUG) Log.d(TAG, "new CastController()");
+ dumpManager.registerNormalDumpable(TAG, this);
}
- public void dump(PrintWriter pw, String[] args) {
+ @Override
+ public void dump(PrintWriter pw, @NonNull String[] args) {
pw.println("CastController state:");
pw.print(" mDiscovering="); pw.println(mDiscovering);
pw.print(" mCallbackRegistered="); pw.println(mCallbackRegistered);
@@ -88,7 +89,7 @@
pw.print(" mRoutes.size="); pw.println(mRoutes.size());
for (int i = 0; i < mRoutes.size(); i++) {
final RouteInfo route = mRoutes.valueAt(i);
- pw.print(" "); pw.println(routeToString(route));
+ pw.print(" "); pw.println(CastControllerLogger.Companion.toLogString(route));
}
pw.print(" mProjection="); pw.println(mProjection);
}
@@ -119,7 +120,7 @@
synchronized (mDiscoveringLock) {
if (mDiscovering == request) return;
mDiscovering = request;
- if (DEBUG) Log.d(TAG, "setDiscovering: " + request);
+ mLogger.logDiscovering(request);
handleDiscoveryChangeLocked();
}
}
@@ -166,7 +167,8 @@
CastDevice.Companion.toCastDevice(
mProjection,
mContext,
- mPackageManager));
+ mPackageManager,
+ mLogger));
}
}
@@ -177,22 +179,24 @@
public void startCasting(CastDevice device) {
if (device == null || device.getTag() == null) return;
final RouteInfo route = (RouteInfo) device.getTag();
- if (DEBUG) Log.d(TAG, "startCasting: " + routeToString(route));
+ mLogger.logStartCasting(route);
mMediaRouter.selectRoute(ROUTE_TYPE_REMOTE_DISPLAY, route);
}
@Override
public void stopCasting(CastDevice device) {
+ // TODO(b/332662551): Convert Logcat to LogBuffer.
final boolean isProjection = device.getTag() instanceof MediaProjectionInfo;
- if (DEBUG) Log.d(TAG, "stopCasting isProjection=" + isProjection);
+ mLogger.logStopCasting(isProjection);
if (isProjection) {
final MediaProjectionInfo projection = (MediaProjectionInfo) device.getTag();
if (Objects.equals(mProjectionManager.getActiveProjectionInfo(), projection)) {
mProjectionManager.stopActiveProjection();
} else {
- Log.w(TAG, "Projection is no longer active: " + projection);
+ mLogger.logStopCastingNoProjection(projection);
}
} else {
+ mLogger.logStopCastingMediaRouter();
mMediaRouter.getFallbackRoute().select();
}
}
@@ -217,7 +221,7 @@
}
}
if (changed) {
- if (DEBUG) Log.d(TAG, "setProjection: " + oldProjection + " -> " + mProjection);
+ mLogger.logSetProjection(oldProjection, mProjection);
fireOnCastDevicesChanged();
}
}
@@ -264,42 +268,30 @@
callback.onCastDevicesChanged();
}
- private static String routeToString(RouteInfo route) {
- if (route == null) return null;
- final StringBuilder sb = new StringBuilder().append(route.getName()).append('/')
- .append(route.getDescription()).append('@').append(route.getDeviceAddress())
- .append(",status=").append(route.getStatus());
- if (route.isDefault()) sb.append(",default");
- if (route.isEnabled()) sb.append(",enabled");
- if (route.isConnecting()) sb.append(",connecting");
- if (route.isSelected()) sb.append(",selected");
- return sb.append(",id=").append(route.getTag()).toString();
- }
-
private final MediaRouter.SimpleCallback mMediaCallback = new MediaRouter.SimpleCallback() {
@Override
public void onRouteAdded(MediaRouter router, RouteInfo route) {
- if (DEBUG) Log.d(TAG, "onRouteAdded: " + routeToString(route));
+ mLogger.logRouteAdded(route);
updateRemoteDisplays();
}
@Override
public void onRouteChanged(MediaRouter router, RouteInfo route) {
- if (DEBUG) Log.d(TAG, "onRouteChanged: " + routeToString(route));
+ mLogger.logRouteChanged(route);
updateRemoteDisplays();
}
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo route) {
- if (DEBUG) Log.d(TAG, "onRouteRemoved: " + routeToString(route));
+ mLogger.logRouteRemoved(route);
updateRemoteDisplays();
}
@Override
public void onRouteSelected(MediaRouter router, int type, RouteInfo route) {
- if (DEBUG) Log.d(TAG, "onRouteSelected(" + type + "): " + routeToString(route));
+ mLogger.logRouteSelected(route, type);
updateRemoteDisplays();
}
@Override
public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
- if (DEBUG) Log.d(TAG, "onRouteUnselected(" + type + "): " + routeToString(route));
+ mLogger.logRouteUnselected(route, type);
updateRemoteDisplays();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerLogger.kt
new file mode 100644
index 0000000..9a3a244
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerLogger.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.media.MediaRouter.RouteInfo
+import android.media.projection.MediaProjectionInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
+import com.android.systemui.statusbar.policy.dagger.CastControllerLog
+import javax.inject.Inject
+
+/** Helper class for logging events to [CastControllerLog] from Java. */
+@SysUISingleton
+class CastControllerLogger
+@Inject
+constructor(
+ @CastControllerLog val logger: LogBuffer,
+) {
+ /** Passthrough to [logger]. */
+ inline fun log(
+ tag: String,
+ level: LogLevel,
+ messageInitializer: MessageInitializer,
+ noinline messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
+ ) {
+ logger.log(tag, level, messageInitializer, messagePrinter, exception)
+ }
+
+ fun logDiscovering(isDiscovering: Boolean) =
+ logger.log(TAG, LogLevel.DEBUG, { bool1 = isDiscovering }, { "setDiscovering: $bool1" })
+
+ fun logStartCasting(route: RouteInfo) =
+ logger.log(TAG, LogLevel.DEBUG, { str1 = route.toLogString() }, { "startCasting: $str1" })
+
+ fun logStopCasting(isProjection: Boolean) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isProjection },
+ { "stopCasting. isProjection=$bool1" },
+ )
+
+ fun logStopCastingNoProjection(projection: MediaProjectionInfo) =
+ logger.log(
+ TAG,
+ LogLevel.WARNING,
+ { str1 = projection.toString() },
+ { "stopCasting failed because projection is no longer active: $str1" },
+ )
+
+ fun logStopCastingMediaRouter() =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {},
+ { "stopCasting is selecting fallback route in MediaRouter" },
+ )
+
+ fun logSetProjection(oldInfo: MediaProjectionInfo?, newInfo: MediaProjectionInfo?) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = oldInfo.toString()
+ str2 = newInfo.toString()
+ },
+ { "setProjection: $str1 -> $str2" },
+ )
+
+ fun logRouteAdded(route: RouteInfo) =
+ logger.log(TAG, LogLevel.DEBUG, { str1 = route.toLogString() }, { "onRouteAdded: $str1" })
+
+ fun logRouteChanged(route: RouteInfo) =
+ logger.log(TAG, LogLevel.DEBUG, { str1 = route.toLogString() }, { "onRouteChanged: $str1" })
+
+ fun logRouteRemoved(route: RouteInfo) =
+ logger.log(TAG, LogLevel.DEBUG, { str1 = route.toLogString() }, { "onRouteRemoved: $str1" })
+
+ fun logRouteSelected(route: RouteInfo, type: Int) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = route.toLogString()
+ int1 = type
+ },
+ { "onRouteSelected($int1): $str1" },
+ )
+
+ fun logRouteUnselected(route: RouteInfo, type: Int) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = route.toLogString()
+ int1 = type
+ },
+ { "onRouteUnselected($int1): $str1" },
+ )
+
+ companion object {
+ @JvmStatic
+ fun RouteInfo?.toLogString(): String? {
+ if (this == null) return null
+ val sb =
+ StringBuilder()
+ .append(name)
+ .append('/')
+ .append(description)
+ .append('@')
+ .append(deviceAddress)
+ .append(",status=")
+ .append(status)
+ if (isDefault) sb.append(",default")
+ if (isEnabled) sb.append(",enabled")
+ if (isConnecting) sb.append(",connecting")
+ if (isSelected) sb.append(",selected")
+ return sb.append(",id=").append(this.tag).toString()
+ }
+
+ private const val TAG = "CastController"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
index 5fc160b..a787f7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
@@ -20,7 +20,7 @@
import android.media.MediaRouter
import android.media.projection.MediaProjectionInfo
import android.text.TextUtils
-import android.util.Log
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
import com.android.systemui.util.Utils
@@ -38,6 +38,9 @@
) {
val isCasting = state == CastState.Connecting || state == CastState.Connected
+ val shortLogString: String =
+ "CastDevice(id=$id name=$name description=$description state=$state origin=$origin)"
+
companion object {
/** Creates a [CastDevice] based on the provided information from MediaRouter. */
fun MediaRouter.RouteInfo.toCastDevice(context: Context): CastDevice {
@@ -61,11 +64,12 @@
/** Creates a [CastDevice] based on the provided information from MediaProjection. */
fun MediaProjectionInfo.toCastDevice(
context: Context,
- packageManager: PackageManager
+ packageManager: PackageManager,
+ logger: CastControllerLogger,
): CastDevice {
return CastDevice(
id = this.packageName,
- name = getAppName(this.packageName, packageManager),
+ name = getAppName(this.packageName, packageManager, logger),
description = context.getString(R.string.quick_settings_casting),
state = CastState.Connected,
tag = this,
@@ -73,7 +77,11 @@
)
}
- private fun getAppName(packageName: String, packageManager: PackageManager): String {
+ private fun getAppName(
+ packageName: String,
+ packageManager: PackageManager,
+ logger: CastControllerLogger,
+ ): String {
if (Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)) {
return ""
}
@@ -83,9 +91,20 @@
if (!TextUtils.isEmpty(label)) {
return label.toString()
}
- Log.w(CastControllerImpl.TAG, "No label found for package: $packageName")
+ logger.log(
+ "#getAppName",
+ LogLevel.WARNING,
+ { str1 = packageName },
+ { "No label found for package: $str1" },
+ )
} catch (e: PackageManager.NameNotFoundException) {
- Log.w(CastControllerImpl.TAG, "Error getting appName for package: $packageName", e)
+ logger.log(
+ "#getAppName",
+ LogLevel.WARNING,
+ { str1 = packageName },
+ { "Error getting appName for package=$str1" },
+ e,
+ )
}
return packageName
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/CastControllerLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/CastControllerLog.kt
new file mode 100644
index 0000000..23aade6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/CastControllerLog.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * Logs for cast events. See [com.android.systemui.statusbar.policy.CastControllerImpl] and
+ * [com.android.systemui.statusbar.policy.CastControllerLogger].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CastControllerLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index e08e4d7..71bcdfcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -228,4 +228,12 @@
static LogBuffer provideBatteryControllerLog(LogBufferFactory factory) {
return factory.create(BatteryControllerLogger.TAG, 30);
}
+
+ /** Provides a log buffer for CastControllerImpl */
+ @Provides
+ @SysUISingleton
+ @CastControllerLog
+ static LogBuffer provideCastControllerLog(LogBufferFactory factory) {
+ return factory.create("CastControllerLog", 50);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 025354b..848a6e6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -16,12 +16,12 @@
package com.android.systemui.util.settings
import android.annotation.UserIdInt
+import android.annotation.WorkerThread
import android.content.ContentResolver
import android.database.ContentObserver
import android.net.Uri
import android.os.UserHandle
import android.provider.Settings.SettingNotFoundException
-import androidx.annotation.WorkerThread
import com.android.app.tracing.TraceUtils.trace
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat
@@ -67,6 +67,7 @@
} else userTracker.userId
}
+ @WorkerThread
override fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) {
registerContentObserverForUserSync(uri, settingsObserver, userId)
}
@@ -83,6 +84,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+ @WorkerThread
override fun registerContentObserverSync(
uri: Uri,
notifyForDescendants: Boolean,
@@ -120,6 +122,7 @@
*
* Implicitly calls [getUriFor] on the passed in name.
*/
+ @WorkerThread
fun registerContentObserverForUserSync(
name: String,
settingsObserver: ContentObserver,
@@ -160,6 +163,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
+ @WorkerThread
fun registerContentObserverForUserSync(
uri: Uri,
settingsObserver: ContentObserver,
@@ -222,6 +226,7 @@
*
* Implicitly calls [getUriFor] on the passed in name.
*/
+ @WorkerThread
fun registerContentObserverForUserSync(
name: String,
notifyForDescendants: Boolean,
@@ -281,6 +286,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
+ @WorkerThread
fun registerContentObserverForUserSync(
uri: Uri,
notifyForDescendants: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index de8b9b1..eb2f71a1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -73,10 +73,19 @@
@Provides
@SysUISingleton
fun provideAudioSharingRepository(
+ @Application context: Context,
+ contentResolver: ContentResolver,
localBluetoothManager: LocalBluetoothManager?,
+ @Application coroutineScope: CoroutineScope,
@Background coroutineContext: CoroutineContext,
): AudioSharingRepository =
- AudioSharingRepositoryImpl(localBluetoothManager, coroutineContext)
+ AudioSharingRepositoryImpl(
+ context,
+ contentResolver,
+ localBluetoothManager,
+ coroutineScope,
+ coroutineContext
+ )
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
new file mode 100644
index 0000000..2904092
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractorEmptyImpl
+import dagger.Module
+import dagger.Provides
+
+/** Dagger module for empty audio sharing impl for unnecessary volume overlay */
+@Module
+interface AudioSharingEmptyImplModule {
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun provideAudioSharingInteractor(): AudioSharingInteractor =
+ AudioSharingInteractorEmptyImpl()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
new file mode 100644
index 0000000..9f1e60e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dagger
+
+import com.android.settingslib.volume.data.repository.AudioSharingRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractorImpl
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineScope
+
+/** Dagger module for audio sharing code in the volume package */
+@Module
+interface AudioSharingModule {
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun provideAudioSharingInteractor(
+ @Application coroutineScope: CoroutineScope,
+ repository: AudioSharingRepository
+ ): AudioSharingInteractor = AudioSharingInteractorImpl(coroutineScope, repository)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 5420988..ebb9ce9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -59,6 +59,7 @@
@Module(
includes = {
AudioModule.class,
+ AudioSharingModule.class,
AncModule.class,
CaptioningModule.class,
MediaDevicesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
new file mode 100644
index 0000000..4d29788
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.interactor
+
+import android.bluetooth.BluetoothCsipSetCoordinator
+import androidx.annotation.IntRange
+import com.android.settingslib.volume.data.repository.AudioSharingRepository
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.launch
+
+interface AudioSharingInteractor {
+ /** Audio sharing secondary headset volume changes. */
+ val volume: Flow<Int?>
+
+ /** Audio sharing secondary headset min volume. */
+ val volumeMin: Int
+
+ /** Audio sharing secondary headset max volume. */
+ val volumeMax: Int
+
+ /** Set the volume of the secondary headset in audio sharing. */
+ fun setStreamVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ level: Int
+ )
+}
+
+@SysUISingleton
+class AudioSharingInteractorImpl
+@Inject
+constructor(
+ @Application private val coroutineScope: CoroutineScope,
+ private val audioSharingRepository: AudioSharingRepository
+) : AudioSharingInteractor {
+
+ override val volume: Flow<Int?> =
+ combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) {
+ secondaryGroupId,
+ volumeMap ->
+ if (secondaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) null
+ else volumeMap.getOrDefault(secondaryGroupId, DEFAULT_VOLUME)
+ }
+
+ override val volumeMin: Int = AUDIO_SHARING_VOLUME_MIN
+
+ override val volumeMax: Int = AUDIO_SHARING_VOLUME_MAX
+
+ override fun setStreamVolume(level: Int) {
+ coroutineScope.launch { audioSharingRepository.setSecondaryVolume(level) }
+ }
+
+ private companion object {
+ const val DEFAULT_VOLUME = 20
+ }
+}
+
+@SysUISingleton
+class AudioSharingInteractorEmptyImpl : AudioSharingInteractor {
+ override val volume: Flow<Int?> = emptyFlow()
+ override val volumeMin: Int = EMPTY_VOLUME
+ override val volumeMax: Int = EMPTY_VOLUME
+
+ override fun setStreamVolume(level: Int) {}
+
+ private companion object {
+ const val EMPTY_VOLUME = 0
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 3f1ec85..ec9b5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -20,9 +20,8 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -67,7 +66,6 @@
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
@@ -251,25 +249,7 @@
pip.showPictureInPictureMenu();
}
});
- pip.registerPipTransitionCallback(
- new PipTransitionController.PipTransitionCallback() {
- @Override
- public void onPipTransitionStarted(int direction, Rect pipBounds) {
- mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, true)
- .commitUpdate(mDisplayTracker.getDefaultDisplayId());
- }
- @Override
- public void onPipTransitionFinished(int direction) {
- mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false)
- .commitUpdate(mDisplayTracker.getDefaultDisplayId());
- }
-
- @Override
- public void onPipTransitionCanceled(int direction) {
- // No op.
- }
- }, mSysUiMainExecutor);
mSysUiState.addCallback(sysUiStateFlag -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
new file mode 100644
index 0000000..baef620
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.hardware.biometrics.BiometricFaceConstants
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class FaceHelpMessageDebouncerTest : SysuiTestCase() {
+ private lateinit var underTest: FaceHelpMessageDebouncer
+ private val window = 9L
+ private val startWindow = 4L
+ private val shownFaceMessageFrequencyBoost = 2
+
+ @Before
+ fun setUp() {
+ underTest =
+ FaceHelpMessageDebouncer(
+ window = window,
+ startWindow = startWindow,
+ shownFaceMessageFrequencyBoost = shownFaceMessageFrequencyBoost,
+ )
+ }
+
+ @Test
+ fun getMessageBeforeStartWindow_null() {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "testTooClose",
+ 0
+ )
+ )
+ assertThat(underTest.getMessageToShow(0)).isNull()
+ }
+
+ @Test
+ fun getMessageAfterStartWindow() {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+
+ assertThat(underTest.getMessageToShow(startWindow)?.msgId)
+ .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
+ assertThat(underTest.getMessageToShow(startWindow)?.msg).isEqualTo("tooClose")
+ }
+
+ @Test
+ fun getMessageAfterMessagesCleared_null() {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+ underTest.startNewFaceAuthSession(0)
+
+ assertThat(underTest.getMessageToShow(startWindow)).isNull()
+ }
+
+ @Test
+ fun messagesBeforeWindowRemoved() {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ window - 1
+ )
+ )
+ val lastMessage =
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+ "tooBright",
+ window
+ )
+ underTest.addMessage(lastMessage)
+
+ assertThat(underTest.getMessageToShow(window + 1)).isEqualTo(lastMessage)
+ }
+
+ @Test
+ fun getMessageTieGoesToMostRecent() {
+ for (i in 1..window step 2) {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ i
+ )
+ )
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+ "tooBright",
+ i + 1
+ )
+ )
+ }
+
+ assertThat(underTest.getMessageToShow(window)?.msgId)
+ .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT)
+ assertThat(underTest.getMessageToShow(window)?.msg).isEqualTo("tooBright")
+ }
+
+ @Test
+ fun boostCurrentlyShowingMessage() {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+ "tooBright",
+ 0
+ )
+ )
+
+ val lastMessageShown = underTest.getMessageToShow(startWindow)
+ assertThat(lastMessageShown?.msgId)
+ .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT)
+
+ for (i in 1..<shownFaceMessageFrequencyBoost) {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ startWindow
+ )
+ )
+ }
+
+ // although technically there's a different msgId with a higher frequency count now, the
+ // shownFaceMessageFrequencyBoost causes the last message shown to get a "boost"
+ // to keep showing
+ assertThat(underTest.getMessageToShow(startWindow)).isEqualTo(lastMessageShown)
+ }
+
+ @Test
+ fun overcomeBoostedCurrentlyShowingMessage() {
+ // Comments are assuming shownFaceMessageFrequencyBoost = 2
+ // [B], weights: B=1
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+ "tooBright",
+ 0
+ )
+ )
+
+ // [B], showing messageB, weights: B=3
+ val messageB = underTest.getMessageToShow(startWindow)
+
+ // [B, C, C], showing messageB, weights: B=3, C=2
+ for (i in 1..shownFaceMessageFrequencyBoost) {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ startWindow
+ )
+ )
+ }
+ // messageB is getting boosted to continue to show
+ assertThat(underTest.getMessageToShow(startWindow)).isEqualTo(messageB)
+
+ // receive one more FACE_ACQUIRED_TOO_CLOSE acquired info to pass the boost
+ // [C, C, C], showing messageB, weights: B=2, C=3
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ startWindow
+ )
+ )
+
+ // Now FACE_ACQUIRED_TOO_CLOSE has surpassed the boosted messageB frequency
+ // [C, C, C], showing messageC, weights: C=5
+ assertThat(underTest.getMessageToShow(startWindow)?.msgId)
+ .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
index 431fef6..6fd8660 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
@@ -24,6 +24,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.biometrics.domain.faceHelpMessageDeferral
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -45,6 +46,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -264,11 +266,20 @@
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- // WHEN authentication status help
+ // WHEN authentication status help past debouncer
faceAuthRepository.setAuthenticationStatus(
HelpFaceAuthenticationStatus(
msg = "Move left",
msgId = FACE_ACQUIRED_TOO_RIGHT,
+ createdAt = 0L,
+ )
+ )
+ runCurrent()
+ faceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(
+ msg = "Move left",
+ msgId = FACE_ACQUIRED_TOO_RIGHT,
+ createdAt = FaceHelpMessageDebouncer.DEFAULT_WINDOW_MS,
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 529cd6e..0b7a3ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -90,6 +90,7 @@
private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
private val faceWakeUpTriggersConfig = kosmos.fakeFaceWakeUpTriggersConfig
private val trustManager = kosmos.trustManager
+ private val deviceEntryFaceAuthStatusInteractor = kosmos.deviceEntryFaceAuthStatusInteractor
@Before
fun setup() {
@@ -112,6 +113,7 @@
powerInteractor,
fakeBiometricSettingsRepository,
trustManager,
+ deviceEntryFaceAuthStatusInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt
new file mode 100644
index 0000000..6022d9c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.face.FaceManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer.Companion.DEFAULT_WINDOW_MS
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryFaceAuthStatusInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope: TestScope = kosmos.testScope
+ private lateinit var underTest: DeviceEntryFaceAuthStatusInteractor
+ private val ignoreHelpMessageId = 1
+
+ @Before
+ fun setup() {
+ overrideResource(
+ R.array.config_face_acquire_device_entry_ignorelist,
+ intArrayOf(ignoreHelpMessageId)
+ )
+ underTest = kosmos.deviceEntryFaceAuthStatusInteractor
+ }
+
+ @Test
+ fun successAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ val successStatus =
+ SuccessFaceAuthenticationStatus(
+ successResult = mock(FaceManager.AuthenticationResult::class.java)
+ )
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(successStatus)
+ assertThat(authenticationStatus).isEqualTo(successStatus)
+ }
+
+ @Test
+ fun acquiredFaceAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ val acquiredStatus = AcquiredFaceAuthenticationStatus(acquiredInfo = 0)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(acquiredStatus)
+ assertThat(authenticationStatus).isEqualTo(acquiredStatus)
+ }
+
+ @Test
+ fun failedFaceAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ val failedStatus = FailedFaceAuthenticationStatus()
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(failedStatus)
+ assertThat(authenticationStatus).isEqualTo(failedStatus)
+ }
+
+ @Test
+ fun errorFaceAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ val errorStatus = ErrorFaceAuthenticationStatus(0, "test")
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(errorStatus)
+ assertThat(authenticationStatus).isEqualTo(errorStatus)
+ }
+
+ @Test
+ fun firstHelpFaceAuthenticationStatus_noUpdate() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ AcquiredFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_START,
+ createdAt = 0
+ )
+ )
+ val helpMessage = HelpFaceAuthenticationStatus(0, "test", 1)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(helpMessage)
+ assertThat(authenticationStatus).isNull()
+ }
+
+ @Test
+ fun helpFaceAuthenticationStatus_afterWindow() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(0, "test1", 0)
+ )
+ runCurrent()
+ val helpMessage = HelpFaceAuthenticationStatus(0, "test2", DEFAULT_WINDOW_MS)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(helpMessage)
+ runCurrent()
+ assertThat(authenticationStatus).isEqualTo(helpMessage)
+ }
+
+ @Test
+ fun helpFaceAuthenticationStatus_onlyIgnoredHelpMessages_afterWindow() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(ignoreHelpMessageId, "ignoredMsg", 0)
+ )
+ runCurrent()
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(ignoreHelpMessageId, "ignoredMsg", DEFAULT_WINDOW_MS)
+ )
+ runCurrent()
+ assertThat(authenticationStatus).isNull()
+ }
+
+ @Test
+ fun helpFaceAuthenticationStatus_afterWindow_onIgnoredMessage_showsOtherMessageInstead() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ val validHelpMessage = HelpFaceAuthenticationStatus(0, "validHelpMsg", 0)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(validHelpMessage)
+ runCurrent()
+ // help message that should be ignored
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(ignoreHelpMessageId, "ignoredMsg", DEFAULT_WINDOW_MS)
+ )
+ runCurrent()
+ assertThat(authenticationStatus).isEqualTo(validHelpMessage)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index e2cca38..ae635b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -41,7 +41,6 @@
import android.os.Handler;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.dreams.IDreamManager;
import android.testing.TestableLooper;
import android.view.GestureDetector;
import android.view.IWindowManager;
@@ -106,7 +105,6 @@
@Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs;
@Mock private AudioManager mAudioManager;
- @Mock private IDreamManager mDreamManager;
@Mock private DevicePolicyManager mDevicePolicyManager;
@Mock private LockPatternUtils mLockPatternUtils;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@@ -165,7 +163,6 @@
mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
mWindowManagerFuncs,
mAudioManager,
- mDreamManager,
mDevicePolicyManager,
mLockPatternUtils,
mBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 4fba7e3..c9c39b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -189,6 +189,15 @@
listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3)
)
+ private val standardPackageName1 = "standard.app.group1"
+
+ private val standardAppGroup1 =
+ KeyboardShortcutGroup(
+ "Standard app group 1",
+ listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3)
+ )
+ .apply { packageName = standardPackageName1 }
+
private val standardSubCategory1 =
ShortcutSubCategory(
standardGroup1.label!!.toString(),
@@ -230,6 +239,9 @@
)
)
+ val currentAppGroups = listOf(standardAppGroup1)
+ val currentAppPackageName = standardPackageName1
+
val systemGroups = listOf(standardGroup3, standardGroup2, standardGroup1)
val systemCategory =
ShortcutCategory(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 07feaa1..69fc463 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
@@ -52,6 +53,7 @@
private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeCurrentAppsSource = FakeKeyboardShortcutGroupsSource()
private val kosmos =
Kosmos().also {
@@ -61,7 +63,7 @@
it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
- it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource
}
private val testScope = kosmos.testScope
@@ -216,4 +218,17 @@
assertThat(activeUiState.defaultSelectedCategory)
.isEqualTo(activeUiState.shortcutCategories.first().type)
}
+
+ @Test
+ fun shortcutsUiState_featureActive_emitsActiveWithCurrentAppsCategorySelectedWhenPresent() =
+ testScope.runTest {
+ fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.defaultSelectedCategory)
+ .isEqualTo(CurrentApp(TestShortcuts.currentAppPackageName))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 03afcb7..e68a4a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -77,6 +77,7 @@
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardSecurityView;
@@ -101,6 +102,7 @@
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.scene.FakeWindowRootViewComponent;
import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.settings.UserTracker;
@@ -188,6 +190,7 @@
private @Mock ActivityTransitionAnimator mActivityTransitionAnimator;
private @Mock ScrimController mScrimController;
private @Mock IActivityTaskManager mActivityTaskManagerService;
+ private @Mock IStatusBarService mStatusBarService;
private @Mock SysuiColorExtractor mColorExtractor;
private @Mock AuthController mAuthController;
private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -211,6 +214,7 @@
private @Mock SystemSettings mSystemSettings;
private @Mock SecureSettings mSecureSettings;
private @Mock AlarmManager mAlarmManager;
+ private @Mock ProcessWrapper mProcessWrapper;
private FakeSystemClock mSystemClock;
private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
@@ -247,6 +251,7 @@
.thenReturn(mock(Flow.class));
when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
+ when(mProcessWrapper.isSystemUser()).thenReturn(true);
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
new FakeWindowRootViewComponent.Factory(mock(WindowRootView.class)),
@@ -1225,10 +1230,12 @@
() -> mActivityTransitionAnimator,
() -> mScrimController,
mActivityTaskManagerService,
+ mStatusBarService,
mFeatureFlags,
mSecureSettings,
mSystemSettings,
mSystemClock,
+ mProcessWrapper,
mDispatcher,
() -> mDreamViewModel,
() -> mCommunalTransitionViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index fbeb6d8..732bef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -44,6 +44,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
@@ -68,6 +69,8 @@
private KeyguardStateController mKeyguardStateController;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private ProcessWrapper mProcessWrapper;
@Captor
ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -86,13 +89,15 @@
@Before
public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ when(mProcessWrapper.isSystemUser()).thenReturn(true);
mSessionTracker = new SessionTracker(
mStatusBarService,
mAuthController,
mKeyguardUpdateMonitor,
mKeyguardStateController,
- mUiEventLogger
+ mUiEventLogger,
+ mProcessWrapper
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
index 5db8981..785d5a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
@@ -273,6 +274,7 @@
applicationScope = kosmos.applicationCoroutineScope,
backgroundDispatcher = kosmos.testDispatcher,
mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
+ logger = logcatLogBuffer("TestMediaProjection"),
)
val state by collectLastValue(repoWithTimingControl.mediaProjectionState)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index b169cc1..b4db6da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -27,6 +27,7 @@
import android.view.WindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.jank.Cuj
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
@@ -63,7 +64,7 @@
private var triggerThreshold: Float = 0.0f
private val touchSlop = ViewConfiguration.get(context).scaledEdgeSlop
@Mock private lateinit var vibratorHelper: VibratorHelper
- @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var latencyTracker: LatencyTracker
private val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
@@ -78,7 +79,7 @@
mBackPanelController =
BackPanelController(
context,
- windowManager,
+ viewCaptureAwareWindowManager,
ViewConfiguration.get(context),
Handler.createAsync(testableLooper.looper),
systemClock,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 98ff6c9..45d77f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -29,6 +29,8 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
import static com.android.systemui.assist.AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
import static com.android.systemui.navigationbar.views.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +40,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -70,6 +73,7 @@
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -162,6 +166,8 @@
@Mock
ButtonDispatcher mImeSwitchButton;
@Mock
+ KeyButtonView mImeSwitchButtonView;
+ @Mock
ButtonDispatcher mBackButton;
@Mock
NavigationBarTransitions mNavigationBarTransitions;
@@ -433,6 +439,45 @@
}
@Test
+ public void testImeSwitcherClick() {
+ mNavigationBar.init();
+ mNavigationBar.onViewAttached();
+ mNavigationBar.onImeSwitcherClick(mImeSwitchButtonView);
+
+ verify(mUiEventLogger).log(NAVBAR_IME_SWITCHER_BUTTON_TAP);
+ verify(mUiEventLogger, never()).log(NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ if (Flags.imeSwitcherRevamp()) {
+ verify(mInputMethodManager)
+ .onImeSwitchButtonClickFromSystem(mNavigationBar.mDisplayId);
+ verify(mInputMethodManager, never()).showInputMethodPickerFromSystem(
+ anyBoolean() /* showAuxiliarySubtypes */, anyInt() /* displayId */);
+ } else {
+ verify(mInputMethodManager, never())
+ .onImeSwitchButtonClickFromSystem(anyInt() /* displayId */);
+ verify(mInputMethodManager).showInputMethodPickerFromSystem(
+ true /* showAuxiliarySubtypes */, mNavigationBar.mDisplayId);
+ }
+ }
+
+ @Test
+ public void testImeSwitcherLongClick() {
+ mNavigationBar.init();
+ mNavigationBar.onViewAttached();
+ mNavigationBar.onImeSwitcherLongClick(mImeSwitchButtonView);
+
+ verify(mUiEventLogger, never()).log(NAVBAR_IME_SWITCHER_BUTTON_TAP);
+ if (Flags.imeSwitcherRevamp()) {
+ verify(mUiEventLogger).log(NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ verify(mInputMethodManager).showInputMethodPickerFromSystem(
+ true /* showAuxiliarySubtypes */, mNavigationBar.mDisplayId);
+ } else {
+ verify(mUiEventLogger, never()).log(NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ verify(mInputMethodManager, never()).showInputMethodPickerFromSystem(
+ anyBoolean() /* showAuxiliarySubtypes */, anyInt() /* displayId */);
+ }
+ }
+
+ @Test
public void testRegisteredWithUserTracker() {
mNavigationBar.init();
mNavigationBar.onViewAttached();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 5273495..eea02ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -61,6 +61,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.wifi.WifiUtils;
@@ -160,7 +161,7 @@
@Mock
InternetDialogController.InternetDialogCallback mInternetDialogCallback;
@Mock
- private WindowManager mWindowManager;
+ private ViewCaptureAwareWindowManager mWindowManager;
@Mock
private ToastFactory mToastFactory;
@Mock
@@ -232,8 +233,9 @@
mSubscriptionManager, mTelephonyManager, mWifiManager,
mConnectivityManager, mHandler, mExecutor, mBroadcastDispatcher,
mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
- mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker,
- mLocationController, mDialogTransitionAnimator, mWifiStateWorker, mFlags);
+ mWindowManager, mToastFactory, mWorkerHandler,
+ mCarrierConfigTracker, mLocationController, mDialogTransitionAnimator,
+ mWifiStateWorker, mFlags);
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mInternetDialogController.mOnSubscriptionsChangedListener);
mInternetDialogController.onStart(mInternetDialogCallback, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 2444af7..477c50b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -18,6 +18,8 @@
import static android.os.Process.myUid;
+import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
@@ -69,10 +71,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-/**
- * Tests for exception handling and bitmap configuration in adding smart actions to Screenshot
- * Notification.
- */
public class RecordingControllerTest extends SysuiTestCase {
private static final int TEST_USER_ID = 12345;
@@ -146,6 +144,7 @@
mFeatureFlags,
() -> mDevicePolicyResolver,
mUserTracker,
+ new RecordingControllerLogger(logcatLogBuffer("RecordingControllerTest")),
mMediaProjectionMetricsLogger,
mScreenCaptureDisabledDialogDelegate,
mScreenRecordDialogFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 2f52248..150f53d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProviderPlugin
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.PluginLifecycleManager
@@ -74,6 +75,7 @@
private lateinit var fakeDefaultProvider: FakeClockPlugin
private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
private lateinit var registry: ClockRegistry
+ private lateinit var pickerConfig: ClockPickerConfig
private val featureFlags = FakeFeatureFlags()
companion object {
@@ -82,9 +84,9 @@
return null!!
}
- private fun failThumbnail(clockId: ClockId): Drawable? {
- fail("Unexpected call to getThumbnail: $clockId")
- return null
+ private fun failPickerConfig(clockId: ClockId): ClockPickerConfig {
+ fail("Unexpected call to getClockPickerConfig: $clockId")
+ return null!!
}
}
@@ -123,22 +125,31 @@
private class FakeClockPlugin : ClockProviderPlugin {
private val metadata = mutableListOf<ClockMetadata>()
private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>()
- private val thumbnailCallbacks = mutableMapOf<ClockId, (ClockId) -> Drawable?>()
+ private val pickerConfigs = mutableMapOf<ClockId, (ClockId) -> ClockPickerConfig>()
override fun getClocks() = metadata
- override fun createClock(settings: ClockSettings): ClockController =
- createCallbacks[settings.clockId!!]!!(settings.clockId!!)
- override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id)
+
+ override fun createClock(settings: ClockSettings): ClockController {
+ val clockId = settings.clockId ?: throw IllegalArgumentException("No clockId specified")
+ return createCallbacks[clockId]?.invoke(clockId)
+ ?: throw NotImplementedError("No callback for '$clockId'")
+ }
+
+ override fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig {
+ return pickerConfigs[clockId]?.invoke(clockId)
+ ?: throw NotImplementedError("No picker config for '$clockId'")
+ }
+
override fun initialize(buffers: ClockMessageBuffers?) { }
fun addClock(
id: ClockId,
create: (ClockId) -> ClockController = ::failFactory,
- getThumbnail: (ClockId) -> Drawable? = ::failThumbnail
+ getPickerConfig: (ClockId) -> ClockPickerConfig = ::failPickerConfig
): FakeClockPlugin {
metadata.add(ClockMetadata(id))
createCallbacks[id] = create
- thumbnailCallbacks[id] = getThumbnail
+ pickerConfigs[id] = getPickerConfig
return this
}
}
@@ -148,9 +159,10 @@
scheduler = TestCoroutineScheduler()
dispatcher = StandardTestDispatcher(scheduler)
scope = TestScope(dispatcher)
+ pickerConfig = ClockPickerConfig("CLOCK_ID", "NAME", "DESC", mockThumbnail)
fakeDefaultProvider = FakeClockPlugin()
- .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { mockThumbnail })
+ .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { pickerConfig })
whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
@@ -215,8 +227,8 @@
@Test
fun clockIdConflict_ErrorWithoutCrash_unloadDuplicate() {
val plugin1 = FakeClockPlugin()
- .addClock("clock_1", { mockClock }, { mockThumbnail })
- .addClock("clock_2", { mockClock }, { mockThumbnail })
+ .addClock("clock_1", { mockClock }, { pickerConfig })
+ .addClock("clock_2", { mockClock }, { pickerConfig })
val lifecycle1 = spy(FakeLifecycle("1", plugin1))
val plugin2 = FakeClockPlugin()
@@ -238,8 +250,8 @@
assertEquals(registry.createExampleClock("clock_1"), mockClock)
assertEquals(registry.createExampleClock("clock_2"), mockClock)
- assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
- assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
+ assertEquals(registry.getClockPickerConfig("clock_1"), pickerConfig)
+ assertEquals(registry.getClockPickerConfig("clock_2"), pickerConfig)
verify(lifecycle1, never()).unloadPlugin()
verify(lifecycle2, times(2)).unloadPlugin()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 2522ed7..bbe03f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -72,6 +72,10 @@
.thenReturn(mockSmallClockView)
whenever(layoutInflater.inflate(eq(R.layout.clock_default_large), any(), anyBoolean()))
.thenReturn(mockLargeClockView)
+ whenever(resources.getString(R.string.clock_default_name))
+ .thenReturn("DEFAULT_CLOCK_NAME")
+ whenever(resources.getString(R.string.clock_default_description))
+ .thenReturn("DEFAULT_CLOCK_DESC")
whenever(resources.getDrawable(R.drawable.clock_default_thumbnail, null))
.thenReturn(mockClockThumbnail)
whenever(mockSmallClockView.getLayoutParams()).thenReturn(FrameLayout.LayoutParams(10, 10))
@@ -85,7 +89,7 @@
// All providers need to provide clocks & thumbnails for exposed clocks
for (metadata in provider.getClocks()) {
assertNotNull(provider.createClock(metadata.clockId))
- assertNotNull(provider.getClockThumbnail(metadata.clockId))
+ assertNotNull(provider.getClockPickerConfig(metadata.clockId))
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index 2e0c773..ca043f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
@@ -19,6 +19,7 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
import com.android.systemui.statusbar.phone.SystemUIDialog
import kotlin.test.Test
@@ -32,7 +33,12 @@
@Test
fun createDialogLaunchOnClickListener_showsDialogOnClick() {
- val clickListener = createDialogLaunchOnClickListener(dialogDelegate)
+ val clickListener =
+ createDialogLaunchOnClickListener(
+ dialogDelegate,
+ logcatLogBuffer("OngoingActivityChipViewModelTest"),
+ "tag",
+ )
// Dialogs must be created on the main thread
context.mainExecutor.execute {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 10d07a0..5052a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -35,8 +35,8 @@
import android.os.PowerManager;
import android.provider.Settings;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
@@ -53,6 +53,7 @@
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Assert;
import org.junit.Before;
@@ -114,6 +115,7 @@
.thenReturn(mFoldAodAnimationController);
when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+ SecureSettings secureSettings = new FakeSettings();
mDozeParameters = new DozeParameters(
mContext,
mHandler,
@@ -132,7 +134,7 @@
mStatusBarStateController,
mUserTracker,
mDozeInteractor,
- new FakeSettings()
+ secureSettings
);
verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
index 59b20c8..627463b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
@@ -1,6 +1,8 @@
package com.android.systemui.statusbar.policy;
+import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
+
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
@@ -58,7 +60,8 @@
mController = new CastControllerImpl(
mContext,
mock(PackageManager.class),
- mock(DumpManager.class));
+ mock(DumpManager.class),
+ new CastControllerLogger(logcatLogBuffer("CastControllerImplTest")));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
index 03ad66c..16061df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
@@ -25,6 +25,7 @@
import android.media.projection.MediaProjectionInfo
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.CastDevice.Companion.toCastDevice
import com.google.common.truth.Truth.assertThat
@@ -40,6 +41,7 @@
class CastDeviceTest : SysuiTestCase() {
private val mockAppInfo =
mock<ApplicationInfo>().apply { whenever(this.loadLabel(any())).thenReturn("") }
+ private val logger = CastControllerLogger(logcatLogBuffer("CastDeviceTest"))
private val packageManager =
mock<PackageManager>().apply {
@@ -322,7 +324,7 @@
whenever(this.packageName).thenReturn("fake.package")
}
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.id).isEqualTo("fake.package")
}
@@ -334,7 +336,7 @@
whenever(this.packageName).thenReturn(HEADLESS_REMOTE_PACKAGE)
}
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.name).isEmpty()
}
@@ -349,7 +351,7 @@
whenever(packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>()))
.thenThrow(PackageManager.NameNotFoundException())
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.name).isEqualTo(NORMAL_PACKAGE)
}
@@ -366,7 +368,7 @@
whenever(packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>()))
.thenReturn(appInfo)
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.name).isEqualTo(NORMAL_PACKAGE)
}
@@ -383,7 +385,7 @@
whenever(packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>()))
.thenReturn(appInfo)
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.name).isEqualTo("Valid App Name")
}
@@ -392,7 +394,7 @@
fun projectionToCastDevice_descriptionIsCasting() {
val projection = mockProjectionInfo()
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.description).isEqualTo(context.getString(R.string.quick_settings_casting))
}
@@ -401,7 +403,7 @@
fun projectionToCastDevice_stateIsConnected() {
val projection = mockProjectionInfo()
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.state).isEqualTo(CastDevice.CastState.Connected)
}
@@ -410,7 +412,7 @@
fun projectionToCastDevice_tagIsProjection() {
val projection = mockProjectionInfo()
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.tag).isEqualTo(projection)
}
@@ -419,7 +421,7 @@
fun projectionToCastDevice_originIsMediaProjection() {
val projection = mockProjectionInfo()
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.origin).isEqualTo(CastDevice.CastOrigin.MediaProjection)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
index 4b64416..de5f0f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
@@ -24,7 +24,7 @@
import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
@@ -44,7 +44,7 @@
clock = systemClock,
biometricMessageInteractor = biometricMessageInteractor,
faceAuthInteractor = deviceEntryFaceAuthInteractor,
- deviceEntryInteractor = deviceEntryInteractor,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
fingerprintInteractor = deviceEntryFingerprintAuthInteractor,
flags = composeBouncerFlags,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 25e7729..045bd5d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -30,16 +30,10 @@
private val _isBypassEnabled = MutableStateFlow(false)
override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
- var userPresentCount = 0
-
override suspend fun isLockscreenEnabled(): Boolean {
return isLockscreenEnabled
}
- override suspend fun reportUserPresent() {
- userPresentCount++
- }
-
fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
this.isLockscreenEnabled = isLockscreenEnabled
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index a8fc27a..b9be04d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -57,5 +57,6 @@
powerInteractor = powerInteractor,
biometricSettingsRepository = biometricSettingsRepository,
trustManager = trustManager,
+ deviceEntryFaceAuthStatusInteractor = deviceEntryFaceAuthStatusInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt
new file mode 100644
index 0000000..66d3709
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import android.content.res.mainResources
+import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryFaceAuthStatusInteractor by
+ Kosmos.Fixture {
+ DeviceEntryFaceAuthStatusInteractor(
+ repository = deviceEntryFaceAuthRepository,
+ resources = mainResources,
+ applicationScope = applicationCoroutineScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 1200866..caa6e99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -19,8 +19,6 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
-import com.android.systemui.flags.fakeSystemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,12 +32,7 @@
repository = deviceEntryRepository,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- faceAuthInteractor = deviceEntryFaceAuthInteractor,
- fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
- biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
- trustInteractor = trustInteractor,
deviceUnlockedInteractor = deviceUnlockedInteractor,
- systemPropertiesHelper = fakeSystemPropertiesHelper,
alternateBouncerInteractor = alternateBouncerInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index 14210bc..1ed10fbe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -18,6 +18,8 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.flags.systemPropertiesHelper
import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -33,5 +35,7 @@
faceAuthInteractor = deviceEntryFaceAuthInteractor,
fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
powerInteractor = powerInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ systemPropertiesHelper = fakeSystemPropertiesHelper,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
new file mode 100644
index 0000000..f73f43d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import java.time.Instant
+
+var Kosmos.contextualEducationRepository: ContextualEducationRepository by
+ Kosmos.Fixture { FakeContextualEducationRepository(FakeEduClock(Instant.MIN)) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
new file mode 100644
index 0000000..5410882
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.data.repository
+
+import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.shared.education.GestureType
+import java.time.Clock
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeContextualEducationRepository(private val clock: Clock) : ContextualEducationRepository {
+
+ private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
+ private val _gestureEduModels = MutableStateFlow(GestureEduModel())
+ private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+
+ override fun setUser(userId: Int) {
+ if (!userGestureMap.contains(userId)) {
+ userGestureMap[userId] = GestureEduModel()
+ }
+ _gestureEduModels.value = userGestureMap[userId]!!
+ }
+
+ override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> {
+ return gestureEduModelsFlow
+ }
+
+ override suspend fun incrementSignalCount(gestureType: GestureType) {
+ _gestureEduModels.value =
+ GestureEduModel(
+ signalCount = _gestureEduModels.value.signalCount + 1,
+ )
+ }
+
+ override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
+ _gestureEduModels.value = GestureEduModel(lastShortcutTriggeredTime = clock.instant())
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
new file mode 100644
index 0000000..513c143
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.data.repository
+
+import java.time.Clock
+import java.time.Instant
+import java.time.ZoneId
+
+class FakeEduClock(private val base: Instant) : Clock() {
+ private val zone: ZoneId = ZoneId.of("UTC")
+
+ override fun instant(): Instant {
+ return base
+ }
+
+ override fun withZone(zoneId: ZoneId?): Clock {
+ return FakeEduClock(base)
+ }
+
+ override fun getZone(): ZoneId {
+ return zone
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
index 3e09b23..6ca5cd8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
@@ -41,6 +41,7 @@
}
private var imeShortcuts: List<KeyboardShortcutGroup> = emptyList()
+ private var currentAppsShortcuts: List<KeyboardShortcutGroup> = emptyList()
init {
whenever(windowManager.requestImeKeyboardShortcuts(any(), any())).thenAnswer {
@@ -48,6 +49,11 @@
keyboardShortcutReceiver.onKeyboardShortcutsReceived(imeShortcuts)
return@thenAnswer Unit
}
+ whenever(windowManager.requestAppKeyboardShortcuts(any(), any())).thenAnswer {
+ val keyboardShortcutReceiver = it.getArgument<KeyboardShortcutsReceiver>(0)
+ keyboardShortcutReceiver.onKeyboardShortcutsReceived(currentAppsShortcuts)
+ return@thenAnswer Unit
+ }
repo.start()
}
@@ -59,6 +65,14 @@
this.imeShortcuts = imeShortcuts
}
+ /**
+ * Use this method to set what current app shortcuts should be returned from windowManager in
+ * tests. By default [WindowManager.requestAppKeyboardShortcuts] will return emptyList.
+ */
+ fun setCurrentAppsShortcuts(currentAppShortcuts: List<KeyboardShortcutGroup>) {
+ this.currentAppsShortcuts = currentAppShortcuts
+ }
+
fun hideThroughCloseSystemDialogs() {
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..b5f0b89
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+var Kosmos.dreamingToAodTransitionViewModel by Fixture {
+ DreamingToAodTransitionViewModel(
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 2567ffe..3c5baa5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -46,6 +46,7 @@
dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+ dreamingToAodTransitionViewModel = dreamingToAodTransitionViewModel,
dreamingToGoneTransitionViewModel = dreamingToGoneTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt
index 81ba77a..0412274 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.mediaprojection.taskswitcher.activityTaskManagerTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
@@ -37,5 +38,6 @@
tasksRepository = activityTaskManagerTasksRepository,
backgroundDispatcher = testDispatcher,
mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
+ logger = logcatLogBuffer("TestMediaProjection"),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
index eec9920..e1ecc51 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.policy.fakeCastController
val Kosmos.realMediaRouterRepository by
@@ -25,6 +26,7 @@
MediaRouterRepositoryImpl(
scope = applicationCoroutineScope,
castController = fakeCastController,
+ logger = logcatLogBuffer("MediaRouter"),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
index ab71b5e..1e304d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.chips.call.domain.interactor.callChipInteractor
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.callChipViewModel: CallChipViewModel by
@@ -29,5 +30,6 @@
interactor = callChipInteractor,
systemClock = fakeSystemClock,
activityStarter = activityStarter,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
index cb18b68..1737bc4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
@@ -19,11 +19,13 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.mediarouter.data.repository.fakeMediaRouterRepository
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
val Kosmos.mediaRouterChipInteractor by
Kosmos.Fixture {
MediaRouterChipInteractor(
scope = applicationCoroutineScope,
mediaRouterRepository = fakeMediaRouterRepository,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
index 2335f21..3d85a4a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel by
@@ -33,5 +34,6 @@
mediaRouterChipInteractor = mediaRouterChipInteractor,
systemClock = fakeSystemClock,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
index 2773f82..e4bb166 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.screenRecordChipInteractor
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.screenRecordChipViewModel: ScreenRecordChipViewModel by
@@ -31,5 +32,6 @@
interactor = screenRecordChipInteractor,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
systemClock = fakeSystemClock,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
index 1b3108c..8ed7f96 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.shareToAppChipViewModel: ShareToAppChipViewModel by
@@ -31,5 +32,6 @@
mediaProjectionChipInteractor = mediaProjectionChipInteractor,
systemClock = fakeSystemClock,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
index 327e1b5..d391750 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
@@ -16,16 +16,40 @@
package com.android.systemui.volume.data.repository
+import androidx.annotation.IntRange
import com.android.settingslib.volume.data.repository.AudioSharingRepository
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
+import com.android.settingslib.volume.data.repository.GroupIdToVolumes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
class FakeAudioSharingRepository : AudioSharingRepository {
private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ private val mutableSecondaryGroupId: MutableStateFlow<Int> =
+ MutableStateFlow(TEST_GROUP_ID_INVALID)
+ private val mutableVolumeMap: MutableStateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
override val inAudioSharing: Flow<Boolean> = mutableInAudioSharing
+ override val secondaryGroupId: StateFlow<Int> = mutableSecondaryGroupId
+ override val volumeMap: StateFlow<GroupIdToVolumes> = mutableVolumeMap
+
+ override suspend fun setSecondaryVolume(volume: Int) {}
fun setInAudioSharing(state: Boolean) {
mutableInAudioSharing.value = state
}
+
+ fun setSecondaryGroupId(groupId: Int) {
+ mutableSecondaryGroupId.value = groupId
+ }
+
+ fun setVolumeMap(volumeMap: GroupIdToVolumes) {
+ mutableVolumeMap.value = volumeMap
+ }
+
+ private companion object {
+ const val TEST_GROUP_ID_INVALID = -1
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
new file mode 100644
index 0000000..03981bb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.data.repository.audioSharingRepository
+
+val Kosmos.audioSharingInteractor by
+ Kosmos.Fixture {
+ AudioSharingInteractorImpl(
+ applicationCoroutineScope,
+ audioSharingRepository,
+ )
+ }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index e031eb2..8a1fe62 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -30,7 +30,6 @@
return RavenwoodRuntimeNative.lseek(fd, offset, whence);
}
-
public static FileDescriptor[] pipe2(int flags) throws ErrnoException {
return RavenwoodRuntimeNative.pipe2(flags);
}
@@ -42,4 +41,16 @@
public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException {
return RavenwoodRuntimeNative.fcntlInt(fd, cmd, arg);
}
+
+ public static StructStat fstat(FileDescriptor fd) throws ErrnoException {
+ return RavenwoodRuntimeNative.fstat(fd);
+ }
+
+ public static StructStat lstat(String path) throws ErrnoException {
+ return RavenwoodRuntimeNative.lstat(path);
+ }
+
+ public static StructStat stat(String path) throws ErrnoException {
+ return RavenwoodRuntimeNative.stat(path);
+ }
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java
new file mode 100644
index 0000000..a8b1fca
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import libcore.util.Objects;
+
+/**
+ * File information returned by {@link Os#fstat}, {@link Os#lstat}, and {@link Os#stat}.
+ * Corresponds to C's {@code struct stat} from {@code <stat.h>}.
+ */
+public final class StructStat {
+ /** Device ID of device containing file. */
+ public final long st_dev; /*dev_t*/
+
+ /** File serial number (inode). */
+ public final long st_ino; /*ino_t*/
+
+ /** Mode (permissions) of file. */
+ public final int st_mode; /*mode_t*/
+
+ /** Number of hard links to the file. */
+ public final long st_nlink; /*nlink_t*/
+
+ /** User ID of file. */
+ public final int st_uid; /*uid_t*/
+
+ /** Group ID of file. */
+ public final int st_gid; /*gid_t*/
+
+ /** Device ID (if file is character or block special). */
+ public final long st_rdev; /*dev_t*/
+
+ /**
+ * For regular files, the file size in bytes.
+ * For symbolic links, the length in bytes of the pathname contained in the symbolic link.
+ * For a shared memory object, the length in bytes.
+ * For a typed memory object, the length in bytes.
+ * For other file types, the use of this field is unspecified.
+ */
+ public final long st_size; /*off_t*/
+
+ /** Seconds part of time of last access. */
+ public final long st_atime; /*time_t*/
+
+ /** StructTimespec with time of last access. */
+ public final StructTimespec st_atim;
+
+ /** Seconds part of time of last data modification. */
+ public final long st_mtime; /*time_t*/
+
+ /** StructTimespec with time of last modification. */
+ public final StructTimespec st_mtim;
+
+ /** Seconds part of time of last status change */
+ public final long st_ctime; /*time_t*/
+
+ /** StructTimespec with time of last status change. */
+ public final StructTimespec st_ctim;
+
+ /**
+ * A file system-specific preferred I/O block size for this object.
+ * For some file system types, this may vary from file to file.
+ */
+ public final long st_blksize; /*blksize_t*/
+
+ /** Number of blocks allocated for this object. */
+ public final long st_blocks; /*blkcnt_t*/
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
+ long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime,
+ long st_blksize, long st_blocks) {
+ this(st_dev, st_ino, st_mode, st_nlink, st_uid, st_gid,
+ st_rdev, st_size, new StructTimespec(st_atime, 0L), new StructTimespec(st_mtime, 0L),
+ new StructTimespec(st_ctime, 0L), st_blksize, st_blocks);
+ }
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
+ long st_rdev, long st_size, StructTimespec st_atim, StructTimespec st_mtim,
+ StructTimespec st_ctim, long st_blksize, long st_blocks) {
+ this.st_dev = st_dev;
+ this.st_ino = st_ino;
+ this.st_mode = st_mode;
+ this.st_nlink = st_nlink;
+ this.st_uid = st_uid;
+ this.st_gid = st_gid;
+ this.st_rdev = st_rdev;
+ this.st_size = st_size;
+ this.st_atime = st_atim.tv_sec;
+ this.st_mtime = st_mtim.tv_sec;
+ this.st_ctime = st_ctim.tv_sec;
+ this.st_atim = st_atim;
+ this.st_mtim = st_mtim;
+ this.st_ctim = st_ctim;
+ this.st_blksize = st_blksize;
+ this.st_blocks = st_blocks;
+ }
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java
new file mode 100644
index 0000000..c106780
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import libcore.util.Objects;;
+
+/**
+ * Corresponds to C's {@code struct timespec} from {@code <time.h>}.
+ */
+public final class StructTimespec implements Comparable<StructTimespec> {
+ /** Seconds part of time of last data modification. */
+ public final long tv_sec; /*time_t*/
+
+ /** Nanoseconds (values are [0, 999999999]). */
+ public final long tv_nsec;
+
+ public StructTimespec(long tv_sec, long tv_nsec) {
+ this.tv_sec = tv_sec;
+ this.tv_nsec = tv_nsec;
+ if (tv_nsec < 0 || tv_nsec > 999_999_999) {
+ throw new IllegalArgumentException(
+ "tv_nsec value " + tv_nsec + " is not in [0, 999999999]");
+ }
+ }
+
+ @Override
+ public int compareTo(StructTimespec other) {
+ if (tv_sec > other.tv_sec) {
+ return 1;
+ }
+ if (tv_sec < other.tv_sec) {
+ return -1;
+ }
+ if (tv_nsec > other.tv_nsec) {
+ return 1;
+ }
+ if (tv_nsec < other.tv_nsec) {
+ return -1;
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ StructTimespec that = (StructTimespec) o;
+
+ if (tv_sec != that.tv_sec) return false;
+ return tv_nsec == that.tv_nsec;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) (tv_sec ^ (tv_sec >>> 32));
+ result = 31 * result + (int) (tv_nsec ^ (tv_nsec >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toString(this);
+ }
+}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
similarity index 65%
rename from ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java
rename to ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
index 6540221..e9b305e 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
@@ -15,6 +15,9 @@
*/
package com.android.ravenwood.common;
+import android.system.ErrnoException;
+import android.system.StructStat;
+
import java.io.FileDescriptor;
/**
@@ -31,19 +34,25 @@
public static native void applyFreeFunction(long freeFunction, long nativePtr);
- public static native long nLseek(int fd, long offset, int whence);
+ private static native long nLseek(int fd, long offset, int whence) throws ErrnoException;
- public static native int[] nPipe2(int flags);
+ private static native int[] nPipe2(int flags) throws ErrnoException;
- public static native int nDup(int oldfd);
+ private static native int nDup(int oldfd) throws ErrnoException;
- public static native int nFcntlInt(int fd, int cmd, int arg);
+ private static native int nFcntlInt(int fd, int cmd, int arg) throws ErrnoException;
- public static long lseek(FileDescriptor fd, long offset, int whence) {
+ private static native StructStat nFstat(int fd) throws ErrnoException;
+
+ public static native StructStat lstat(String path) throws ErrnoException;
+
+ public static native StructStat stat(String path) throws ErrnoException;
+
+ public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
}
- public static FileDescriptor[] pipe2(int flags) {
+ public static FileDescriptor[] pipe2(int flags) throws ErrnoException {
var fds = nPipe2(flags);
var ret = new FileDescriptor[] {
new FileDescriptor(),
@@ -55,7 +64,7 @@
return ret;
}
- public static FileDescriptor dup(FileDescriptor fd) {
+ public static FileDescriptor dup(FileDescriptor fd) throws ErrnoException {
var fdInt = nDup(JvmWorkaround.getInstance().getFdInt(fd));
var retFd = new java.io.FileDescriptor();
@@ -63,9 +72,15 @@
return retFd;
}
- public static int fcntlInt(FileDescriptor fd, int cmd, int arg) {
+ public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException {
var fdInt = JvmWorkaround.getInstance().getFdInt(fd);
return nFcntlInt(fdInt, cmd, arg);
}
+
+ public static StructStat fstat(FileDescriptor fd) throws ErrnoException {
+ var fdInt = JvmWorkaround.getInstance().getFdInt(fd);
+
+ return nFstat(fdInt);
+ }
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java
new file mode 100644
index 0000000..3781fcf
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2010 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 libcore.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+public final class Objects {
+ private Objects() {}
+
+ /**
+ * Returns a string reporting the value of each declared field, via reflection.
+ * Static and transient fields are automatically skipped. Produces output like
+ * "SimpleClassName[integer=1234,string="hello",character='c',intArray=[1,2,3]]".
+ */
+ public static String toString(Object o) {
+ Class<?> c = o.getClass();
+ StringBuilder sb = new StringBuilder();
+ sb.append(c.getSimpleName()).append('[');
+ int i = 0;
+ for (Field f : c.getDeclaredFields()) {
+ if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) {
+ continue;
+ }
+ f.setAccessible(true);
+ try {
+ Object value = f.get(o);
+
+ if (i++ > 0) {
+ sb.append(',');
+ }
+
+ sb.append(f.getName());
+ sb.append('=');
+
+ if (value.getClass().isArray()) {
+ if (value.getClass() == boolean[].class) {
+ sb.append(Arrays.toString((boolean[]) value));
+ } else if (value.getClass() == byte[].class) {
+ sb.append(Arrays.toString((byte[]) value));
+ } else if (value.getClass() == char[].class) {
+ sb.append(Arrays.toString((char[]) value));
+ } else if (value.getClass() == double[].class) {
+ sb.append(Arrays.toString((double[]) value));
+ } else if (value.getClass() == float[].class) {
+ sb.append(Arrays.toString((float[]) value));
+ } else if (value.getClass() == int[].class) {
+ sb.append(Arrays.toString((int[]) value));
+ } else if (value.getClass() == long[].class) {
+ sb.append(Arrays.toString((long[]) value));
+ } else if (value.getClass() == short[].class) {
+ sb.append(Arrays.toString((short[]) value));
+ } else {
+ sb.append(Arrays.toString((Object[]) value));
+ }
+ } else if (value.getClass() == Character.class) {
+ sb.append('\'').append(value).append('\'');
+ } else if (value.getClass() == String.class) {
+ sb.append('"').append(value).append('"');
+ } else {
+ sb.append(value);
+ }
+ } catch (IllegalAccessException unexpected) {
+ throw new AssertionError(unexpected);
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 34cf9f9..e0a3e1c 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -19,6 +19,9 @@
#include <string.h>
#include <unistd.h>
#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"
@@ -41,6 +44,75 @@
return rc;
}
+// ---- Helper functions ---
+
+static jclass g_StructStat;
+static jclass g_StructTimespecClass;
+
+static jclass findClass(JNIEnv* env, const char* name) {
+ ScopedLocalRef<jclass> localClass(env, env->FindClass(name));
+ jclass result = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
+ if (result == NULL) {
+ ALOGE("failed to find class '%s'", name);
+ abort();
+ }
+ return result;
+}
+
+static jobject makeStructTimespec(JNIEnv* env, const struct timespec& ts) {
+ static jmethodID ctor = env->GetMethodID(g_StructTimespecClass, "<init>",
+ "(JJ)V");
+ if (ctor == NULL) {
+ return NULL;
+ }
+ return env->NewObject(g_StructTimespecClass, ctor,
+ static_cast<jlong>(ts.tv_sec), static_cast<jlong>(ts.tv_nsec));
+}
+
+static jobject makeStructStat(JNIEnv* env, const struct stat64& sb) {
+ static jmethodID ctor = env->GetMethodID(g_StructStat, "<init>",
+ "(JJIJIIJJLandroid/system/StructTimespec;Landroid/system/StructTimespec;Landroid/system/StructTimespec;JJ)V");
+ if (ctor == NULL) {
+ return NULL;
+ }
+
+ jobject atim_timespec = makeStructTimespec(env, sb.st_atim);
+ if (atim_timespec == NULL) {
+ return NULL;
+ }
+ jobject mtim_timespec = makeStructTimespec(env, sb.st_mtim);
+ if (mtim_timespec == NULL) {
+ return NULL;
+ }
+ jobject ctim_timespec = makeStructTimespec(env, sb.st_ctim);
+ if (ctim_timespec == NULL) {
+ return NULL;
+ }
+
+ return env->NewObject(g_StructStat, ctor,
+ static_cast<jlong>(sb.st_dev), static_cast<jlong>(sb.st_ino),
+ static_cast<jint>(sb.st_mode), static_cast<jlong>(sb.st_nlink),
+ static_cast<jint>(sb.st_uid), static_cast<jint>(sb.st_gid),
+ static_cast<jlong>(sb.st_rdev), static_cast<jlong>(sb.st_size),
+ atim_timespec, mtim_timespec, ctim_timespec,
+ static_cast<jlong>(sb.st_blksize), static_cast<jlong>(sb.st_blocks));
+}
+
+static jobject doStat(JNIEnv* env, jstring javaPath, bool isLstat) {
+ ScopedUtfChars path(env, javaPath);
+ if (path.c_str() == NULL) {
+ return NULL;
+ }
+ struct stat64 sb;
+ int rc = isLstat ? TEMP_FAILURE_RETRY(lstat64(path.c_str(), &sb))
+ : TEMP_FAILURE_RETRY(stat64(path.c_str(), &sb));
+ if (rc == -1) {
+ throwErrnoException(env, isLstat ? "lstat" : "stat");
+ return NULL;
+ }
+ return makeStructStat(env, sb);
+}
+
// ---- JNI methods ----
typedef void (*FreeFunction)(void*);
@@ -77,6 +149,24 @@
return throwIfMinusOne(env, "fcntl", TEMP_FAILURE_RETRY(fcntl(fd, F_DUPFD_CLOEXEC, 0)));
}
+static jobject nFstat(JNIEnv* env, jobject, jint fd) {
+ struct stat64 sb;
+ int rc = TEMP_FAILURE_RETRY(fstat64(fd, &sb));
+ if (rc == -1) {
+ throwErrnoException(env, "fstat");
+ return NULL;
+ }
+ return makeStructStat(env, sb);
+}
+
+static jobject Linux_lstat(JNIEnv* env, jobject, jstring javaPath) {
+ return doStat(env, javaPath, true);
+}
+
+static jobject Linux_stat(JNIEnv* env, jobject, jstring javaPath) {
+ return doStat(env, javaPath, false);
+}
+
// ---- Registration ----
static const JNINativeMethod sMethods[] =
@@ -86,6 +176,9 @@
{ "nLseek", "(IJI)J", (void*)nLseek },
{ "nPipe2", "(I)[I", (void*)nPipe2 },
{ "nDup", "(I)I", (void*)nDup },
+ { "nFstat", "(I)Landroid/system/StructStat;", (void*)nFstat },
+ { "lstat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_lstat },
+ { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
@@ -101,6 +194,9 @@
ALOGI("%s: JNI_OnLoad", __FILE__);
+ g_StructStat = findClass(env, "android/system/StructStat");
+ g_StructTimespecClass = findClass(env, "android/system/StructTimespec");
+
jint res = jniRegisterNativeMethods(env, "com/android/ravenwood/common/RavenwoodRuntimeNative",
sMethods, NELEM(sMethods));
if (res < 0) {
diff --git a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
index b5038e6..05275b2 100644
--- a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
+++ b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
@@ -15,15 +15,26 @@
*/
package com.android.ravenwood.runtimetest;
+import static android.system.OsConstants.S_ISBLK;
+import static android.system.OsConstants.S_ISCHR;
+import static android.system.OsConstants.S_ISDIR;
+import static android.system.OsConstants.S_ISFIFO;
+import static android.system.OsConstants.S_ISLNK;
+import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_ISSOCK;
+
import static org.junit.Assert.assertEquals;
+import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
+
import android.system.Os;
import android.system.OsConstants;
+import android.system.StructStat;
+import android.system.StructTimespec;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.ravenwood.common.JvmWorkaround;
-import com.android.ravenwood.common.RavenwoodRuntimeNative;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,8 +43,15 @@
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class OsTest {
@@ -41,7 +59,7 @@
void accept(T var1) throws Exception;
}
- private void withTestFile(ConsumerWithThrow<FileDescriptor> consumer) throws Exception {
+ private void withTestFileFD(ConsumerWithThrow<FileDescriptor> consumer) throws Exception {
File file = File.createTempFile("osTest", "bin");
try (var raf = new RandomAccessFile(file, "rw")) {
var fd = raf.getFD();
@@ -57,9 +75,20 @@
}
}
+ private void withTestFile(ConsumerWithThrow<Path> consumer) throws Exception {
+ var path = Files.createTempFile("osTest", "bin");
+ try (var os = Files.newOutputStream(path)) {
+ os.write(1);
+ os.write(2);
+ os.write(3);
+ os.write(4);
+ }
+ consumer.accept(path);
+ }
+
@Test
public void testLseek() throws Exception {
- withTestFile((fd) -> {
+ withTestFileFD((fd) -> {
assertEquals(4, Os.lseek(fd, 4, OsConstants.SEEK_SET));
assertEquals(4, Os.lseek(fd, 0, OsConstants.SEEK_CUR));
assertEquals(6, Os.lseek(fd, 2, OsConstants.SEEK_CUR));
@@ -68,7 +97,7 @@
@Test
public void testDup() throws Exception {
- withTestFile((fd) -> {
+ withTestFileFD((fd) -> {
var dup = Os.dup(fd);
checkAreDup(fd, dup);
@@ -85,7 +114,7 @@
@Test
public void testFcntlInt() throws Exception {
- withTestFile((fd) -> {
+ withTestFileFD((fd) -> {
var dupInt = Os.fcntlInt(fd, 0, 0);
var dup = new FileDescriptor();
@@ -95,16 +124,90 @@
});
}
- private static void write(FileDescriptor fd, int oneByte) throws IOException {
+ @Test
+ public void testStat() throws Exception {
+ withTestFile(path -> {
+ var attr = Files.readAttributes(path, PosixFileAttributes.class);
+ var stat = Os.stat(path.toAbsolutePath().toString());
+ assertAttributesEqual(attr, stat);
+ });
+ }
+
+ @Test
+ public void testLstat() throws Exception {
+ withTestFile(path -> {
+ // Create a symbolic link
+ var lnk = Files.createTempFile("osTest", "lnk");
+ Files.delete(lnk);
+ Files.createSymbolicLink(lnk, path);
+
+ // Test lstat
+ var attr = Files.readAttributes(lnk, PosixFileAttributes.class, NOFOLLOW_LINKS);
+ var stat = Os.lstat(lnk.toAbsolutePath().toString());
+ assertAttributesEqual(attr, stat);
+
+ // Test stat
+ var followAttr = Files.readAttributes(lnk, PosixFileAttributes.class);
+ var followStat = Os.stat(lnk.toAbsolutePath().toString());
+ assertAttributesEqual(followAttr, followStat);
+ });
+ }
+
+ @Test
+ public void testFstat() throws Exception {
+ withTestFile(path -> {
+ var attr = Files.readAttributes(path, PosixFileAttributes.class);
+ try (var raf = new RandomAccessFile(path.toFile(), "r")) {
+ var fd = raf.getFD();
+ var stat = Os.fstat(fd);
+ assertAttributesEqual(attr, stat);
+ }
+ });
+ }
+
+ // Verify StructStat values from libcore against native JVM PosixFileAttributes
+ private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) {
+ assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim));
+ assertEquals(attr.size(), stat.st_size);
+ assertEquals(attr.isDirectory(), S_ISDIR(stat.st_mode));
+ assertEquals(attr.isRegularFile(), S_ISREG(stat.st_mode));
+ assertEquals(attr.isSymbolicLink(), S_ISLNK(stat.st_mode));
+ assertEquals(attr.isOther(), S_ISCHR(stat.st_mode)
+ || S_ISBLK(stat.st_mode) || S_ISFIFO(stat.st_mode) || S_ISSOCK(stat.st_mode));
+ assertEquals(attr.permissions(), convertModeToPosixPerms(stat.st_mode));
+
+ }
+
+ private static FileTime convertTimespecToFileTime(StructTimespec ts) {
+ var nanos = TimeUnit.SECONDS.toNanos(ts.tv_sec);
+ nanos += ts.tv_nsec;
+ return FileTime.from(nanos, TimeUnit.NANOSECONDS);
+ }
+
+ private static Set<PosixFilePermission> convertModeToPosixPerms(int mode) {
+ var set = new HashSet<PosixFilePermission>();
+ if ((mode & OsConstants.S_IRUSR) != 0) set.add(PosixFilePermission.OWNER_READ);
+ if ((mode & OsConstants.S_IWUSR) != 0) set.add(PosixFilePermission.OWNER_WRITE);
+ if ((mode & OsConstants.S_IXUSR) != 0) set.add(PosixFilePermission.OWNER_EXECUTE);
+ if ((mode & OsConstants.S_IRGRP) != 0) set.add(PosixFilePermission.GROUP_READ);
+ if ((mode & OsConstants.S_IWGRP) != 0) set.add(PosixFilePermission.GROUP_WRITE);
+ if ((mode & OsConstants.S_IXGRP) != 0) set.add(PosixFilePermission.GROUP_EXECUTE);
+ if ((mode & OsConstants.S_IROTH) != 0) set.add(PosixFilePermission.OTHERS_READ);
+ if ((mode & OsConstants.S_IWOTH) != 0) set.add(PosixFilePermission.OTHERS_WRITE);
+ if ((mode & OsConstants.S_IXOTH) != 0) set.add(PosixFilePermission.OTHERS_EXECUTE);
+ return set;
+ }
+
+ private static void write(FileDescriptor fd, int oneByte) throws Exception {
// Create a dup to avoid closing the FD.
- try (var dup = new FileOutputStream(RavenwoodRuntimeNative.dup(fd))) {
+ try (var dup = new FileOutputStream(Os.dup(fd))) {
dup.write(oneByte);
}
}
- private static int read(FileDescriptor fd) throws IOException {
+ private static int read(FileDescriptor fd) throws Exception {
// Create a dup to avoid closing the FD.
- try (var dup = new FileInputStream(RavenwoodRuntimeNative.dup(fd))) {
+ try (var dup = new FileInputStream(Os.dup(fd))) {
return dup.read();
}
}
diff --git a/services/Android.bp b/services/Android.bp
index cd974c5..dce6aa7 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -186,7 +186,15 @@
// merge all required services into one jar
// ============================================================
-java_library {
+soong_config_module_type {
+ name: "system_java_library",
+ module_type: "java_library",
+ config_namespace: "system_services",
+ bool_variables: ["without_vibrator"],
+ properties: ["vintf_fragments"],
+}
+
+system_java_library {
name: "services",
defaults: [
"services_java_defaults",
@@ -248,9 +256,19 @@
"service-sdksandbox.stubs.system_server",
],
- vintf_fragments: [
- "manifest_services.xml",
- ],
+ soong_config_variables: {
+ without_vibrator: {
+ vintf_fragments: [
+ "manifest_services.xml",
+ ],
+ conditions_default: {
+ vintf_fragments: [
+ "manifest_services.xml",
+ "manifest_services_android.frameworks.vibrator.xml",
+ ],
+ },
+ },
+ },
required: [
"libukey2_jni_shared",
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index 7a99b60..311addb 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -29,10 +29,12 @@
"//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc",
],
libs: [
+ "aatf",
"services.core",
"androidx.annotation_annotation",
],
static_libs: [
+ "a11ychecker-protos-java-proto-lite",
"com_android_server_accessibility_flags_lib",
"//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
@@ -68,3 +70,14 @@
name: "com_android_server_accessibility_flags_lib",
aconfig_declarations: "com_android_server_accessibility_flags",
}
+
+java_library_static {
+ name: "a11ychecker-protos-java-proto-lite",
+ proto: {
+ type: "lite",
+ canonical_path_from_root: false,
+ },
+ srcs: [
+ "java/**/a11ychecker/proto/*.proto",
+ ],
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
new file mode 100644
index 0000000..55af9a0
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.a11ychecker;
+
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.util.Slog;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckClass;
+import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultReported;
+import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultType;
+
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClassNameCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClickableSpanCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.DuplicateClickableBoundsCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.DuplicateSpeakableTextCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.EditableContentDescCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ImageContrastCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.LinkPurposeUnclearCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.RedundantDescriptionCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TextContrastCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TextSizeCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TraversalOrderCheck;
+
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Util class to process a11y checker results for logging.
+ *
+ * @hide
+ */
+public class AccessibilityCheckerUtils {
+
+ private static final String LOG_TAG = "AccessibilityCheckerUtils";
+ @VisibleForTesting
+ // LINT.IfChange
+ static final Map<Class<? extends AccessibilityHierarchyCheck>, AccessibilityCheckClass>
+ CHECK_CLASS_TO_ENUM_MAP =
+ Map.ofEntries(
+ classMapEntry(ClassNameCheck.class, AccessibilityCheckClass.CLASS_NAME_CHECK),
+ classMapEntry(ClickableSpanCheck.class,
+ AccessibilityCheckClass.CLICKABLE_SPAN_CHECK),
+ classMapEntry(DuplicateClickableBoundsCheck.class,
+ AccessibilityCheckClass.DUPLICATE_CLICKABLE_BOUNDS_CHECK),
+ classMapEntry(DuplicateSpeakableTextCheck.class,
+ AccessibilityCheckClass.DUPLICATE_SPEAKABLE_TEXT_CHECK),
+ classMapEntry(EditableContentDescCheck.class,
+ AccessibilityCheckClass.EDITABLE_CONTENT_DESC_CHECK),
+ classMapEntry(ImageContrastCheck.class,
+ AccessibilityCheckClass.IMAGE_CONTRAST_CHECK),
+ classMapEntry(LinkPurposeUnclearCheck.class,
+ AccessibilityCheckClass.LINK_PURPOSE_UNCLEAR_CHECK),
+ classMapEntry(RedundantDescriptionCheck.class,
+ AccessibilityCheckClass.REDUNDANT_DESCRIPTION_CHECK),
+ classMapEntry(SpeakableTextPresentCheck.class,
+ AccessibilityCheckClass.SPEAKABLE_TEXT_PRESENT_CHECK),
+ classMapEntry(TextContrastCheck.class,
+ AccessibilityCheckClass.TEXT_CONTRAST_CHECK),
+ classMapEntry(TextSizeCheck.class, AccessibilityCheckClass.TEXT_SIZE_CHECK),
+ classMapEntry(TouchTargetSizeCheck.class,
+ AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK),
+ classMapEntry(TraversalOrderCheck.class,
+ AccessibilityCheckClass.TRAVERSAL_ORDER_CHECK));
+ // LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto)
+
+ static Set<AccessibilityCheckResultReported> processResults(
+ Context context,
+ AccessibilityNodeInfo nodeInfo,
+ List<AccessibilityHierarchyCheckResult> checkResults,
+ @Nullable AccessibilityEvent accessibilityEvent,
+ ComponentName a11yServiceComponentName) {
+ return processResults(nodeInfo, checkResults, accessibilityEvent,
+ context.getPackageManager(), a11yServiceComponentName);
+ }
+
+ @VisibleForTesting
+ static Set<AccessibilityCheckResultReported> processResults(
+ AccessibilityNodeInfo nodeInfo,
+ List<AccessibilityHierarchyCheckResult> checkResults,
+ @Nullable AccessibilityEvent accessibilityEvent,
+ PackageManager packageManager,
+ ComponentName a11yServiceComponentName) {
+ String appPackageName = nodeInfo.getPackageName().toString();
+ AccessibilityCheckResultReported.Builder builder;
+ try {
+ builder = AccessibilityCheckResultReported.newBuilder()
+ .setPackageName(appPackageName)
+ .setAppVersionCode(getAppVersionCode(packageManager, appPackageName))
+ .setUiElementPath(AccessibilityNodePathBuilder.createNodePath(nodeInfo))
+ .setActivityName(getActivityName(packageManager, accessibilityEvent))
+ .setWindowTitle(getWindowTitle(nodeInfo))
+ .setSourceComponentName(a11yServiceComponentName.flattenToString())
+ .setSourceVersionCode(
+ getAppVersionCode(packageManager,
+ a11yServiceComponentName.getPackageName()));
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(LOG_TAG, "Unknown package name", e);
+ return Set.of();
+ }
+
+ return checkResults.stream()
+ .filter(checkResult -> checkResult.getType()
+ == AccessibilityCheckResult.AccessibilityCheckResultType.ERROR
+ || checkResult.getType()
+ == AccessibilityCheckResult.AccessibilityCheckResultType.WARNING)
+ .map(checkResult -> builder.setResultCheckClass(
+ getCheckClass(checkResult)).setResultType(
+ getCheckResultType(checkResult)).setResultId(
+ checkResult.getResultId()).build())
+ .collect(Collectors.toUnmodifiableSet());
+ }
+
+ private static long getAppVersionCode(PackageManager packageManager, String packageName) throws
+ PackageManager.NameNotFoundException {
+ PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
+ return packageInfo.getLongVersionCode();
+ }
+
+ /**
+ * Returns the simple class name of the Activity providing the cache update, if available,
+ * or an empty String if not.
+ */
+ @VisibleForTesting
+ static String getActivityName(
+ PackageManager packageManager, @Nullable AccessibilityEvent accessibilityEvent) {
+ if (accessibilityEvent == null) {
+ return "";
+ }
+ CharSequence activityName = accessibilityEvent.getClassName();
+ if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ && accessibilityEvent.getPackageName() != null
+ && activityName != null) {
+ try {
+ // Check class is for a valid Activity.
+ packageManager
+ .getActivityInfo(
+ new ComponentName(accessibilityEvent.getPackageName().toString(),
+ activityName.toString()), 0);
+ int qualifierEnd = activityName.toString().lastIndexOf('.');
+ return activityName.toString().substring(qualifierEnd + 1);
+ } catch (PackageManager.NameNotFoundException e) {
+ // No need to spam the logs. This is very frequent when the class doesn't match
+ // an activity.
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Returns the title of the window containing the a11y node.
+ */
+ private static String getWindowTitle(AccessibilityNodeInfo nodeInfo) {
+ if (nodeInfo.getWindow() == null) {
+ return "";
+ }
+ CharSequence windowTitle = nodeInfo.getWindow().getTitle();
+ return windowTitle == null ? "" : windowTitle.toString();
+ }
+
+ /**
+ * Maps the {@link AccessibilityHierarchyCheck} class that produced the given result, with the
+ * corresponding {@link AccessibilityCheckClass} enum. This enumeration is to avoid relying on
+ * String class names in the logging, which can be proguarded. It also reduces the logging size.
+ */
+ private static AccessibilityCheckClass getCheckClass(
+ AccessibilityHierarchyCheckResult checkResult) {
+ if (CHECK_CLASS_TO_ENUM_MAP.containsKey(checkResult.getSourceCheckClass())) {
+ return CHECK_CLASS_TO_ENUM_MAP.get(checkResult.getSourceCheckClass());
+ }
+ return AccessibilityCheckClass.UNKNOWN_CHECK;
+ }
+
+ private static AccessibilityCheckResultType getCheckResultType(
+ AccessibilityHierarchyCheckResult checkResult) {
+ return switch (checkResult.getType()) {
+ case ERROR -> AccessibilityCheckResultType.ERROR;
+ case WARNING -> AccessibilityCheckResultType.WARNING;
+ default -> AccessibilityCheckResultType.UNKNOWN_RESULT_TYPE;
+ };
+ }
+
+ private static Map.Entry<Class<? extends AccessibilityHierarchyCheck>,
+ AccessibilityCheckClass> classMapEntry(
+ Class<? extends AccessibilityHierarchyCheck> checkClass,
+ AccessibilityCheckClass checkClassEnum) {
+ return new AbstractMap.SimpleImmutableEntry<>(checkClass, checkClassEnum);
+ }
+}
diff --git a/core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
similarity index 98%
rename from core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
rename to services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
index 2996dde..bbfb217 100644
--- a/core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.view.accessibility.a11ychecker;
+package com.android.server.accessibility.a11ychecker;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/core/java/android/view/accessibility/a11ychecker/OWNERS b/services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS
similarity index 100%
rename from core/java/android/view/accessibility/a11ychecker/OWNERS
rename to services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto b/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto
new file mode 100644
index 0000000..8beed4a
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+syntax = "proto2";
+package android.accessibility;
+
+option java_package = "com.android.server.accessibility.a11ychecker";
+option java_outer_classname = "A11yCheckerProto";
+
+// TODO(b/326385939): remove and replace usage with the atom extension proto, when submitted.
+/** Logs the result of an AccessibilityCheck. */
+message AccessibilityCheckResultReported {
+ // Package name of the app containing the checked View.
+ optional string package_name = 1;
+ // Version code of the app containing the checked View.
+ optional int64 app_version_code = 2;
+ // The path of the View starting from the root element in the window. Each element is
+ // represented by the View's resource id, when available, or the View's class name.
+ optional string ui_element_path = 3;
+ // Class name of the activity containing the checked View.
+ optional string activity_name = 4;
+ // Title of the window containing the checked View.
+ optional string window_title = 5;
+ // The flattened component name of the app running the AccessibilityService which provided the a11y node.
+ optional string source_component_name = 6;
+ // Version code of the app running the AccessibilityService that provided the a11y node.
+ optional int64 source_version_code = 7;
+ // Class Name of the AccessibilityCheck that produced the result.
+ optional AccessibilityCheckClass result_check_class = 8;
+ // Result type of the AccessibilityCheckResult.
+ optional AccessibilityCheckResultType result_type = 9;
+ // Result ID of the AccessibilityCheckResult.
+ optional int32 result_id = 10;
+}
+
+/** The AccessibilityCheck class. */
+// LINT.IfChange
+enum AccessibilityCheckClass {
+ UNKNOWN_CHECK = 0;
+ CLASS_NAME_CHECK = 1;
+ CLICKABLE_SPAN_CHECK = 2;
+ DUPLICATE_CLICKABLE_BOUNDS_CHECK = 3;
+ DUPLICATE_SPEAKABLE_TEXT_CHECK = 4;
+ EDITABLE_CONTENT_DESC_CHECK = 5;
+ IMAGE_CONTRAST_CHECK = 6;
+ LINK_PURPOSE_UNCLEAR_CHECK = 7;
+ REDUNDANT_DESCRIPTION_CHECK = 8;
+ SPEAKABLE_TEXT_PRESENT_CHECK = 9;
+ TEXT_CONTRAST_CHECK = 10;
+ TEXT_SIZE_CHECK = 11;
+ TOUCH_TARGET_SIZE_CHECK = 12;
+ TRAVERSAL_ORDER_CHECK = 13;
+}
+// LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java)
+
+/** The type of AccessibilityCheckResult */
+enum AccessibilityCheckResultType {
+ UNKNOWN_RESULT_TYPE = 0;
+ ERROR = 1;
+ WARNING = 2;
+}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index d7da2f0..026c69c 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -846,7 +846,6 @@
mCallingAppUid,
event.mIsCredentialRequest,
event.mWebviewRequestedCredential,
- event.mFilteredFillabaleViewCount,
event.mViewFillableTotalCount,
event.mViewFillFailureCount,
event.mFocusedId,
@@ -859,7 +858,8 @@
event.mSuggestionPresentedLastTimestampMs,
event.mFocusedVirtualAutofillId,
event.mFieldFirstLength,
- event.mFieldLastLength);
+ event.mFieldLastLength,
+ event.mFilteredFillabaleViewCount);
mEventInternal = Optional.empty();
}
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
index 3d610d3..6a6aea4 100644
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -15,6 +15,7 @@
*/
package com.android.server;
+import static android.crashrecovery.flags.Flags.refactorCrashrecovery;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
@@ -41,7 +42,6 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
import java.util.Collection;
import java.util.Collections;
@@ -363,22 +363,34 @@
@GuardedBy("mLock")
@Nullable
private ServiceInfo getServiceInfoLocked() {
- final String packageName =
- mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
- if (packageName == null) {
- Slog.w(TAG, "no external services package!");
- return null;
- }
+ if (refactorCrashrecovery()) {
+ final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
+ | PackageManager.MATCH_SYSTEM_ONLY);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ return resolveInfo.serviceInfo;
+ } else {
+ final String packageName =
+ mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+ if (packageName == null) {
+ Slog.w(TAG, "no external services package!");
+ return null;
+ }
- final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
- intent.setPackage(packageName);
- final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
- if (resolveInfo == null || resolveInfo.serviceInfo == null) {
- Slog.w(TAG, "No valid components found.");
- return null;
+ final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ return resolveInfo.serviceInfo;
}
- return resolveInfo.serviceInfo;
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 458749d..2d91331 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -185,6 +185,12 @@
final Context mContext;
+ private static final int[] INTERESTING_APP_OPS = new int[] {
+ AppOpsManager.OP_GET_ACCOUNTS,
+ AppOpsManager.OP_READ_CONTACTS,
+ AppOpsManager.OP_WRITE_CONTACTS,
+ };
+
private final PackageManager mPackageManager;
private final AppOpsManager mAppOpsManager;
private UserManager mUserManager;
@@ -388,74 +394,47 @@
}.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
// Cancel account request notification if an app op was preventing the account access
- mAppOpsManager.startWatchingMode(AppOpsManager.OP_GET_ACCOUNTS, null,
- new AppOpsManager.OnOpChangedInternalListener() {
- @Override
- public void onOpChanged(int op, String packageName) {
- try {
- final int userId = ActivityManager.getCurrentUser();
- final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
- final int mode = mAppOpsManager.checkOpNoThrow(
- AppOpsManager.OP_GET_ACCOUNTS, uid, packageName);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- final long identity = Binder.clearCallingIdentity();
- try {
- UserAccounts accounts = getUserAccounts(userId);
- cancelAccountAccessRequestNotificationIfNeeded(
- packageName, uid, true, accounts);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- } catch (NameNotFoundException e) {
- /* ignore */
- } catch (SQLiteCantOpenDatabaseException e) {
- Log.w(TAG, "Can't read accounts database", e);
- return;
- }
- }
- });
+ for (int i = 0; i < INTERESTING_APP_OPS.length; ++i) {
+ mAppOpsManager.startWatchingMode(INTERESTING_APP_OPS[i], null,
+ new OnInterestingAppOpChangedListener());
+ }
- // Cancel account request notification if a permission was preventing the account access
- mPackageManager.addOnPermissionsChangeListener(
- (int uid) -> {
- // Permission changes cause requires updating accounts cache.
+ // Clear the accounts cache on permission changes.
+ // The specific permissions we care about are backed by AppOps, so just
+ // let the change events on those handle clearing any notifications.
+ mPackageManager.addOnPermissionsChangeListener((int uid) -> {
AccountManager.invalidateLocalAccountsDataCaches();
-
- Account[] accounts = null;
- String[] packageNames = mPackageManager.getPackagesForUid(uid);
- if (packageNames != null) {
- final int userId = UserHandle.getUserId(uid);
- final long identity = Binder.clearCallingIdentity();
- try {
- for (String packageName : packageNames) {
- // if app asked for permission we need to cancel notification even
- // for O+ applications.
- if (mPackageManager.checkPermission(
- Manifest.permission.GET_ACCOUNTS,
- packageName) != PackageManager.PERMISSION_GRANTED) {
- continue;
- }
-
- if (accounts == null) {
- accounts = getAccountsOrEmptyArray(null, userId, "android");
- if (ArrayUtils.isEmpty(accounts)) {
- return;
- }
- }
- UserAccounts userAccounts = getUserAccounts(UserHandle.getUserId(uid));
- for (Account account : accounts) {
- cancelAccountAccessRequestNotificationIfNeeded(
- account, uid, packageName, true, userAccounts);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
});
}
+ private class OnInterestingAppOpChangedListener implements AppOpsManager.OnOpChangedListener {
+ @Override
+ public void onOpChanged(String op, String packageName) {
+ final int userId = ActivityManager.getCurrentUser();
+ final int packageUid;
+ try {
+ packageUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+ } catch (NameNotFoundException e) {
+ /* ignore */
+ return;
+ }
+
+ final int mode = mAppOpsManager.checkOpNoThrow(op, packageUid, packageName);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ return;
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ cancelAccountAccessRequestNotificationIfNeeded(
+ packageName, packageUid, true, getUserAccounts(userId));
+ } catch (SQLiteCantOpenDatabaseException e) {
+ Log.w(TAG, "Can't read accounts database", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
boolean getBindInstantServiceAllowed(int userId) {
return mAuthenticatorCache.getBindInstantServiceAllowed(userId);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 00183ac..67985ef 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -76,6 +76,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.os.WakeLockStats;
import android.os.WorkSource;
@@ -158,6 +159,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -1107,6 +1109,13 @@
FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET,
null, // use default PullAtomMetadata values
DIRECT_EXECUTOR, pullAtomCallback);
+ if (Flags.addBatteryUsageStatsSliceAtom()) {
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ pullAtomCallback);
+ }
}
/** StatsPullAtomCallback for pulling BatteryUsageStats data. */
@@ -1115,7 +1124,7 @@
public int onPullAtom(int atomTag, List<StatsEvent> data) {
final BatteryUsageStats bus;
switch (atomTag) {
- case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET: {
@SuppressLint("MissingPermission")
final double minConsumedPowerThreshold =
DeviceConfig.getFloat(DEVICE_CONFIG_NAMESPACE,
@@ -1130,6 +1139,7 @@
.build();
bus = getBatteryUsageStats(List.of(querySinceReset)).get(0);
break;
+ }
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
final BatteryUsageStatsQuery queryPowerProfile =
new BatteryUsageStatsQuery.Builder()
@@ -1141,7 +1151,7 @@
.build();
bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
break;
- case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET:
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: {
final long sessionStart =
getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
final long sessionEnd;
@@ -1158,6 +1168,31 @@
bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
break;
+ }
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID: {
+ if (!Flags.addBatteryUsageStatsSliceAtom()) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ @SuppressLint("MissingPermission")
+ final double minConsumedPowerThreshold =
+ DeviceConfig.getFloat(
+ DEVICE_CONFIG_NAMESPACE,
+ MIN_CONSUMED_POWER_THRESHOLD_KEY,
+ 0);
+ final long sessionStart = 0;
+ final long sessionEnd = System.currentTimeMillis();
+ final BatteryUsageStatsQuery query =
+ new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includeProcessStateData()
+ .includeVirtualUids()
+ .aggregateSnapshots(sessionStart, sessionEnd)
+ .setMinConsumedPowerThreshold(minConsumedPowerThreshold)
+ .build();
+ bus = getBatteryUsageStats(List.of(query)).get(0);
+ return StatsPerUidLogger.logStats(bus, data);
+ }
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
}
@@ -1169,6 +1204,262 @@
}
}
+ private static class StatsPerUidLogger {
+
+ private static final int STATSD_METRIC_MAX_DIMENSIONS_COUNT = 3000;
+
+ private static final int[] UID_PROCESS_STATES = {
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+ BatteryConsumer.PROCESS_STATE_CACHED
+ };
+
+ public record SessionInfo(
+ long startTs,
+ long endTs,
+ long duration,
+ int dischargePercentage,
+ long dischargeDuration) {}
+ ;
+
+ static int logStats(BatteryUsageStats bus, List<StatsEvent> data) {
+ final SessionInfo sessionInfo =
+ new SessionInfo(
+ bus.getStatsStartTimestamp(),
+ bus.getStatsEndTimestamp(),
+ bus.getStatsDuration(),
+ bus.getDischargePercentage(),
+ bus.getDischargeDurationMs());
+
+ if (DBG) {
+ Slog.d(TAG, "BatteryUsageStats dump = " + bus);
+ }
+ final BatteryConsumer deviceConsumer =
+ bus.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+
+ final float totalDeviceConsumedPowerMah = (float) deviceConsumer.getConsumedPower();
+
+ for (@BatteryConsumer.PowerComponent int componentId = 0;
+ componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+
+ if (!addStatsForPredefinedComponent(
+ data,
+ sessionInfo,
+ Process.INVALID_UID,
+ processState,
+ totalDeviceConsumedPowerMah,
+ deviceConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+
+ final int customPowerComponentCount = deviceConsumer.getCustomPowerComponentCount();
+ for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ componentId
+ < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + customPowerComponentCount;
+ componentId++) {
+
+ if (!addStatsForCustomComponent(
+ data,
+ sessionInfo,
+ Process.INVALID_UID,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0,
+ totalDeviceConsumedPowerMah,
+ deviceConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+
+ final List<UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
+ uidConsumers.sort(
+ Comparator.<BatteryConsumer>comparingDouble(BatteryConsumer::getConsumedPower)
+ .reversed());
+
+ // Log single atom for BatteryUsageStats per uid/process_state/component/etc.
+ for (UidBatteryConsumer uidConsumer : uidConsumers) {
+ final int uid = uidConsumer.getUid();
+ final float totalConsumedPowerMah = (float) uidConsumer.getConsumedPower();
+
+ for (@BatteryConsumer.PowerComponent int componentId = 0;
+ componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+
+ if (!addStatsForPredefinedComponent(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ totalConsumedPowerMah,
+ uidConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+
+ // looping over custom components
+ for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ componentId
+ < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + customPowerComponentCount;
+ componentId++) {
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+ final long timeInStateMillis =
+ uidConsumer.getTimeInProcessStateMs(processState);
+ if (timeInStateMillis <= 0) {
+ continue;
+ }
+
+ if (!addStatsForCustomComponent(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInStateMillis,
+ totalConsumedPowerMah,
+ uidConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private static boolean addStatsForPredefinedComponent(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ @BatteryConsumer.ProcessState int processState,
+ float totalConsumedPowerMah,
+ BatteryConsumer batteryConsumer,
+ @BatteryConsumer.PowerComponent int componentId) {
+ final BatteryConsumer.Key key = batteryConsumer.getKey(componentId, processState);
+ if (key == null) {
+ return true;
+ }
+
+ final String powerComponentName = BatteryConsumer.powerComponentIdToString(componentId);
+ final float powerMah = (float) batteryConsumer.getConsumedPower(key);
+ final long powerComponentDurationMillis = batteryConsumer.getUsageDurationMillis(key);
+
+ if (powerMah == 0 && powerComponentDurationMillis == 0) {
+ return true;
+ }
+
+ long timeInState = 0;
+ if (batteryConsumer instanceof UidBatteryConsumer) {
+ timeInState =
+ ((UidBatteryConsumer) batteryConsumer)
+ .getTimeInProcessStateMs(processState);
+ }
+
+ return addStatsAtom(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInState,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerMah,
+ powerComponentDurationMillis);
+ }
+
+ private static boolean addStatsForCustomComponent(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ @BatteryConsumer.ProcessState int processState,
+ long timeInStateMillis,
+ float totalConsumedPowerMah,
+ BatteryConsumer batteryConsumer,
+ int componentId) {
+
+ if (componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
+ throw new IllegalArgumentException("Invalid custom component id: " + componentId);
+ }
+
+ final float powerMah =
+ (float) batteryConsumer.getConsumedPowerForCustomComponent(componentId);
+ if (powerMah == 0) {
+ return true;
+ }
+
+ final String powerComponentName =
+ batteryConsumer.getCustomPowerComponentName(componentId);
+
+ final long powerComponentDurationMillis =
+ batteryConsumer.getUsageDurationForCustomComponentMillis(componentId);
+
+ return addStatsAtom(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInStateMillis,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerMah,
+ powerComponentDurationMillis);
+ }
+
+ /**
+ * Returns true on success and false if reached max atoms capacity and no more atoms should
+ * be added
+ */
+ private static boolean addStatsAtom(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ int processState,
+ long timeInStateMillis,
+ String powerComponentName,
+ float totalConsumedPowerMah,
+ float powerComponentMah,
+ long powerComponentDurationMillis) {
+ data.add(
+ FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+ sessionInfo.startTs(),
+ sessionInfo.endTs(),
+ sessionInfo.duration(),
+ sessionInfo.dischargePercentage(),
+ sessionInfo.dischargeDuration(),
+ uid,
+ processState,
+ timeInStateMillis,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerComponentMah,
+ powerComponentDurationMillis));
+
+ // Early termination due to statsd dimensions guardrail
+ if (data.size() == STATSD_METRIC_MAX_DIMENSIONS_COUNT) {
+ Slog.w(
+ TAG,
+ "BATTERY_USAGE_STATS_PER_UID is complete reaching"
+ + " dimension guardrail");
+ return false;
+ }
+ return true;
+ }
+ }
+
@Override
@RequiresNoPermission
public boolean isCharging() {
@@ -2824,9 +3115,11 @@
pw.println(" --checkin: generate output for a checkin report; will write (and clear) the");
pw.println(" last old completed stats when they had been reset.");
pw.println(" -c: write the current stats in checkin format.");
- pw.println(" --proto: write the current aggregate stats (without history) in proto format.");
+ pw.println(
+ " --proto: write the current aggregate stats (without history) in proto format.");
pw.println(" --history: show only history data.");
- pw.println(" --history-start <num>: show only history data starting at given time offset.");
+ pw.println(
+ " --history-start <num>: show only history data starting at given time offset.");
pw.println(" --history-create-events <num>: create <num> of battery history events.");
pw.println(" --charged: only output data since last charged.");
pw.println(" --daily: only output full daily data.");
@@ -2850,12 +3143,15 @@
pw.println(" -h: print this help text.");
pw.println("Battery stats (batterystats) commands:");
pw.println(" enable|disable <option>");
- pw.println(" Enable or disable a running option. Option state is not saved across boots.");
+ pw.println(
+ " Enable or disable a running option. Option state is not saved across boots.");
pw.println(" Options are:");
pw.println(" full-history: include additional detailed events in battery history:");
pw.println(" wake_lock_in, alarms and proc events");
pw.println(" no-auto-reset: don't automatically reset stats when unplugged");
- pw.println(" pretend-screen-off: pretend the screen is off, even if screen state changes");
+ pw.println(
+ " pretend-screen-off: pretend the screen is off, even if screen state"
+ + " changes");
}
private void dumpSettings(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index f5231ae..7a055d1 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -619,6 +619,15 @@
* </idleScreenRefreshRateTimeout>
* <supportsVrr>true</supportsVrr>
*
+ * <dozeBrightnessSensorValueToBrightness>
+ * <item>-1</item> <!-- 0: OFF -->
+ * <item>0.003937008</item> <!-- 1: NIGHT -->
+ * <item>0.015748031</item> <!-- 2: LOW -->
+ * <item>0.102362205</item> <!-- 3: HIGH -->
+ * <item>0.106299213</item> <!-- 4: SUN -->
+ * </dozeBrightnessSensorValueToBrightness>
+ * <defaultDozeBrightness>0.235</defaultDozeBrightness>
+ *
* </displayConfiguration>
* }
* </pre>
@@ -638,6 +647,10 @@
public static final int DEFAULT_LOW_REFRESH_RATE = 60;
+ // Float.NaN (used as invalid for brightness) cannot be stored in config.xml
+ // so -2 is used instead
+ public static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
+
@VisibleForTesting
static final float BRIGHTNESS_DEFAULT = 0.5f;
private static final String ETC_DIR = "etc";
@@ -656,10 +669,6 @@
private static final int INTERPOLATION_DEFAULT = 0;
private static final int INTERPOLATION_LINEAR = 1;
- // Float.NaN (used as invalid for brightness) cannot be stored in config.xml
- // so -2 is used instead
- private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
-
// Length of the ambient light horizon used to calculate the long term estimate of ambient
// light.
private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000;
@@ -670,6 +679,11 @@
// Invalid value of AutoBrightness brightening and darkening light debounce
private static final int INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE = -1;
+ @VisibleForTesting
+ static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
+
+ private static final int KEEP_CURRENT_BRIGHTNESS = -1;
+
private final Context mContext;
// The details of the ambient light sensor associated with this display.
@@ -877,6 +891,10 @@
private boolean mVrrSupportEnabled;
+ @Nullable
+ private float[] mDozeBrightnessSensorValueToBrightness;
+ private float mDefaultDozeBrightness;
+
private final DisplayManagerFlags mFlags;
@VisibleForTesting
@@ -1592,6 +1610,24 @@
return mVrrSupportEnabled;
}
+ /**
+ * While the device is dozing, a designated light sensor is used to determine the brightness.
+ * @return The mapping between doze brightness sensor values and brightness values. The value
+ * -1 means that the current brightness should be kept.
+ */
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness() {
+ return mDozeBrightnessSensorValueToBrightness;
+ }
+
+ /**
+ * @return The default doze brightness to use while no other doze brightness is available. Can
+ * be {@link PowerManager#BRIGHTNESS_INVALID_FLOAT} if undefined.
+ */
+ public float getDefaultDozeBrightness() {
+ return mDefaultDozeBrightness;
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -1689,6 +1725,9 @@
? mEvenDimmerBrightnessData.toString() : "null")
+ "\n"
+ "mVrrSupported= " + mVrrSupportEnabled + "\n"
+ + "mDozeBrightnessSensorValueToBrightness= "
+ + Arrays.toString(mDozeBrightnessSensorValueToBrightness) + "\n"
+ + "mDefaultDozeBrightness= " + mDefaultDozeBrightness + "\n"
+ "}";
}
@@ -1783,6 +1822,7 @@
loadBrightnessCapForWearBedtimeMode(config);
loadIdleScreenRefreshRateTimeoutConfigs(config);
mVrrSupportEnabled = config.getSupportsVrr();
+ loadDozeBrightness(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
@@ -1811,6 +1851,7 @@
loadRefreshRateSetting(null);
loadBrightnessCapForWearBedtimeModeFromConfigXml();
loadIdleScreenRefreshRateTimeoutConfigs(null);
+ loadDozeBrightness(null);
mLoadedFrom = "<config.xml>";
}
@@ -2745,6 +2786,37 @@
}
}
+ private void loadDozeBrightness(DisplayConfiguration config) {
+ if (mFlags.isDozeBrightnessFloatEnabled() && config != null
+ && config.getDozeBrightnessSensorValueToBrightness() != null) {
+ List<BigDecimal> values = config.getDozeBrightnessSensorValueToBrightness().getItem();
+ mDozeBrightnessSensorValueToBrightness = new float[values.size()];
+ for (int i = 0; i < values.size(); i++) {
+ float backlight = values.get(i).floatValue();
+ if (backlight != KEEP_CURRENT_BRIGHTNESS) {
+ mDozeBrightnessSensorValueToBrightness[i] =
+ getBrightnessFromBacklight(backlight);
+ } else {
+ mDozeBrightnessSensorValueToBrightness[i] = KEEP_CURRENT_BRIGHTNESS;
+ }
+ }
+ }
+
+ if (mFlags.isDozeBrightnessFloatEnabled() && config != null
+ && config.getDefaultDozeBrightness() != null) {
+ float backlight = config.getDefaultDozeBrightness().floatValue();
+ mDefaultDozeBrightness = getBrightnessFromBacklight(backlight);
+ } else {
+ mDefaultDozeBrightness = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_screenBrightnessDozeFloat);
+ if (mDefaultDozeBrightness == INVALID_BRIGHTNESS_IN_CONFIG) {
+ mDefaultDozeBrightness = BrightnessSynchronizer.brightnessIntToFloat(
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_screenBrightnessDoze));
+ }
+ }
+ }
+
private void validateIdleScreenRefreshRateTimeoutConfig(
IdleScreenRefreshRateTimeout idleScreenRefreshRateTimeoutConfig) {
IdleScreenRefreshRateTimeoutLuxThresholds idleScreenRefreshRateTimeoutLuxThresholds =
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2f3584c..b3a6c1c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4726,6 +4726,32 @@
DisplayManagerService.this.mDisplayModeDirector.requestDisplayModes(
token, displayId, modeIds);
}
+
+ @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Override // Binder call
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ getDozeBrightnessSensorValueToBrightness_enforcePermission();
+ DisplayDeviceConfig ddc =
+ mDisplayDeviceConfigProvider.getDisplayDeviceConfig(displayId);
+ if (ddc == null) {
+ throw new IllegalArgumentException(
+ "Display ID does not have a config: " + displayId);
+ }
+ return ddc.getDozeBrightnessSensorValueToBrightness();
+ }
+
+ @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Override // Binder call
+ public float getDefaultDozeBrightness(int displayId) {
+ getDefaultDozeBrightness_enforcePermission();
+ DisplayDeviceConfig ddc =
+ mDisplayDeviceConfigProvider.getDisplayDeviceConfig(displayId);
+ if (ddc == null) {
+ throw new IllegalArgumentException(
+ "Display ID does not have a config for doze-default: " + displayId);
+ }
+ return ddc.getDefaultDozeBrightness();
+ }
}
private static boolean isValidBrightness(float brightness) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 58309c2..5c1e783 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -272,7 +272,7 @@
private final SettingsObserver mSettingsObserver;
// The doze screen brightness.
- private final float mScreenBrightnessDozeConfig;
+ private float mScreenBrightnessDozeConfig;
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
@@ -550,7 +550,7 @@
// DOZE AND DIM SETTINGS
mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
+ mDisplayDeviceConfig.getDefaultDozeBrightness());
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
R.bool.config_skipScreenOnBrightnessRamp);
@@ -932,6 +932,8 @@
HighBrightnessModeMetadata hbmMetadata) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
+ mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
+ mDisplayDeviceConfig.getDefaultDozeBrightness());
loadBrightnessRampRates();
loadNitsRange(mContext.getResources());
setUpAutoBrightness(mContext, mHandler);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index d3b41b8..e9ecfc6 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -25,6 +25,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceState;
+import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.feature.flags.FeatureFlags;
import android.hardware.devicestate.feature.flags.FeatureFlagsImpl;
import android.os.Handler;
@@ -614,7 +615,11 @@
&& isBootCompleted
&& !mFoldSettingProvider.shouldStayAwakeOnFold();
} else {
- return mDeviceStatesOnWhichToSelectiveSleep.get(pendingState.getIdentifier())
+ return currentState.getIdentifier()
+ != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER
+ && pendingState.getIdentifier()
+ != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER
+ && mDeviceStatesOnWhichToSelectiveSleep.get(pendingState.getIdentifier())
&& !mDeviceStatesOnWhichToSelectiveSleep.get(currentState.getIdentifier())
&& isInteractive
&& isBootCompleted
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 3ce7d2a..e1934b0 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -154,6 +154,10 @@
Flags::useFusionProxSensor
);
+ private final FlagState mDozeBrightnessFloat = new FlagState(
+ Flags.FLAG_DOZE_BRIGHTNESS_FLOAT,
+ Flags::dozeBrightnessFloat);
+
private final FlagState mOffloadControlsDozeAutoBrightness = new FlagState(
Flags.FLAG_OFFLOAD_CONTROLS_DOZE_AUTO_BRIGHTNESS,
Flags::offloadControlsDozeAutoBrightness
@@ -347,6 +351,10 @@
return mUseFusionProxSensor.getName();
}
+ public boolean isDozeBrightnessFloatEnabled() {
+ return mDozeBrightnessFloat.isEnabled();
+ }
+
/**
* @return Whether DisplayOffload should control auto-brightness in doze
*/
@@ -415,6 +423,7 @@
pw.println(" " + mRefactorDisplayPowerController);
pw.println(" " + mResolutionBackupRestore);
pw.println(" " + mUseFusionProxSensor);
+ pw.println(" " + mDozeBrightnessFloat);
pw.println(" " + mOffloadControlsDozeAutoBrightness);
pw.println(" " + mPeakRefreshRatePhysicalLimit);
pw.println(" " + mIgnoreAppPreferredRefreshRate);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index fd3af23..ac5f97f 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -246,6 +246,14 @@
}
flag {
+ name: "doze_brightness_float"
+ namespace: "display_manager"
+ description: "Define doze brightness in the float scale [0, 1]."
+ bug: "343796384"
+ is_fixed_read_only: true
+}
+
+flag {
name: "offload_controls_doze_auto_brightness"
namespace: "display_manager"
description: "Allows the registered DisplayOffloader to control if auto-brightness is used in doze"
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 91ab872..8ca0458 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -18,20 +18,14 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
-import android.content.Context;
-import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.Process;
import android.util.IntArray;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.DirectBootAwareness;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
@@ -225,49 +219,17 @@
sWriter.startThread();
}
- static void initialize(@NonNull Handler ioHandler, @NonNull Context context) {
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- ioHandler.post(() -> {
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserCreated(UserInfo user, @Nullable Object token) {
- final int userId = user.id;
- sWriter.onUserCreated(userId);
- ioHandler.post(() -> {
- synchronized (ImfLock.class) {
- if (!sPerUserMap.contains(userId)) {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeUtils.load(userId);
- sPerUserMap.put(userId, additionalSubtypeMap);
- final InputMethodSettings settings =
- InputMethodManagerService
- .queryInputMethodServicesInternal(context,
- userId,
- additionalSubtypeMap,
- DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(userId, settings);
- }
- }
- });
- }
+ @AnyThread
+ static void onUserCreated(@UserIdInt int userId) {
+ sWriter.onUserCreated(userId);
+ }
- @Override
- public void onUserRemoved(UserInfo user) {
- final int userId = user.id;
- sWriter.onUserRemoved(userId);
- ioHandler.post(() -> {
- synchronized (ImfLock.class) {
- sPerUserMap.remove(userId);
- }
- });
- }
- });
+ @AnyThread
+ static void remove(@UserIdInt int userId, @NonNull Handler ioHandler) {
+ sWriter.onUserRemoved(userId);
+ ioHandler.post(() -> {
synchronized (ImfLock.class) {
- for (int userId : userManagerInternal.getUserIds()) {
- sPerUserMap.put(userId, AdditionalSubtypeUtils.load(userId));
- }
+ sPerUserMap.remove(userId);
}
});
}
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 5ff421a..7c93c8b 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -89,8 +89,8 @@
void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
@InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason, @UserIdInt int userId) {
- final var bindingController = mService.getInputMethodBindingController(userId);
final var userData = mService.getUserData(userId);
+ final var bindingController = userData.mBindingController;
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
if (DEBUG) {
@@ -128,9 +128,9 @@
void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason,
@UserIdInt int userId) {
- final var bindingController = mService.getInputMethodBindingController(userId);
- final IInputMethodInvoker curMethod = bindingController.getCurMethod();
final var userData = mService.getUserData(userId);
+ final var bindingController = userData.mBindingController;
+ final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
// The IME will report its visible state again after the following message finally
// delivered to the IME process as an IPC. Hence the inconsistency between
@@ -171,8 +171,8 @@
void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
@ImeVisibilityStateComputer.VisibilityState int state,
@SoftInputShowHideReason int reason, @UserIdInt int userId) {
- final var bindingController = mService.getInputMethodBindingController(userId);
final var userData = mService.getUserData(userId);
+ final var bindingController = userData.mBindingController;
final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
switch (state) {
case STATE_SHOW_IME:
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
index 3f28c47..a7280e6 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -138,6 +138,9 @@
@PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
boolean isInputMethodPickerShownForTest();
+ @PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ void onImeSwitchButtonClickFromSystem(int displayId);
+
InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId);
void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
@@ -344,6 +347,14 @@
return mCallback.isInputMethodPickerShownForTest();
}
+ @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Override
+ public void onImeSwitchButtonClickFromSystem(int displayId) {
+ super.onImeSwitchButtonClickFromSystem_enforcePermission();
+
+ mCallback.onImeSwitchButtonClickFromSystem(displayId);
+ }
+
@Override
public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
return mCallback.getCurrentInputMethodSubtype(userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 5ab493b..9837ab1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -408,7 +408,8 @@
InputMethodManager
.invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
}
- mService.initializeImeLocked(mCurMethod, mCurToken, mUserId);
+ mService.initializeImeLocked(mCurMethod, mCurToken,
+ InputMethodBindingController.this);
mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
mService.reRequestCurrentClientSessionLocked(mUserId);
mAutofillController.performOnCreateInlineSuggestionsRequest();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
new file mode 100644
index 0000000..b835d05
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static android.content.Intent.ACTION_OVERLAY_CHANGED;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.PatternMatcher;
+import android.os.UserHandle;
+import android.util.Slog;
+
+final class InputMethodDrawsNavBarResourceMonitor {
+ private static final String TAG = "InputMethodDrawsNavBarResourceMonitor";
+
+ private static final String SYSTEM_PACKAGE_NAME = "android";
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodDrawsNavBarResourceMonitor() {
+ }
+
+ @WorkerThread
+ static boolean evaluate(@NonNull Context context, @UserIdInt int userId) {
+ final Context userAwareContext;
+ if (context.getUserId() == userId) {
+ userAwareContext = context;
+ } else {
+ userAwareContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+ }
+ try {
+ return userAwareContext.getPackageManager()
+ .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
+ .getBoolean(com.android.internal.R.bool.config_imeDrawsImeNavBar);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed",
+ e);
+ return false;
+ }
+ }
+
+ @FunctionalInterface
+ interface OnUpdateCallback {
+ void onUpdate(@UserIdInt int userId);
+ }
+
+ @SuppressLint("MissingPermission")
+ @AnyThread
+ static void registerCallback(@NonNull Context context, @NonNull Handler ioHandler,
+ @NonNull OnUpdateCallback callback) {
+ final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+ intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+ intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
+
+ final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int userId = getSendingUserId();
+ callback.onUpdate(userId);
+ }
+ };
+ context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, intentFilter,
+ null /* broadcastPermission */, ioHandler, Context.RECEIVER_NOT_EXPORTED);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7daf958..8fd2033 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -53,6 +53,7 @@
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
+import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_AUTO;
import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -67,6 +68,7 @@
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -169,14 +171,12 @@
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
-import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.input.InputManagerInternal;
@@ -202,7 +202,6 @@
import java.util.OptionalInt;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.IntFunction;
@@ -399,15 +398,6 @@
@SharedByAllUsersField
private IntArray mStylusIds;
- @GuardedBy("ImfLock.class")
- @Nullable
- @MultiUserUnawareField
- private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes;
- @GuardedBy("ImfLock.class")
- @Nullable
- @MultiUserUnawareField
- Future<?> mImeDrawsImeNavBarResLazyInitFuture;
-
private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
/**
* {@inheritDoc}
@@ -484,13 +474,13 @@
@SharedByAllUsersField
boolean mSystemReady;
- @GuardedBy("ImfLock.class")
+ @AnyThread
@NonNull
- UserDataRepository.UserData getUserData(@UserIdInt int userId) {
+ UserData getUserData(@UserIdInt int userId) {
return mUserDataRepository.getOrCreate(userId);
}
- @GuardedBy("ImfLock.class")
+ @AnyThread
@NonNull
InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
return getUserData(userId).mBindingController;
@@ -933,6 +923,14 @@
// For production code, hook up user lifecycle
mService.mUserManagerInternal.addUserLifecycleListener(this);
+
+ // Hook up resource change first before initializeUsersAsync() starts reading the
+ // seemingly initial data so that we can eliminate the race condition.
+ InputMethodDrawsNavBarResourceMonitor.registerCallback(context, mService.mIoHandler,
+ mService::onUpdateResourceOverlay);
+
+ // Also schedule user init tasks onto an I/O thread.
+ initializeUsersAsync(mService.mUserManagerInternal.getUserIds());
}
@VisibleForTesting
@@ -1015,6 +1013,9 @@
@Override
public void onUserCreated(UserInfo user, @Nullable Object token) {
// Called directly from UserManagerService. Do not block the calling thread.
+ final int userId = user.id;
+ AdditionalSubtypeMapRepository.onUserCreated(userId);
+ initializeUsersAsync(new int[userId]);
}
@Override
@@ -1022,6 +1023,8 @@
// Called directly from UserManagerService. Do not block the calling thread.
final int userId = user.id;
SecureSettingsWrapper.onUserRemoved(userId);
+ AdditionalSubtypeMapRepository.remove(userId, mService.mIoHandler);
+ InputMethodSettingsRepository.remove(userId);
mService.mUserDataRepository.remove(userId);
}
@@ -1049,6 +1052,44 @@
});
}
+ @AnyThread
+ private void initializeUsersAsync(@UserIdInt int[] userIds) {
+ mService.mIoHandler.post(() -> {
+ final var service = mService;
+ final var context = service.mContext;
+ final var userManagerInternal = service.mUserManagerInternal;
+
+ // We first create InputMethodMap for each user without loading AdditionalSubtypes.
+ final int numUsers = userIds.length;
+ final InputMethodMap[] rawMethodMaps = new InputMethodMap[numUsers];
+ for (int i = 0; i < numUsers; ++i) {
+ final int userId = userIds[i];
+ rawMethodMaps[i] = InputMethodManagerService.queryInputMethodServicesInternal(
+ context, userId, AdditionalSubtypeMap.EMPTY_MAP,
+ DirectBootAwareness.AUTO).getMethodMap();
+ final int profileParentId = userManagerInternal.getProfileParentId(userId);
+ final boolean value =
+ InputMethodDrawsNavBarResourceMonitor.evaluate(context,
+ profileParentId);
+ final var userData = mService.getUserData(userId);
+ userData.mImeDrawsNavBar.set(value);
+ }
+
+ // Then create full InputMethodMap for each user. Note that
+ // AdditionalSubtypeMapRepository#get() and InputMethodSettingsRepository#put()
+ // need to be called with ImfLock held (b/352387655).
+ // TODO(b/343601565): Avoid ImfLock after fixing b/352387655.
+ synchronized (ImfLock.class) {
+ for (int i = 0; i < numUsers; ++i) {
+ final int userId = userIds[i];
+ final var map = AdditionalSubtypeMapRepository.get(userId);
+ final var methodMap = rawMethodMaps[i].applyAdditionalSubtypes(map);
+ final var settings = InputMethodSettings.create(methodMap, userId);
+ InputMethodSettingsRepository.put(userId, settings);
+ }
+ }
+ });
+ }
}
void onUnlockUser(@UserIdInt int userId) {
@@ -1121,16 +1162,6 @@
mShowOngoingImeSwitcherForPhones = false;
- // Executing InputMethodSettingsRepository.initialize() does not mean that it
- // immediately becomes ready to return the up-to-date InputMethodSettings for each
- // running user, because we want to return from the constructor as early as possible so
- // as not to delay the system boot process.
- // Search for InputMethodSettingsRepository.put() to find where and when it's actually
- // being updated. In general IMMS should refrain from exposing the existence of IMEs
- // until systemReady().
- InputMethodSettingsRepository.initialize(mIoHandler, mContext);
- AdditionalSubtypeMapRepository.initialize(mIoHandler, mContext);
-
mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
@SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
bindingControllerFactory = userId -> new InputMethodBindingController(userId,
@@ -1216,36 +1247,6 @@
setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false, userId);
}
- @GuardedBy("ImfLock.class")
- private void maybeInitImeNavbarConfigLocked(@UserIdInt int targetUserId) {
- // Currently, com.android.internal.R.bool.config_imeDrawsImeNavBar is overlaid only for the
- // profile parent user.
- // TODO(b/221443458): See if we can make OverlayManager be aware of profile groups.
- final int profileParentUserId = mUserManagerInternal.getProfileParentId(targetUserId);
- if (mImeDrawsImeNavBarRes != null
- && mImeDrawsImeNavBarRes.getUserId() != profileParentUserId) {
- mImeDrawsImeNavBarRes.close();
- mImeDrawsImeNavBarRes = null;
- }
- if (mImeDrawsImeNavBarRes == null) {
- final Context userContext;
- if (mContext.getUserId() == profileParentUserId) {
- userContext = mContext;
- } else {
- userContext = mContext.createContextAsUser(UserHandle.of(profileParentUserId),
- 0 /* flags */);
- }
- mImeDrawsImeNavBarRes = OverlayableSystemBooleanResourceWrapper.create(userContext,
- com.android.internal.R.bool.config_imeDrawsImeNavBar, mHandler, resource -> {
- synchronized (ImfLock.class) {
- if (resource == mImeDrawsImeNavBarRes) {
- sendOnNavButtonFlagsChangedLocked();
- }
- }
- });
- }
- }
-
@NonNull
private static PackageManager getPackageManagerForUser(@NonNull Context context,
@UserIdInt int userId) {
@@ -1278,8 +1279,6 @@
// Hereafter we start initializing things for "newUserId".
- maybeInitImeNavbarConfigLocked(newUserId);
-
final var newUserData = getUserData(newUserId);
// TODO(b/342027196): Double check if we need to always reset upon user switching.
@@ -1358,23 +1357,6 @@
});
}
- // TODO(b/32343335): The entire systemRunning() method needs to be revisited.
- mImeDrawsImeNavBarResLazyInitFuture = SystemServerInitThreadPool.submit(() -> {
- // Note that the synchronization block below guarantees that the task
- // can never be completed before the returned Future<?> object is assigned to
- // the "mImeDrawsImeNavBarResLazyInitFuture" field.
- synchronized (ImfLock.class) {
- mImeDrawsImeNavBarResLazyInitFuture = null;
- if (currentUserId != mCurrentUserId) {
- // This means that the current user is already switched to other user
- // before the background task is executed. In this scenario the relevant
- // field should already be initialized.
- return;
- }
- maybeInitImeNavbarConfigLocked(currentUserId);
- }
- }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
-
mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler);
SecureSettingsChangeCallback.register(mHandler, mContext.getContentResolver(),
new String[] {
@@ -1410,9 +1392,7 @@
getPackageManagerForUser(mContext, currentUserId),
newSettings.getEnabledInputMethodList());
- final var unused = SystemServerInitThreadPool.submit(
- AdditionalSubtypeMapRepository::startWriterThread,
- "Start AdditionalSubtypeMapRepository's writer thread");
+ AdditionalSubtypeMapRepository.startWriterThread();
if (mConcurrentMultiUserModeEnabled) {
for (int userId : mUserManagerInternal.getUserIds()) {
@@ -1442,15 +1422,15 @@
* Returns true iff the caller is identified to be the current input method with the token.
*
* @param token the window token given to the input method when it was started
- * @param userId userId of the calling IME process
+ * @param userData {@link UserData} of the calling IME process
* @return true if and only if non-null valid token is specified
*/
@GuardedBy("ImfLock.class")
- private boolean calledWithValidTokenLocked(@NonNull IBinder token, @UserIdInt int userId) {
+ private boolean calledWithValidTokenLocked(@NonNull IBinder token, @NonNull UserData userData) {
if (token == null) {
throw new InvalidParameterException("token must not be null.");
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
if (token != bindingController.getCurToken()) {
Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
+ " uid:" + Binder.getCallingUid() + " token:" + token);
@@ -1729,7 +1709,7 @@
clearClientSessionLocked(client);
clearClientSessionForAccessibilityLocked(client);
// TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
- @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> clientRemovedForUser =
+ @SuppressWarnings("GuardedBy") Consumer<UserData> clientRemovedForUser =
userData -> onClientRemovedInternalLocked(client, userData);
mUserDataRepository.forAllUserData(clientRemovedForUser);
}
@@ -1739,15 +1719,14 @@
*/
// TODO(b/325515685): Move this method to InputMethodBindingController
@GuardedBy("ImfLock.class")
- private void onClientRemovedInternalLocked(ClientState client,
- @NonNull UserDataRepository.UserData userData) {
+ private void onClientRemovedInternalLocked(ClientState client, @NonNull UserData userData) {
final int userId = userData.mUserId;
if (userData.mCurClient == client) {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId);
if (userData.mBoundToMethod) {
userData.mBoundToMethod = false;
- final var userBindingController = getInputMethodBindingController(userId);
+ final var userBindingController = userData.mBindingController;
IInputMethodInvoker curMethod = userBindingController.getCurMethod();
if (curMethod != null) {
// When we unbind input, we are unbinding the client, so we always
@@ -1779,7 +1758,7 @@
Slog.v(TAG, "unbindCurrentInputLocked: client="
+ userData.mCurClient.mClient.asBinder());
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
if (userData.mBoundToMethod) {
userData.mBoundToMethod = false;
IInputMethodInvoker curMethod = bindingController.getCurMethod();
@@ -1865,8 +1844,8 @@
@NonNull
InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial,
@UserIdInt int userId) {
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
if (!userData.mBoundToMethod) {
bindingController.getCurMethod().bindInput(userData.mCurClient.mBinding);
userData.mBoundToMethod = true;
@@ -1896,7 +1875,8 @@
userData.mCurClient.mUid, true /* direct */);
}
- @InputMethodNavButtonFlags final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
+ @InputMethodNavButtonFlags final int navButtonFlags =
+ getInputMethodNavButtonFlagsLocked(userData);
final SessionState session = userData.mCurClient.mCurSession;
setEnabledSessionLocked(session, userData);
session.mMethod.startInput(startInputToken, userData.mCurInputConnection,
@@ -2288,15 +2268,16 @@
@GuardedBy("ImfLock.class")
void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
- @UserIdInt int userId) {
+ @NonNull InputMethodBindingController bindingController) {
if (DEBUG) {
Slog.v(TAG, "Sending attach of token: " + token + " for display: "
- + getInputMethodBindingController(userId).getCurTokenDisplayId());
+ + bindingController.getCurTokenDisplayId());
}
+ final int userId = bindingController.getUserId();
+ final var userData = getUserData(userId);
inputMethod.initializeInternal(token,
- new InputMethodPrivilegedOperationsImpl(this, token, userId),
- // TODO(b/345519864): Make getInputMethodNavButtonFlagsLocked() multi-user aware
- getInputMethodNavButtonFlagsLocked());
+ new InputMethodPrivilegedOperationsImpl(this, token, userData),
+ getInputMethodNavButtonFlagsLocked(userData));
}
@AnyThread
@@ -2336,8 +2317,8 @@
channel.dispose();
return;
}
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null && method != null
&& curMethod.asBinder() == method.asBinder()) {
@@ -2550,9 +2531,10 @@
@BinderThread
private void updateStatusIcon(@NonNull IBinder token, String packageName,
- @DrawableRes int iconId, @UserIdInt int userId) {
+ @DrawableRes int iconId, @NonNull UserData userData) {
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
final long ident = Binder.clearCallingIdentity();
@@ -2595,23 +2577,16 @@
@GuardedBy("ImfLock.class")
@InputMethodNavButtonFlags
- private int getInputMethodNavButtonFlagsLocked() {
- // TODO(b/345519864): Make mImeDrawsImeNavBarRes multi-user aware.
- final int userId = mCurrentUserId;
- final var bindingController = getInputMethodBindingController(userId);
- if (mImeDrawsImeNavBarResLazyInitFuture != null) {
- // TODO(b/225366708): Avoid Future.get(), which is internally used here.
- ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture,
- "Waiting for the lazy init of mImeDrawsImeNavBarRes");
- }
+ private int getInputMethodNavButtonFlagsLocked(@NonNull UserData userData) {
+ final int userId = userData.mUserId;
+ final var bindingController = userData.mBindingController;
// Whether the current display has a navigation bar. When this is false (e.g. emulator),
// the IME should not draw the IME navigation bar.
final int tokenDisplayId = bindingController.getCurTokenDisplayId();
final boolean hasNavigationBar = mWindowManagerInternal
.hasNavigationBar(tokenDisplayId != INVALID_DISPLAY
? tokenDisplayId : DEFAULT_DISPLAY);
- final boolean canImeDrawsImeNavBar =
- mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
+ final boolean canImeDrawsImeNavBar = userData.mImeDrawsNavBar.get() && hasNavigationBar;
final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE, userId);
return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0)
@@ -2697,14 +2672,15 @@
@BinderThread
@SuppressWarnings("deprecation")
private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId();
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
// Skip update IME status when current token display is not same as focused display.
// Note that we still need to update IME status when focusing external display
// that does not support system decoration and fallback to show IME on default
@@ -2736,9 +2712,9 @@
@BinderThread
private void reportStartInput(@NonNull IBinder token, IBinder startInputToken,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
@@ -2772,8 +2748,8 @@
@GuardedBy("ImfLock.class")
private void updateSystemUiLocked(int vis, int backDisposition, @UserIdInt int userId) {
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
final var curToken = bindingController.getCurToken();
if (curToken == null) {
return;
@@ -2871,11 +2847,11 @@
settings.putSelectedInputMethod(id);
}
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
bindingController.setSelectedMethodId(id);
// Also re-initialize controllers.
- final var userData = getUserData(userId);
userData.mSwitchingController.resetCircularListLocked(mContext, settings);
userData.mHardwareKeyboardShortcutController.update(settings);
}
@@ -2912,7 +2888,8 @@
}
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
if (bindingController.getDeviceIdToShowIme() == DEVICE_ID_DEFAULT) {
String ime = SecureSettingsWrapper.getString(
Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
@@ -2952,10 +2929,9 @@
resetCurrentMethodAndClientLocked(UnbindReason.NO_IME, userId);
}
- final var userData = getUserData(userId);
userData.mSwitchingController.resetCircularListLocked(mContext, settings);
userData.mHardwareKeyboardShortcutController.update(settings);
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedLocked(userData);
}
@GuardedBy("ImfLock.class")
@@ -3434,8 +3410,8 @@
mVisibilityStateComputer.requestImeVisibility(windowToken, true);
// Ensure binding the connection when IME is going to show.
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
bindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
ImeTracker.forLogging().onCancelled(userData.mCurStatsToken,
@@ -3568,7 +3544,8 @@
boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
@InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason, @UserIdInt int userId) {
- final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) {
return false;
}
@@ -3581,7 +3558,6 @@
// since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only
// IMMS#InputShown indicates that the software keyboard is shown.
// TODO(b/246309664): Clean up IMMS#mImeWindowVis
- final var userData = getUserData(userId);
IInputMethodInvoker curMethod = bindingController.getCurMethod();
final boolean shouldHideSoftInput = curMethod != null
&& (isInputShownLocked()
@@ -3661,6 +3637,7 @@
Slog.w(TAG, "User #" + userId + " is not running.");
return InputBindResult.INVALID_USER;
}
+ final var userData = getUserData(userId);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"IMMS.startInputOrWindowGainedFocus");
@@ -3668,7 +3645,7 @@
"InputMethodManagerService#startInputOrWindowGainedFocus", mDumper);
final InputBindResult result;
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
// If the system is not yet ready, we shouldn't be running third party code.
if (!mSystemReady) {
return new InputBindResult(
@@ -3733,7 +3710,6 @@
final boolean shouldClearFlag =
mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
final boolean showForced = mVisibilityStateComputer.mShowForced;
- final var userData = getUserData(userId);
if (userData.mImeBindingState.mFocusedWindow != windowToken
&& showForced && shouldClearFlag) {
mVisibilityStateComputer.mShowForced = false;
@@ -3993,6 +3969,25 @@
}
}
+ @BinderThread
+ private void onImeSwitchButtonClickFromClient(@NonNull IBinder token, int displayId,
+ @NonNull UserData userData) {
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(token, userData)) {
+ return;
+ }
+ showInputMethodPickerFromSystem(
+ InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES, displayId);
+ }
+ }
+
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Override
+ public void onImeSwitchButtonClickFromSystem(int displayId) {
+ showInputMethodPickerFromSystem(
+ InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES, displayId);
+ }
+
@NonNull
private static IllegalArgumentException getExceptionForUnknownImeId(
@Nullable String imeId) {
@@ -4000,10 +3995,11 @@
}
@BinderThread
- private void setInputMethod(@NonNull IBinder token, String id, @UserIdInt int userId) {
+ private void setInputMethod(@NonNull IBinder token, String id, @NonNull UserData userData) {
final int callingUid = Binder.getCallingUid();
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
@@ -4018,10 +4014,11 @@
@BinderThread
private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
- InputMethodSubtype subtype, @UserIdInt int userId) {
+ InputMethodSubtype subtype, @NonNull UserData userData) {
final int callingUid = Binder.getCallingUid();
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
@@ -4034,18 +4031,20 @@
setInputMethodWithSubtypeIdLocked(token, id,
SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()), userId);
} else {
- setInputMethod(token, id, userId);
+ setInputMethod(token, id, userData);
}
}
}
@BinderThread
- private boolean switchToPreviousInputMethod(@NonNull IBinder token, @UserIdInt int userId) {
+ private boolean switchToPreviousInputMethod(@NonNull IBinder token,
+ @NonNull UserData userData) {
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return false;
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
final InputMethodInfo lastImi;
@@ -4122,43 +4121,45 @@
@BinderThread
private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return false;
}
- return switchToNextInputMethodLocked(token, onlyCurrentIme, userId);
+ return switchToNextInputMethodLocked(token, onlyCurrentIme, userData);
}
}
@GuardedBy("ImfLock.class")
private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme,
- @UserIdInt int userId) {
- final var bindingController = getInputMethodBindingController(userId);
+ @NonNull UserData userData) {
+ final var bindingController = userData.mBindingController;
final var currentImi = bindingController.getSelectedMethod();
- final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
+ final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
.getNextInputMethodLocked(onlyCurrentIme, currentImi,
- bindingController.getCurrentSubtype());
+ bindingController.getCurrentSubtype(),
+ MODE_AUTO, true /* forward */);
if (nextSubtype == null) {
return false;
}
setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
- nextSubtype.mSubtypeId, userId);
+ nextSubtype.mSubtypeId, userData.mUserId);
return true;
}
@BinderThread
private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return false;
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
final var currentImi = bindingController.getSelectedMethod();
- final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
+ final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
.getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
- bindingController.getCurrentSubtype());
+ bindingController.getCurrentSubtype(),
+ MODE_AUTO, true /* forward */);
return nextSubtype != null;
}
}
@@ -4556,8 +4557,8 @@
private void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (ImfLock.class) {
final int userId = mCurrentUserId;
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
final long token = proto.start(fieldId);
proto.write(CUR_METHOD_ID, bindingController.getSelectedMethodId());
proto.write(CUR_SEQ, bindingController.getSequenceNumber());
@@ -4588,12 +4589,12 @@
}
@BinderThread
- private void notifyUserAction(@NonNull IBinder token, @UserIdInt int userId) {
+ private void notifyUserAction(@NonNull IBinder token, @NonNull UserData userData) {
if (DEBUG) {
Slog.d(TAG, "Got the notification of a user action.");
}
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
if (bindingController.getCurToken() != token) {
if (DEBUG) {
Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer"
@@ -4603,7 +4604,7 @@
}
final InputMethodInfo imi = bindingController.getSelectedMethod();
if (imi != null) {
- getUserData(userId).mSwitchingController.onUserActionLocked(imi,
+ userData.mSwitchingController.onUserActionLocked(imi,
bindingController.getCurrentSubtype());
}
}
@@ -4611,11 +4612,12 @@
@BinderThread
private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
- @NonNull ImeTracker.Token statsToken, @UserIdInt int userId) {
+ @NonNull ImeTracker.Token statsToken, @NonNull UserData userData) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
return;
@@ -4690,8 +4692,8 @@
@UserIdInt int userId) {
final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken,
userId);
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
final WindowManagerInternal.ImeTargetInfo info =
mWindowManagerInternal.onToggleImeRequested(
show, userData.mImeBindingState.mFocusedWindow, requestToken,
@@ -4711,16 +4713,16 @@
@BinderThread
private void hideMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
@InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
return;
}
- final var userData = getUserData(userId);
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
final long ident = Binder.clearCallingIdentity();
@@ -4750,16 +4752,16 @@
@BinderThread
private void showMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
@InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
return;
}
- final var userData = getUserData(userId);
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
final long ident = Binder.clearCallingIdentity();
@@ -4804,8 +4806,7 @@
}
@GuardedBy("ImfLock.class")
- void setEnabledSessionLocked(SessionState session,
- @NonNull UserDataRepository.UserData userData) {
+ void setEnabledSessionLocked(SessionState session, @NonNull UserData userData) {
if (userData.mEnabledSession != session) {
if (userData.mEnabledSession != null && userData.mEnabledSession.mSession != null) {
if (DEBUG) Slog.v(TAG, "Disabling: " + userData.mEnabledSession);
@@ -4824,7 +4825,7 @@
@GuardedBy("ImfLock.class")
void setEnabledSessionForAccessibilityLocked(
SparseArray<AccessibilitySessionState> accessibilitySessions,
- @NonNull UserDataRepository.UserData userData) {
+ @NonNull UserData userData) {
// mEnabledAccessibilitySessions could the same object as accessibilitySessions.
SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>();
for (int i = 0; i < userData.mEnabledAccessibilitySessions.size(); i++) {
@@ -4979,7 +4980,7 @@
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
synchronized (ImfLock.class) {
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedToAllImesLocked();
}
return true;
case MSG_SYSTEM_UNLOCK_USER: {
@@ -5033,9 +5034,8 @@
case MSG_START_HANDWRITING:
final var handwritingRequest = (HandwritingRequest) msg.obj;
synchronized (ImfLock.class) {
- final int userId = handwritingRequest.userId;
- final var bindingController = getInputMethodBindingController(userId);
- final var userData = getUserData(userId);
+ final var userData = handwritingRequest.userData;
+ final var bindingController = userData.mBindingController;
IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod == null || userData.mImeBindingState.mFocusedWindow == null) {
return true;
@@ -5080,24 +5080,24 @@
return false;
}
- private record HandwritingRequest(int requestId, int pid, @UserIdInt int userId) { }
+ private record HandwritingRequest(int requestId, int pid, @NonNull UserData userData) { }
@BinderThread
- private void onStylusHandwritingReady(int requestId, int pid, @UserIdInt int userId) {
+ private void onStylusHandwritingReady(int requestId, int pid, @NonNull UserData userData) {
mHandler.obtainMessage(MSG_START_HANDWRITING,
- new HandwritingRequest(requestId, pid, userId)).sendToTarget();
+ new HandwritingRequest(requestId, pid, userData)).sendToTarget();
}
private void handleSetInteractive(final boolean interactive) {
synchronized (ImfLock.class) {
// TODO(b/305849394): Support multiple IMEs.
final int userId = mCurrentUserId;
- final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
mIsInteractive = interactive;
updateSystemUiLocked(
interactive ? bindingController.getImeWindowVis() : 0,
bindingController.getBackDisposition(), userId);
- final var userData = getUserData(userId);
// Inform the current client of the change in active status
if (userData.mCurClient == null || userData.mCurClient.mClient == null) {
return;
@@ -5313,7 +5313,7 @@
userData.mSwitchingController.resetCircularListLocked(mContext, settings);
userData.mHardwareKeyboardShortcutController.update(settings);
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedLocked(userData);
// Notify InputMethodListListeners of the new installed InputMethods.
final List<InputMethodInfo> inputMethodList = settings.getMethodList();
@@ -5322,14 +5322,38 @@
}
@GuardedBy("ImfLock.class")
- void sendOnNavButtonFlagsChangedLocked() {
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ void sendOnNavButtonFlagsChangedToAllImesLocked() {
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ sendOnNavButtonFlagsChangedLocked(getUserData(userId));
+ }
+ }
+
+ @GuardedBy("ImfLock.class")
+ void sendOnNavButtonFlagsChangedLocked(@NonNull UserData userData) {
+ final var bindingController = userData.mBindingController;
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod == null) {
// No need to send the data if the IME is not yet bound.
return;
}
- curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked());
+ curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked(userData));
+ }
+
+ @WorkerThread
+ private void onUpdateResourceOverlay(@UserIdInt int userId) {
+ final int profileParentId = mUserManagerInternal.getProfileParentId(userId);
+ final boolean value =
+ InputMethodDrawsNavBarResourceMonitor.evaluate(mContext, profileParentId);
+ final var profileUserIds = mUserManagerInternal.getProfileIds(profileParentId, false);
+ final ArrayList<UserData> updatedUsers = new ArrayList<>();
+ for (int profileUserId : profileUserIds) {
+ final var userData = getUserData(profileUserId);
+ userData.mImeDrawsNavBar.set(value);
+ updatedUsers.add(userData);
+ }
+ synchronized (ImfLock.class) {
+ updatedUsers.forEach(this::sendOnNavButtonFlagsChangedLocked);
+ }
}
@GuardedBy("ImfLock.class")
@@ -5459,6 +5483,10 @@
// Set InputMethod here
settings.putSelectedInputMethod(imi != null ? imi.getId() : "");
}
+
+ if (Flags.imeSwitcherRevamp()) {
+ getUserData(userId).mSwitchingController.onInputMethodSubtypeChanged();
+ }
}
@GuardedBy("ImfLock.class")
@@ -5570,20 +5598,38 @@
}
@GuardedBy("ImfLock.class")
- private void switchKeyboardLayoutLocked(int direction, @UserIdInt int userId) {
+ private void switchKeyboardLayoutLocked(int direction, @NonNull UserData userData) {
+ final int userId = userData.mUserId;
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
final InputMethodInfo currentImi = settings.getMethodMap().get(
bindingController.getSelectedMethodId());
if (currentImi == null) {
return;
}
- final InputMethodSubtypeHandle currentSubtypeHandle =
- InputMethodSubtypeHandle.of(currentImi, bindingController.getCurrentSubtype());
- final InputMethodSubtypeHandle nextSubtypeHandle =
- getUserData(userId).mHardwareKeyboardShortcutController.onSubtypeSwitch(
+ final var currentSubtype = bindingController.getCurrentSubtype();
+ final InputMethodSubtypeHandle nextSubtypeHandle;
+ if (Flags.imeSwitcherRevamp()) {
+ final var nextItem = userData.mSwitchingController
+ .getNextInputMethodForHardware(
+ false /* onlyCurrentIme */, currentImi, currentSubtype, MODE_AUTO,
+ direction > 0 /* forward */);
+ if (nextItem == null) {
+ Slog.i(TAG, "Hardware keyboard switching shortcut,"
+ + " next input method and subtype not found");
+ return;
+ }
+
+ final var nextSubtype = nextItem.mSubtypeId > NOT_A_SUBTYPE_ID
+ ? nextItem.mImi.getSubtypeAt(nextItem.mSubtypeId) : null;
+ nextSubtypeHandle = InputMethodSubtypeHandle.of(nextItem.mImi, nextSubtype);
+ } else {
+ final InputMethodSubtypeHandle currentSubtypeHandle =
+ InputMethodSubtypeHandle.of(currentImi, currentSubtype);
+ nextSubtypeHandle = userData.mHardwareKeyboardShortcutController.onSubtypeSwitch(
currentSubtypeHandle, direction > 0);
+ }
if (nextSubtypeHandle == null) {
return;
}
@@ -5729,7 +5775,7 @@
if (displayId != bindingController.getCurTokenDisplayId()) {
return false;
}
- curHostInputToken = getInputMethodBindingController(userId).getCurHostInputToken();
+ curHostInputToken = bindingController.getCurHostInputToken();
if (curHostInputToken == null) {
return false;
}
@@ -5785,8 +5831,8 @@
public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
IAccessibilityInputMethodSession session, @UserIdInt int userId) {
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
// TODO(b/305829876): Implement user ID verification
if (userData.mCurClient != null) {
clearClientSessionForAccessibilityLocked(userData.mCurClient,
@@ -5823,8 +5869,8 @@
public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
// TODO(b/305829876): Implement user ID verification
if (userData.mCurClient != null) {
if (DEBUG) {
@@ -5868,14 +5914,14 @@
IBinder targetWindowToken) {
synchronized (ImfLock.class) {
// TODO(b/305849394): Infer userId from displayId
- switchKeyboardLayoutLocked(direction, mCurrentUserId);
+ switchKeyboardLayoutLocked(direction, getUserData(mCurrentUserId));
}
}
}
@BinderThread
private IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
- @Nullable Uri contentUri, @Nullable String packageName, @UserIdInt int imeUserId) {
+ @Nullable Uri contentUri, @Nullable String packageName, @NonNull UserData userData) {
if (token == null) {
throw new NullPointerException("token");
}
@@ -5892,7 +5938,7 @@
synchronized (ImfLock.class) {
final int uid = Binder.getCallingUid();
- final var bindingController = getInputMethodBindingController(imeUserId);
+ final var bindingController = userData.mBindingController;
if (bindingController.getSelectedMethodId() == null) {
return null;
}
@@ -5904,7 +5950,6 @@
// We cannot simply distinguish a bad IME that reports an arbitrary package name from
// an unfortunate IME whose internal state is already obsolete due to the asynchronous
// nature of our system. Let's compare it with our internal record.
- final var userData = getUserData(imeUserId);
final var curPackageName = userData.mCurEditorInfo != null
? userData.mCurEditorInfo.packageName : null;
if (!TextUtils.equals(curPackageName, packageName)) {
@@ -5916,7 +5961,7 @@
final int appUserId = UserHandle.getUserId(userData.mCurClient.mUid);
// This user ID may be invalid if "contentUri" embedded an invalid user ID.
final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
- imeUserId);
+ userData.mUserId);
final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
// Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
// actually has the right to grant a read permission for "contentUriWithoutUserId" that
@@ -5931,12 +5976,11 @@
@BinderThread
private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
- final var userData = getUserData(userId);
if (userData.mCurClient != null && userData.mCurClient.mClient != null) {
userData.mInFullscreenMode = fullscreen;
userData.mCurClient.mClient.reportFullscreenMode(fullscreen);
@@ -6063,8 +6107,8 @@
p.println(" pid=" + c.mPid);
};
mClientController.forAllClients(clientControllerDump);
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
- p.println(" mCurrentUserId=" + mCurrentUserId);
+ final var bindingController = userData.mBindingController;
+ p.println(" mCurrentUserId=" + userData.mUserId);
p.println(" mCurMethodId=" + bindingController.getSelectedMethodId());
client = userData.mCurClient;
p.println(" mCurClient=" + client + " mCurSeq="
@@ -6079,7 +6123,7 @@
p.println(" mUserDataRepository=");
// TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
- @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> userDataDump =
+ @SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump =
u -> {
p.println(" mUserId=" + u.mUserId);
p.println(" hasMainConnection="
@@ -6097,6 +6141,7 @@
u.mImeBindingState.dump(" ", p);
p.println(" enabledSession=" + u.mEnabledSession);
p.println(" inFullscreenMode=" + u.mInFullscreenMode);
+ p.println(" imeDrawsNavBar=" + u.mImeDrawsNavBar.get());
p.println(" switchingController:");
u.mSwitchingController.dump(p, " ");
p.println(" mLastEnabledInputMethodsStr="
@@ -6615,7 +6660,7 @@
0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND, userId);
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
bindingController.unbindCurrentMethod();
// Enable default IMEs, disable others
@@ -6763,26 +6808,26 @@
private final InputMethodManagerService mImms;
@NonNull
private final IBinder mToken;
- @UserIdInt
- private final int mUserId;
+ @NonNull
+ private final UserData mUserData;
InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms,
- @NonNull IBinder token, @UserIdInt int userId) {
+ @NonNull IBinder token, @NonNull UserData userData) {
mImms = imms;
mToken = token;
- mUserId = userId;
+ mUserData = userData;
}
@BinderThread
@Override
public void setImeWindowStatusAsync(int vis, int backDisposition) {
- mImms.setImeWindowStatus(mToken, vis, backDisposition, mUserId);
+ mImms.setImeWindowStatus(mToken, vis, backDisposition, mUserData);
}
@BinderThread
@Override
public void reportStartInputAsync(IBinder startInputToken) {
- mImms.reportStartInput(mToken, startInputToken, mUserId);
+ mImms.reportStartInput(mToken, startInputToken, mUserData);
}
@BinderThread
@@ -6798,7 +6843,7 @@
@SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
try {
typedFuture.complete(mImms.createInputContentUriToken(
- mToken, contentUri, packageName, mUserId).asBinder());
+ mToken, contentUri, packageName, mUserData).asBinder());
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6807,7 +6852,7 @@
@BinderThread
@Override
public void reportFullscreenModeAsync(boolean fullscreen) {
- mImms.reportFullscreenMode(mToken, fullscreen, mUserId);
+ mImms.reportFullscreenMode(mToken, fullscreen, mUserData);
}
@BinderThread
@@ -6815,7 +6860,7 @@
public void setInputMethod(String id, AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.setInputMethod(mToken, id, mUserId);
+ mImms.setInputMethod(mToken, id, mUserData);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6828,7 +6873,7 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.setInputMethodAndSubtype(mToken, id, subtype, mUserId);
+ mImms.setInputMethodAndSubtype(mToken, id, subtype, mUserData);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6842,7 +6887,7 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.hideMySoftInput(mToken, statsToken, flags, reason, mUserId);
+ mImms.hideMySoftInput(mToken, statsToken, flags, reason, mUserData);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6856,7 +6901,7 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.showMySoftInput(mToken, statsToken, flags, reason, mUserId);
+ mImms.showMySoftInput(mToken, statsToken, flags, reason, mUserData);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6866,7 +6911,7 @@
@BinderThread
@Override
public void updateStatusIconAsync(String packageName, @DrawableRes int iconId) {
- mImms.updateStatusIcon(mToken, packageName, iconId, mUserId);
+ mImms.updateStatusIcon(mToken, packageName, iconId, mUserData);
}
@BinderThread
@@ -6874,7 +6919,7 @@
public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) {
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
- typedFuture.complete(mImms.switchToPreviousInputMethod(mToken, mUserId));
+ typedFuture.complete(mImms.switchToPreviousInputMethod(mToken, mUserData));
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6887,7 +6932,7 @@
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme,
- mUserId));
+ mUserData));
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6898,7 +6943,8 @@
public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) {
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
- typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken, mUserId));
+ typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken,
+ mUserData));
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6906,21 +6952,27 @@
@BinderThread
@Override
+ public void onImeSwitchButtonClickFromClient(int displayId) {
+ mImms.onImeSwitchButtonClickFromClient(mToken, displayId, mUserData);
+ }
+
+ @BinderThread
+ @Override
public void notifyUserActionAsync() {
- mImms.notifyUserAction(mToken, mUserId);
+ mImms.notifyUserAction(mToken, mUserData);
}
@BinderThread
@Override
public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
@NonNull ImeTracker.Token statsToken) {
- mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken, mUserId);
+ mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken, mUserData);
}
@BinderThread
@Override
public void onStylusHandwritingReady(int requestId, int pid) {
- mImms.onStylusHandwritingReady(requestId, pid, mUserId);
+ mImms.onStylusHandwritingReady(requestId, pid, mUserData);
}
@BinderThread
@@ -6933,12 +6985,12 @@
@Override
public void switchKeyboardLayoutAsync(int direction) {
synchronized (ImfLock.class) {
- if (!mImms.calledWithValidTokenLocked(mToken, mUserId)) {
+ if (!mImms.calledWithValidTokenLocked(mToken, mUserData)) {
return;
}
final long ident = Binder.clearCallingIdentity();
try {
- mImms.switchKeyboardLayoutLocked(direction, mUserId);
+ mImms.switchKeyboardLayoutLocked(direction, mUserData);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 656c87d..06f73f3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -202,7 +202,7 @@
attrs.setTitle("Select input method");
w.setAttributes(attrs);
mService.updateSystemUiLocked(userId);
- mService.sendOnNavButtonFlagsChangedLocked();
+ mService.sendOnNavButtonFlagsChangedLocked(mService.getUserData(userId));
mSwitchingDialog.show();
}
@@ -242,7 +242,7 @@
// TODO(b/305849394): Make InputMethodMenuController multi-user aware
final int userId = mService.getCurrentImeUserIdLocked();
mService.updateSystemUiLocked(userId);
- mService.sendOnNavButtonFlagsChangedLocked();
+ mService.sendOnNavButtonFlagsChangedToAllImesLocked();
mDialogBuilder = null;
mIms = null;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index a4d8ee5..50ba364 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -16,17 +16,12 @@
package com.android.server.inputmethod;
+import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.Handler;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.DirectBootAwareness;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
final class InputMethodSettingsRepository {
@GuardedBy("ImfLock.class")
@@ -54,33 +49,10 @@
sPerUserMap.put(userId, obj);
}
- static void initialize(@NonNull Handler ioHandler, @NonNull Context context) {
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- ioHandler.post(() -> {
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserRemoved(UserInfo user) {
- final int userId = user.id;
- ioHandler.post(() -> {
- synchronized (ImfLock.class) {
- sPerUserMap.remove(userId);
- }
- });
- }
- });
- synchronized (ImfLock.class) {
- for (int userId : userManagerInternal.getUserIds()) {
- final InputMethodSettings settings =
- InputMethodManagerService.queryInputMethodServicesInternal(
- context,
- userId,
- AdditionalSubtypeMapRepository.get(userId),
- DirectBootAwareness.AUTO);
- put(userId, settings);
- }
- }
- });
+ @AnyThread
+ static void remove(@UserIdInt int userId) {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index bb1b9df..05cc598 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -16,6 +16,8 @@
package com.android.server.inputmethod;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -24,11 +26,14 @@
import android.util.ArraySet;
import android.util.Printer;
import android.util.Slog;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -45,6 +50,34 @@
private static final boolean DEBUG = false;
private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+ @IntDef(prefix = {"MODE_"}, value = {
+ MODE_STATIC,
+ MODE_RECENT,
+ MODE_AUTO
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SwitchMode {
+ }
+
+ /**
+ * Switch using the static order (the order of the given list of input methods and subtypes).
+ * This order is only set when given a new list, and never updated.
+ */
+ public static final int MODE_STATIC = 0;
+
+ /**
+ * Switch using the recency based order, going from most recent to least recent,
+ * updated on {@link #onUserActionLocked user action}.
+ */
+ public static final int MODE_RECENT = 1;
+
+ /**
+ * If there was a {@link #onUserActionLocked user action} since the last
+ * {@link #onInputMethodSubtypeChanged() switch}, and direction is forward,
+ * use {@link #MODE_RECENT}, otherwise use {@link #MODE_STATIC}.
+ */
+ public static final int MODE_AUTO = 2;
+
public static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> {
@NonNull
@@ -117,20 +150,25 @@
if (result != 0) {
return result;
}
- // Subtype that has the same locale of the system's has higher priority.
- result = (mIsSystemLocale ? -1 : 0) - (other.mIsSystemLocale ? -1 : 0);
- if (result != 0) {
- return result;
+ if (!Flags.imeSwitcherRevamp()) {
+ // Subtype that has the same locale of the system's has higher priority.
+ result = (mIsSystemLocale ? -1 : 0) - (other.mIsSystemLocale ? -1 : 0);
+ if (result != 0) {
+ return result;
+ }
+ // Subtype that has the same language of the system's has higher priority.
+ result = (mIsSystemLanguage ? -1 : 0) - (other.mIsSystemLanguage ? -1 : 0);
+ if (result != 0) {
+ return result;
+ }
+ result = compareNullableCharSequences(mSubtypeName, other.mSubtypeName);
+ if (result != 0) {
+ return result;
+ }
}
- // Subtype that has the same language of the system's has higher priority.
- result = (mIsSystemLanguage ? -1 : 0) - (other.mIsSystemLanguage ? -1 : 0);
- if (result != 0) {
- return result;
- }
- result = compareNullableCharSequences(mSubtypeName, other.mSubtypeName);
- if (result != 0) {
- return result;
- }
+ // This will no longer compare by subtype name, however as {@link Collections.sort} is
+ // guaranteed to be a stable sorting, this allows sorting by the IME name (and ID),
+ // while maintaining the order of subtypes (given by each IME) at the IME level.
return mImi.getId().compareTo(other.mImi.getId());
}
@@ -226,6 +264,59 @@
return imList;
}
+ @NonNull
+ private static List<ImeSubtypeListItem> getInputMethodAndSubtypeListForHardwareKeyboard(
+ @NonNull Context context, @NonNull InputMethodSettings settings) {
+ if (!Flags.imeSwitcherRevamp()) {
+ return new ArrayList<>();
+ }
+ final int userId = settings.getUserId();
+ final Context userAwareContext = context.getUserId() == userId
+ ? context
+ : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+ final String mSystemLocaleStr = SystemLocaleWrapper.get(userId).get(0).toLanguageTag();
+
+ final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodList();
+ if (imis.isEmpty()) {
+ Slog.w(TAG, "Enabled input method list is empty.");
+ return new ArrayList<>();
+ }
+
+ final ArrayList<ImeSubtypeListItem> imList = new ArrayList<>();
+ final int numImes = imis.size();
+ for (int i = 0; i < numImes; ++i) {
+ final InputMethodInfo imi = imis.get(i);
+ if (!imi.shouldShowInInputMethodPicker()) {
+ continue;
+ }
+ final var subtypes = settings.getEnabledInputMethodSubtypeList(imi, true);
+ final ArraySet<InputMethodSubtype> enabledSubtypeSet = new ArraySet<>(subtypes);
+ final CharSequence imeLabel = imi.loadLabel(userAwareContext.getPackageManager());
+ if (!subtypes.isEmpty()) {
+ final int subtypeCount = imi.getSubtypeCount();
+ if (DEBUG) {
+ Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
+ }
+ for (int j = 0; j < subtypeCount; j++) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(j);
+ if (enabledSubtypeSet.contains(subtype)
+ && subtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
+ final CharSequence subtypeLabel =
+ subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
+ .getDisplayName(userAwareContext, imi.getPackageName(),
+ imi.getServiceInfo().applicationInfo);
+ imList.add(new ImeSubtypeListItem(imeLabel,
+ subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+ }
+ }
+ } else {
+ imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+ mSystemLocaleStr));
+ }
+ }
+ return imList;
+ }
+
private static int calculateSubtypeId(@NonNull InputMethodInfo imi,
@Nullable InputMethodSubtype subtype) {
return subtype != null ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
@@ -385,6 +476,132 @@
}
}
+ /**
+ * List container that allows getting the next item in either forwards or backwards direction,
+ * in either static or recency order, and either in the same IME or not.
+ */
+ private static class RotationList {
+
+ /**
+ * List of items in a static order.
+ */
+ @NonNull
+ private final List<ImeSubtypeListItem> mItems;
+
+ /**
+ * Mapping of recency index to static index (in {@link #mItems}), with lower indices being
+ * more recent.
+ */
+ @NonNull
+ private final int[] mRecencyMap;
+
+ RotationList(@NonNull List<ImeSubtypeListItem> items) {
+ mItems = items;
+ mRecencyMap = new int[items.size()];
+ for (int i = 0; i < mItems.size(); i++) {
+ mRecencyMap[i] = i;
+ }
+ }
+
+ /**
+ * Gets the next input method and subtype from the given ones.
+ *
+ * @param imi the input method to find the next value from.
+ * @param subtype the input method subtype to find the next value from, if any.
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param useRecency whether to use the recency order, or the static order.
+ * @param forward whether to search forwards to backwards in the list.
+ * @return the next input method and subtype if found, otherwise {@code null}.
+ */
+ @Nullable
+ public ImeSubtypeListItem next(@NonNull InputMethodInfo imi,
+ @Nullable InputMethodSubtype subtype, boolean onlyCurrentIme,
+ boolean useRecency, boolean forward) {
+ final int size = mItems.size();
+ if (size <= 1) {
+ return null;
+ }
+ final int index = getIndex(imi, subtype, useRecency);
+ if (index < 0) {
+ return null;
+ }
+
+ final int incrementSign = (forward ? 1 : -1);
+
+ for (int i = 1; i < size; i++) {
+ final int nextIndex = (index + i * incrementSign + size) % size;
+ final int mappedIndex = useRecency ? mRecencyMap[nextIndex] : nextIndex;
+ final var nextItem = mItems.get(mappedIndex);
+ if (!onlyCurrentIme || nextItem.mImi.equals(imi)) {
+ return nextItem;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the given input method and subtype as the most recent one.
+ *
+ * @param imi the input method to set as the most recent.
+ * @param subtype the input method subtype to set as the most recent, if any.
+ * @return {@code true} if the recency was updated, otherwise {@code false}.
+ */
+ public boolean setMostRecent(@NonNull InputMethodInfo imi,
+ @Nullable InputMethodSubtype subtype) {
+ if (mItems.size() <= 1) {
+ return false;
+ }
+
+ final int recencyIndex = getIndex(imi, subtype, true /* useRecency */);
+ if (recencyIndex <= 0) {
+ // Already most recent or not found.
+ return false;
+ }
+ final int staticIndex = mRecencyMap[recencyIndex];
+ System.arraycopy(mRecencyMap, 0, mRecencyMap, 1, recencyIndex);
+ mRecencyMap[0] = staticIndex;
+ return true;
+ }
+
+ /**
+ * Gets the index of the given input method and subtype, in either recency or static order.
+ *
+ * @param imi the input method to get the index of.
+ * @param subtype the input method subtype to get the index of, if any.
+ * @param useRecency whether to get the index in the recency or static order.
+ * @return an index in either {@link #mItems} or {@link #mRecencyMap}, or {@code -1}
+ * if not found.
+ */
+ @IntRange(from = -1)
+ private int getIndex(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
+ boolean useRecency) {
+ final int subtypeIndex = calculateSubtypeId(imi, subtype);
+ for (int i = 0; i < mItems.size(); i++) {
+ final int mappedIndex = useRecency ? mRecencyMap[i] : i;
+ final var item = mItems.get(mappedIndex);
+ if (item.mImi.equals(imi) && item.mSubtypeId == subtypeIndex) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /** Dumps the state of the list into the given printer. */
+ private void dump(@NonNull Printer pw, @NonNull String prefix) {
+ pw.println(prefix + "Static order:");
+ for (int i = 0; i < mItems.size(); ++i) {
+ final var item = mItems.get(i);
+ pw.println(prefix + "i=" + i + " item=" + item);
+ }
+ pw.println(prefix + "Recency order:");
+ for (int i = 0; i < mRecencyMap.length; ++i) {
+ final int index = mRecencyMap[i];
+ final var item = mItems.get(index);
+ pw.println(prefix + "i=" + i + " item=" + item);
+ }
+ }
+ }
+
@VisibleForTesting
public static class ControllerImpl {
@@ -392,10 +609,23 @@
private final DynamicRotationList mSwitchingAwareRotationList;
@NonNull
private final StaticRotationList mSwitchingUnawareRotationList;
+ /** List of input methods and subtypes. */
+ @Nullable
+ private final RotationList mRotationList;
+ /** List of input methods and subtypes suitable for hardware keyboards. */
+ @Nullable
+ private final RotationList mHardwareRotationList;
+
+ /**
+ * Whether there was a user action since the last input method and subtype switch.
+ * Used to determine the switching behaviour for {@link #MODE_AUTO}.
+ */
+ private boolean mUserActionSinceSwitch;
@NonNull
public static ControllerImpl createFrom(@Nullable ControllerImpl currentInstance,
- @NonNull List<ImeSubtypeListItem> sortedEnabledItems) {
+ @NonNull List<ImeSubtypeListItem> sortedEnabledItems,
+ @NonNull List<ImeSubtypeListItem> hardwareKeyboardItems) {
final var switchingAwareImeSubtypes = filterImeSubtypeList(sortedEnabledItems,
true /* supportsSwitchingToNextInputMethod */);
final var switchingUnawareImeSubtypes = filterImeSubtypeList(sortedEnabledItems,
@@ -421,22 +651,55 @@
switchingUnawareRotationList = new StaticRotationList(switchingUnawareImeSubtypes);
}
- return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList);
+ final RotationList rotationList;
+ if (!Flags.imeSwitcherRevamp()) {
+ rotationList = null;
+ } else if (currentInstance != null && currentInstance.mRotationList != null
+ && Objects.equals(
+ currentInstance.mRotationList.mItems, sortedEnabledItems)) {
+ // Can reuse the current instance.
+ rotationList = currentInstance.mRotationList;
+ } else {
+ rotationList = new RotationList(sortedEnabledItems);
+ }
+
+ final RotationList hardwareRotationList;
+ if (!Flags.imeSwitcherRevamp()) {
+ hardwareRotationList = null;
+ } else if (currentInstance != null && currentInstance.mHardwareRotationList != null
+ && Objects.equals(
+ currentInstance.mHardwareRotationList.mItems, hardwareKeyboardItems)) {
+ // Can reuse the current instance.
+ hardwareRotationList = currentInstance.mHardwareRotationList;
+ } else {
+ hardwareRotationList = new RotationList(hardwareKeyboardItems);
+ }
+
+ return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList,
+ rotationList, hardwareRotationList);
}
private ControllerImpl(@NonNull DynamicRotationList switchingAwareRotationList,
- @NonNull StaticRotationList switchingUnawareRotationList) {
+ @NonNull StaticRotationList switchingUnawareRotationList,
+ @Nullable RotationList rotationList,
+ @Nullable RotationList hardwareRotationList) {
mSwitchingAwareRotationList = switchingAwareRotationList;
mSwitchingUnawareRotationList = switchingUnawareRotationList;
+ mRotationList = rotationList;
+ mHardwareRotationList = hardwareRotationList;
}
@Nullable
public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme,
- @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
+ @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
+ @SwitchMode int mode, boolean forward) {
if (imi == null) {
return null;
}
- if (imi.supportsSwitchingToNextInputMethod()) {
+ if (Flags.imeSwitcherRevamp() && mRotationList != null) {
+ return mRotationList.next(imi, subtype, onlyCurrentIme,
+ isRecency(mode, forward), forward);
+ } else if (imi.supportsSwitchingToNextInputMethod()) {
return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
subtype);
} else {
@@ -445,11 +708,66 @@
}
}
- public void onUserActionLocked(@NonNull InputMethodInfo imi,
+ @Nullable
+ public ImeSubtypeListItem getNextInputMethodForHardware(boolean onlyCurrentIme,
+ @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
+ @SwitchMode int mode, boolean forward) {
+ if (Flags.imeSwitcherRevamp() && mHardwareRotationList != null) {
+ return mHardwareRotationList.next(imi, subtype, onlyCurrentIme,
+ isRecency(mode, forward), forward);
+ }
+ return null;
+ }
+
+ /**
+ * Called when the user took an action that should update the recency of the current
+ * input method and subtype in the switching list.
+ *
+ * @param imi the currently selected input method.
+ * @param subtype the currently selected input method subtype, if any.
+ * @return {@code true} if the recency was updated, otherwise {@code false}.
+ * @see android.inputmethodservice.InputMethodServiceInternal#notifyUserActionIfNecessary()
+ */
+ public boolean onUserActionLocked(@NonNull InputMethodInfo imi,
@Nullable InputMethodSubtype subtype) {
- if (imi.supportsSwitchingToNextInputMethod()) {
+ boolean recencyUpdated = false;
+ if (Flags.imeSwitcherRevamp()) {
+ if (mRotationList != null) {
+ recencyUpdated |= mRotationList.setMostRecent(imi, subtype);
+ }
+ if (mHardwareRotationList != null) {
+ recencyUpdated |= mHardwareRotationList.setMostRecent(imi, subtype);
+ }
+ if (recencyUpdated) {
+ mUserActionSinceSwitch = true;
+ }
+ } else if (imi.supportsSwitchingToNextInputMethod()) {
mSwitchingAwareRotationList.onUserAction(imi, subtype);
}
+ return recencyUpdated;
+ }
+
+ /** Called when the input method and subtype was changed. */
+ public void onInputMethodSubtypeChanged() {
+ mUserActionSinceSwitch = false;
+ }
+
+ /**
+ * Whether the given mode and direction result in recency or static order.
+ *
+ * <p>{@link #MODE_AUTO} resolves to the recency order for the first forwards switch
+ * after an {@link #onUserActionLocked user action}, and otherwise to the static order.</p>
+ *
+ * @param mode the switching mode.
+ * @param forward the switching direction.
+ * @return {@code true} for the recency order, otherwise {@code false}.
+ */
+ private boolean isRecency(@SwitchMode int mode, boolean forward) {
+ if (mode == MODE_AUTO && mUserActionSinceSwitch && forward) {
+ return true;
+ } else {
+ return mode == MODE_RECENT;
+ }
}
@NonNull
@@ -473,6 +791,17 @@
mSwitchingAwareRotationList.dump(pw, prefix + " ");
pw.println(prefix + "mSwitchingUnawareRotationList:");
mSwitchingUnawareRotationList.dump(pw, prefix + " ");
+ if (Flags.imeSwitcherRevamp()) {
+ if (mRotationList != null) {
+ pw.println(prefix + "mRotationList:");
+ mRotationList.dump(pw, prefix + " ");
+ }
+ if (mHardwareRotationList != null) {
+ pw.println(prefix + "mHardwareRotationList:");
+ mHardwareRotationList.dump(pw, prefix + " ");
+ }
+ pw.println("User action since last switch: " + mUserActionSinceSwitch);
+ }
}
}
@@ -480,26 +809,71 @@
private ControllerImpl mController;
InputMethodSubtypeSwitchingController() {
- mController = ControllerImpl.createFrom(null, Collections.emptyList());
+ mController = ControllerImpl.createFrom(null, Collections.emptyList(),
+ Collections.emptyList());
}
+ /**
+ * Called when the user took an action that should update the recency of the current
+ * input method and subtype in the switching list.
+ *
+ * @param imi the currently selected input method.
+ * @param subtype the currently selected input method subtype, if any.
+ * @see android.inputmethodservice.InputMethodServiceInternal#notifyUserActionIfNecessary()
+ */
public void onUserActionLocked(@NonNull InputMethodInfo imi,
@Nullable InputMethodSubtype subtype) {
mController.onUserActionLocked(imi, subtype);
}
+ /** Called when the input method and subtype was changed. */
+ public void onInputMethodSubtypeChanged() {
+ mController.onInputMethodSubtypeChanged();
+ }
+
public void resetCircularListLocked(@NonNull Context context,
@NonNull InputMethodSettings settings) {
mController = ControllerImpl.createFrom(mController,
getSortedInputMethodAndSubtypeList(
false /* includeAuxiliarySubtypes */, false /* isScreenLocked */,
- false /* forImeMenu */, context, settings));
+ false /* forImeMenu */, context, settings),
+ getInputMethodAndSubtypeListForHardwareKeyboard(context, settings));
}
+ /**
+ * Gets the next input method and subtype, starting from the given ones, in the given direction.
+ *
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param imi the input method to find the next value from.
+ * @param subtype the input method subtype to find the next value from, if any.
+ * @param mode the switching mode.
+ * @param forward whether to search search forwards or backwards in the list.
+ * @return the next input method and subtype if found, otherwise {@code null}.
+ */
@Nullable
public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
- @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
- return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
+ @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
+ @SwitchMode int mode, boolean forward) {
+ return mController.getNextInputMethod(onlyCurrentIme, imi, subtype, mode, forward);
+ }
+
+ /**
+ * Gets the next input method and subtype suitable for hardware keyboards, starting from the
+ * given ones, in the given direction.
+ *
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param imi the input method to find the next value from.
+ * @param subtype the input method subtype to find the next value from, if any.
+ * @param mode the switching mode
+ * @param forward whether to search search forwards or backwards in the list.
+ * @return the next input method and subtype if found, otherwise {@code null}.
+ */
+ @Nullable
+ public ImeSubtypeListItem getNextInputMethodForHardware(boolean onlyCurrentIme,
+ @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
+ @SwitchMode int mode, boolean forward) {
+ return mController.getNextInputMethodForHardware(onlyCurrentIme, imi, subtype, mode,
+ forward);
}
public void dump(@NonNull Printer pw, @NonNull String prefix) {
diff --git a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java b/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
deleted file mode 100644
index 33e7a76..0000000
--- a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-
-import android.annotation.AnyThread;
-import android.annotation.BoolRes;
-import android.annotation.NonNull;
-import android.annotation.UserHandleAware;
-import android.annotation.UserIdInt;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.os.Handler;
-import android.os.PatternMatcher;
-import android.util.Slog;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-
-/**
- * A wrapper object for any boolean resource defined in {@code "android"} package, in a way that is
- * aware of per-user Runtime Resource Overlay (RRO).
- */
-final class OverlayableSystemBooleanResourceWrapper implements AutoCloseable {
- private static final String TAG = "OverlayableSystemBooleanResourceWrapper";
-
- private static final String SYSTEM_PACKAGE_NAME = "android";
-
- @UserIdInt
- private final int mUserId;
- @NonNull
- private final AtomicBoolean mValueRef;
- @NonNull
- private final AtomicReference<Runnable> mCleanerRef;
-
- /**
- * Creates {@link OverlayableSystemBooleanResourceWrapper} for the given boolean resource ID
- * with a value change callback for the user associated with the {@link Context}.
- *
- * @param userContext The {@link Context} to be used to access the resource. This needs to be
- * associated with the right user because the Runtime Resource Overlay (RRO)
- * is per-user configuration.
- * @param boolResId The resource ID to be queried.
- * @param handler {@link Handler} to be used to dispatch {@code callback}.
- * @param callback The callback to be notified when the specified value might be updated.
- * The callback needs to take care of spurious wakeup. The value returned from
- * {@link #get()} may look to be exactly the same as the previously read value
- * e.g. when the value is changed from {@code false} to {@code true} to
- * {@code false} in a very short period of time, because {@link #get()} always
- * does volatile-read.
- * @return New {@link OverlayableSystemBooleanResourceWrapper}.
- */
- @NonNull
- @UserHandleAware
- static OverlayableSystemBooleanResourceWrapper create(@NonNull Context userContext,
- @BoolRes int boolResId, @NonNull Handler handler,
- @NonNull Consumer<OverlayableSystemBooleanResourceWrapper> callback) {
-
- // Note that we cannot fully trust this initial value due to the dead time between obtaining
- // the value here and setting up a broadcast receiver for change callback below.
- // We will refresh the value again later after setting up the change callback anyway.
- final AtomicBoolean valueRef = new AtomicBoolean(evaluate(userContext, boolResId));
-
- final AtomicReference<Runnable> cleanerRef = new AtomicReference<>();
-
- final OverlayableSystemBooleanResourceWrapper object =
- new OverlayableSystemBooleanResourceWrapper(userContext.getUserId(), valueRef,
- cleanerRef);
-
- final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
- intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
- intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
-
- final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final boolean newValue = evaluate(userContext, boolResId);
- if (newValue != valueRef.getAndSet(newValue)) {
- callback.accept(object);
- }
- }
- };
- userContext.registerReceiver(broadcastReceiver, intentFilter,
- null /* broadcastPermission */, handler,
- Context.RECEIVER_NOT_EXPORTED);
- cleanerRef.set(() -> userContext.unregisterReceiver(broadcastReceiver));
-
- // Make sure that the initial observable value is obtained after the change callback is set.
- valueRef.set(evaluate(userContext, boolResId));
- return object;
- }
-
- private OverlayableSystemBooleanResourceWrapper(@UserIdInt int userId,
- @NonNull AtomicBoolean valueRef, @NonNull AtomicReference<Runnable> cleanerRef) {
- mUserId = userId;
- mValueRef = valueRef;
- mCleanerRef = cleanerRef;
- }
-
- /**
- * @return The boolean resource value.
- */
- @AnyThread
- boolean get() {
- return mValueRef.get();
- }
-
- /**
- * @return The user ID associated with this resource reader.
- */
- @AnyThread
- @UserIdInt
- int getUserId() {
- return mUserId;
- }
-
- @AnyThread
- private static boolean evaluate(@NonNull Context context, @BoolRes int boolResId) {
- try {
- return context.getPackageManager()
- .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
- .getBoolean(boolResId);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed", e);
- return false;
- }
- }
-
- /**
- * Cleans up the callback.
- */
- @AnyThread
- @Override
- public void close() {
- final Runnable cleaner = mCleanerRef.getAndSet(null);
- if (cleaner != null) {
- cleaner.run();
- }
- }
-}
diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java
new file mode 100644
index 0000000..ec5c9e6
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.util.SparseArray;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Placeholder for all IMMS user specific fields */
+final class UserData {
+ @UserIdInt
+ final int mUserId;
+
+ @NonNull
+ final InputMethodBindingController mBindingController;
+
+ @NonNull
+ final InputMethodSubtypeSwitchingController mSwitchingController =
+ new InputMethodSubtypeSwitchingController();
+
+ @NonNull
+ final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
+ new HardwareKeyboardShortcutController();
+
+ /**
+ * Have we called mCurMethod.bindInput()?
+ */
+ @GuardedBy("ImfLock.class")
+ boolean mBoundToMethod = false;
+
+ /**
+ * Have we called bindInput() for accessibility services?
+ */
+ @GuardedBy("ImfLock.class")
+ boolean mBoundToAccessibility;
+
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ ImeBindingState mImeBindingState = ImeBindingState.newEmptyState();
+
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ ClientState mCurClient = null;
+
+ @GuardedBy("ImfLock.class")
+ boolean mInFullscreenMode;
+
+ /**
+ * The {@link IRemoteInputConnection} last provided by the current client.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ IRemoteInputConnection mCurInputConnection;
+
+ /**
+ * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
+ * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ ImeOnBackInvokedDispatcher mCurImeDispatcher;
+
+ /**
+ * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
+
+ /**
+ * The {@link EditorInfo} last provided by the current client.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ EditorInfo mCurEditorInfo;
+
+ /**
+ * The token tracking the current IME show request that is waiting for a connection to an
+ * IME, otherwise {@code null}.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ ImeTracker.Token mCurStatsToken;
+
+ /**
+ * Currently enabled session.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ InputMethodManagerService.SessionState mEnabledSession;
+
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ SparseArray<InputMethodManagerService.AccessibilitySessionState>
+ mEnabledAccessibilitySessions = new SparseArray<>();
+
+ /**
+ * A per-user cache of {@link InputMethodSettings#getEnabledInputMethodsStr()}.
+ */
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ String mLastEnabledInputMethodsStr = "";
+
+ /**
+ * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
+ */
+ @NonNull
+ final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
+
+ /**
+ * Intended to be instantiated only from this file.
+ */
+ UserData(@UserIdInt int userId,
+ @NonNull InputMethodBindingController bindingController) {
+ mUserId = userId;
+ mBindingController = bindingController;
+ }
+
+ @Override
+ public String toString() {
+ return "UserData{" + "mUserId=" + mUserId + '}';
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 98d7548..6f831cc 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -18,16 +18,10 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.util.SparseArray;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ImeTracker;
-import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
-import com.android.internal.inputmethod.IRemoteInputConnection;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
@@ -86,114 +80,4 @@
mUserDataLock.writeLock().unlock();
}
}
-
- /** Placeholder for all IMMS user specific fields */
- static final class UserData {
- @UserIdInt
- final int mUserId;
-
- @NonNull
- final InputMethodBindingController mBindingController;
-
- @NonNull
- final InputMethodSubtypeSwitchingController mSwitchingController;
-
- @NonNull
- final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
-
- /**
- * Have we called mCurMethod.bindInput()?
- */
- @GuardedBy("ImfLock.class")
- boolean mBoundToMethod = false;
-
- /**
- * Have we called bindInput() for accessibility services?
- */
- @GuardedBy("ImfLock.class")
- boolean mBoundToAccessibility;
-
- @GuardedBy("ImfLock.class")
- @NonNull
- ImeBindingState mImeBindingState = ImeBindingState.newEmptyState();
-
- @GuardedBy("ImfLock.class")
- @Nullable
- ClientState mCurClient = null;
-
- @GuardedBy("ImfLock.class")
- boolean mInFullscreenMode;
-
- /**
- * The {@link IRemoteInputConnection} last provided by the current client.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- IRemoteInputConnection mCurInputConnection;
-
- /**
- * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
- * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- ImeOnBackInvokedDispatcher mCurImeDispatcher;
-
- /**
- * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
-
- /**
- * The {@link EditorInfo} last provided by the current client.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- EditorInfo mCurEditorInfo;
-
- /**
- * The token tracking the current IME show request that is waiting for a connection to an
- * IME, otherwise {@code null}.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- ImeTracker.Token mCurStatsToken;
-
- /**
- * Currently enabled session.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- InputMethodManagerService.SessionState mEnabledSession;
-
- @GuardedBy("ImfLock.class")
- @Nullable
- SparseArray<InputMethodManagerService.AccessibilitySessionState>
- mEnabledAccessibilitySessions = new SparseArray<>();
-
- /**
- * A per-user cache of {@link InputMethodSettings#getEnabledInputMethodsStr()}.
- */
- @GuardedBy("ImfLock.class")
- @NonNull
- String mLastEnabledInputMethodsStr = "";
-
- /**
- * Intended to be instantiated only from this file.
- */
- private UserData(@UserIdInt int userId,
- @NonNull InputMethodBindingController bindingController) {
- mUserId = userId;
- mBindingController = bindingController;
- mSwitchingController = new InputMethodSubtypeSwitchingController();
- mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController();
- }
-
- @Override
- public String toString() {
- return "UserData{" + "mUserId=" + mUserId + '}';
- }
- }
}
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 41aac32..770e12d 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -296,6 +296,12 @@
return mInner.isInputMethodPickerShownForTest();
}
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Override
+ public void onImeSwitchButtonClickFromSystem(int displayId) {
+ mInner.onImeSwitchButtonClickFromSystem(displayId);
+ }
+
@Override
public InputMethodSubtype getCurrentInputMethodSubtype(int userId) {
return mInner.getCurrentInputMethodSubtype(userId);
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 4851a81..3d0b079 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -48,6 +48,7 @@
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.annotations.KeepForWeakReference;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.FrameworkStatsLog;
@@ -100,6 +101,7 @@
private LocaleManagerBackupHelper mBackupHelper;
+ @KeepForWeakReference
private final PackageMonitor mPackageMonitor;
private final Object mWriteLock = new Object();
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
index 058c1c8..e1b1416 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
@@ -16,10 +16,12 @@
package com.android.server.location.contexthub;
+import android.chre.flags.Flags;
import android.hardware.location.NanoAppMessage;
import android.util.Log;
import java.util.Collection;
+import java.util.Optional;
/**
* A class to log events and useful metrics within the Context Hub service.
@@ -149,10 +151,20 @@
*/
public final NanoAppMessage message;
+ /**
+ * the error code for the message
+ */
+ public Optional<Byte> errorCode;
+
public NanoappMessageEvent(long mTimeStampInMs, int mContextHubId,
NanoAppMessage mMessage, boolean mSuccess) {
super(mTimeStampInMs, mContextHubId, 0, mSuccess);
message = mMessage;
+ errorCode = Optional.empty();
+ }
+
+ public void setErrorCode(byte errorCode) {
+ this.errorCode = Optional.of(errorCode);
}
@Override
@@ -165,6 +177,8 @@
sb.append(message.toString());
sb.append(", success = ");
sb.append(success ? "true" : "false");
+ sb.append(", errorCode = ");
+ sb.append(errorCode.isPresent() ? errorCode.get() : "null");
sb.append(']');
return sb.toString();
}
@@ -312,6 +326,28 @@
}
/**
+ * Logs the status of a reliable message
+ *
+ * @param messageSequenceNumber the message sequence number
+ * @param errorCode the error code
+ */
+ public synchronized void logReliableMessageToNanoappStatus(
+ int messageSequenceNumber, byte errorCode) {
+ if (!Flags.reliableMessage()) {
+ return;
+ }
+
+ for (NanoappMessageEvent event : mMessageToNanoappQueue) {
+ if (event.message.isReliable()
+ && event.message.getMessageSequenceNumber()
+ == messageSequenceNumber) {
+ event.setErrorCode(errorCode);
+ break;
+ }
+ }
+ }
+
+ /**
* Logs a context hub restart event
*
* @param contextHubId the ID of the context hub
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index a0aad52..ed451ff 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -1087,6 +1087,8 @@
* @param messageDeliveryStatus The message delivery status to deliver.
*/
private void handleMessageDeliveryStatusCallback(MessageDeliveryStatus messageDeliveryStatus) {
+ ContextHubEventLogger.getInstance().logReliableMessageToNanoappStatus(
+ messageDeliveryStatus.messageSequenceNumber, messageDeliveryStatus.errorCode);
mTransactionManager.onMessageDeliveryResponse(messageDeliveryStatus.messageSequenceNumber,
messageDeliveryStatus.errorCode == ErrorCode.OK);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9e53cc3..016abff 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -110,8 +110,8 @@
import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Flags.callstyleCallbackApi;
import static android.service.notification.Flags.notificationForceGrouping;
-import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle;
+import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -248,7 +248,6 @@
import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.database.ContentObserver;
-import android.graphics.drawable.Icon;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Binder;
@@ -373,7 +372,6 @@
import com.android.server.wm.BackgroundActivityStartCallback;
import com.android.server.wm.WindowManagerInternal;
-import java.util.function.BiPredicate;
import libcore.io.IoUtils;
import org.json.JSONException;
@@ -4634,29 +4632,28 @@
@Override
public List<String> getPackagesBypassingDnd(int userId,
- boolean includeConversationChannels) {
+ boolean includeConversationChannels) {
checkCallerIsSystem();
final ArraySet<String> packageNames = new ArraySet<>();
- for (int user : mUm.getProfileIds(userId, false)) {
- List<PackageInfo> pkgs = mPackageManagerClient.getInstalledPackagesAsUser(0, user);
- for (PackageInfo pi : pkgs) {
- String pkg = pi.packageName;
- // If any NotificationChannel for this package is bypassing, the
- // package is considered bypassing.
- for (NotificationChannel channel : getNotificationChannelsBypassingDnd(pkg,
- pi.applicationInfo.uid).getList()) {
- // Skips non-demoted conversation channels.
- if (!includeConversationChannels
- && !TextUtils.isEmpty(channel.getConversationId())
- && !channel.isDemoted()) {
- continue;
- }
- packageNames.add(pkg);
+ List<PackageInfo> pkgs = mPackageManagerClient.getInstalledPackagesAsUser(0, userId);
+ for (PackageInfo pi : pkgs) {
+ String pkg = pi.packageName;
+ // If any NotificationChannel for this package is bypassing, the
+ // package is considered bypassing.
+ for (NotificationChannel channel : getNotificationChannelsBypassingDnd(pkg,
+ pi.applicationInfo.uid).getList()) {
+ // Skips non-demoted conversation channels.
+ if (!includeConversationChannels
+ && !TextUtils.isEmpty(channel.getConversationId())
+ && !channel.isDemoted()) {
+ continue;
}
+ packageNames.add(pkg);
}
}
+
return new ArrayList<String>(packageNames);
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 56e4590..46585a5 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -80,6 +80,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.KeepForWeakReference;
import com.android.internal.content.PackageMonitor;
import com.android.internal.content.om.OverlayConfig;
import com.android.internal.util.ArrayUtils;
@@ -261,6 +262,7 @@
private final OverlayActorEnforcer mActorEnforcer;
+ @KeepForWeakReference
private final PackageMonitor mPackageMonitor = new OverlayManagerPackageMonitor();
private int mPrevStartedUserId = -1;
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index c5e2bb8..8410cff 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -34,6 +34,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -370,8 +371,13 @@
}
private void enforceUid(int callingUid) {
- if (callingUid != mAllowedUid && callingUid != UserHandle.AID_ROOT) {
- throw new SecurityException("uid " + callingUid + " not allowed to access PDB");
+ enforceUid(callingUid, /* allowShell= */ false);
+ }
+
+ private void enforceUid(int callingUid, boolean allowShell) {
+ if (callingUid != mAllowedUid && callingUid != UserHandle.AID_ROOT
+ && (callingUid != Process.SHELL_UID || !allowShell)) {
+ throw new SecurityException("Uid " + callingUid + " not allowed to access PDB");
}
}
@@ -864,7 +870,8 @@
private final IBinder mService = new IPersistentDataBlockService.Stub() {
private int printFrpStatus(PrintWriter pw, boolean printSecrets) {
- enforceUid(Binder.getCallingUid());
+ // Only allow SHELL_UID to print the status if printing the secrets is disabled
+ enforceUid(Binder.getCallingUid(), /* allowShell= */ !printSecrets);
pw.println("FRP state");
pw.println("=========");
@@ -872,8 +879,14 @@
pw.println("FRP state: " + mFrpActive);
printFrpDataFilesContents(pw, printSecrets);
printFrpSecret(pw, printSecrets);
- pw.println("OEM unlock state: " + getOemUnlockEnabled());
- pw.println("Bootloader lock state: " + getFlashLockState());
+
+ // Do not print OEM unlock state and flash lock state if the caller is a non-root
+ // shell - it likely won't have permissions anyways.
+ if (Binder.getCallingUid() != Process.SHELL_UID) {
+ pw.println("OEM unlock state: " + getOemUnlockEnabled());
+ pw.println("Bootloader lock state: " + getFlashLockState());
+ }
+
pw.println("Verified boot state: " + getVerifiedBootState());
pw.println("Has FRP credential handle: " + hasFrpCredentialHandle());
pw.println("FRP challenge block size: " + getDataBlockSize());
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 303371b..8d3f07e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2261,6 +2261,12 @@
installRequest.getNewUsers());
mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
mPm.updateInstantAppInstallerLocked(packageName);
+
+ // The installation is success, remove the split info copy stored in package
+ // setting for the downgrade version check of DELETE_KEEP_DATA and archived app
+ // cases.
+ ps.setSplitNames(null);
+ ps.setSplitRevisionCodes(null);
}
installRequest.onCommitFinished();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index c3cac20..a1dffc6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1423,11 +1423,8 @@
*/
public static void checkDowngrade(@NonNull PackageSetting before,
@NonNull PackageInfoLite after) throws PackageManagerException {
- if (after.getLongVersionCode() < before.getVersionCode()) {
- throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
- "Update version code " + after.versionCode + " is older than current "
- + before.getVersionCode());
- }
+ checkDowngrade(before.getVersionCode(), before.getBaseRevisionCode(),
+ before.getSplitNames(), before.getSplitRevisionCodes(), after);
}
/**
@@ -1436,28 +1433,35 @@
*/
public static void checkDowngrade(@NonNull AndroidPackage before,
@NonNull PackageInfoLite after) throws PackageManagerException {
- if (after.getLongVersionCode() < before.getLongVersionCode()) {
+ checkDowngrade(before.getLongVersionCode(), before.getBaseRevisionCode(),
+ before.getSplitNames(), before.getSplitRevisionCodes(), after);
+ }
+
+ private static void checkDowngrade(long beforeVersionCode, int beforeBaseRevisionCode,
+ @NonNull String[] beforeSplitNames, @NonNull int[] beforeSplitRevisionCodes,
+ @NonNull PackageInfoLite after) throws PackageManagerException {
+ if (after.getLongVersionCode() < beforeVersionCode) {
throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
"Update version code " + after.versionCode + " is older than current "
- + before.getLongVersionCode());
- } else if (after.getLongVersionCode() == before.getLongVersionCode()) {
- if (after.baseRevisionCode < before.getBaseRevisionCode()) {
+ + beforeVersionCode);
+ } else if (after.getLongVersionCode() == beforeVersionCode) {
+ if (after.baseRevisionCode < beforeBaseRevisionCode) {
throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
"Update base revision code " + after.baseRevisionCode
- + " is older than current " + before.getBaseRevisionCode());
+ + " is older than current " + beforeBaseRevisionCode);
}
if (!ArrayUtils.isEmpty(after.splitNames)) {
for (int i = 0; i < after.splitNames.length; i++) {
final String splitName = after.splitNames[i];
- final int j = ArrayUtils.indexOf(before.getSplitNames(), splitName);
+ final int j = ArrayUtils.indexOf(beforeSplitNames, splitName);
if (j != -1) {
- if (after.splitRevisionCodes[i] < before.getSplitRevisionCodes()[j]) {
+ if (after.splitRevisionCodes[i] < beforeSplitRevisionCodes[j]) {
throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
"Update split " + splitName + " revision code "
+ after.splitRevisionCodes[i]
+ " is older than current "
- + before.getSplitRevisionCodes()[j]);
+ + beforeSplitRevisionCodes[j]);
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 82df527..9f10e01 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -234,6 +234,22 @@
@Nullable
private byte[] mRestrictUpdateHash;
+ // This is the copy of the same data stored in AndroidPackage. It is not null if the
+ // AndroidPackage is deleted in cases of DELETE_KEEP_DATA. When AndroidPackage is not null,
+ // the field will be null, and the getter method will return the data from AndroidPackage
+ // instead.
+ @Nullable
+ private String[] mSplitNames;
+
+ // This is the copy of the same data stored in AndroidPackage. It is not null if the
+ // AndroidPackage is deleted in cases of DELETE_KEEP_DATA. When AndroidPackage is not null,
+ // the field will be null, and the getter method will return the data from AndroidPackage
+ // instead.
+ @Nullable
+ private int[] mSplitRevisionCodes;
+
+ private int mBaseRevisionCode;
+
/**
* Snapshot support.
*/
@@ -578,6 +594,62 @@
return getBoolean(Booleans.DEBUGGABLE);
}
+ /**
+ * @see AndroidPackage#getBaseRevisionCode
+ */
+ public PackageSetting setBaseRevisionCode(int value) {
+ mBaseRevisionCode = value;
+ onChanged();
+ return this;
+ }
+
+ /**
+ * @see AndroidPackage#getBaseRevisionCode
+ */
+ public int getBaseRevisionCode() {
+ return mBaseRevisionCode;
+ }
+
+ /**
+ * @see AndroidPackage#getSplitNames
+ */
+ public PackageSetting setSplitNames(String[] value) {
+ mSplitNames = value;
+ onChanged();
+ return this;
+ }
+
+ /**
+ * @see AndroidPackage#getSplitNames
+ */
+ @NonNull
+ public String[] getSplitNames() {
+ if (pkg != null) {
+ return pkg.getSplitNames();
+ }
+ return mSplitNames == null ? EmptyArray.STRING : mSplitNames;
+ }
+
+ /**
+ * @see AndroidPackage#getSplitRevisionCodes
+ */
+ public PackageSetting setSplitRevisionCodes(int[] value) {
+ mSplitRevisionCodes = value;
+ onChanged();
+ return this;
+ }
+
+ /**
+ * @see AndroidPackage#getSplitRevisionCodes
+ */
+ @NonNull
+ public int[] getSplitRevisionCodes() {
+ if (pkg != null) {
+ return pkg.getSplitRevisionCodes();
+ }
+ return mSplitRevisionCodes == null ? EmptyArray.INT : mSplitRevisionCodes;
+ }
+
@Override
public String toString() {
return "PackageSetting{"
@@ -739,6 +811,11 @@
mTargetSdkVersion = other.mTargetSdkVersion;
mRestrictUpdateHash = other.mRestrictUpdateHash == null
? null : other.mRestrictUpdateHash.clone();
+ mBaseRevisionCode = other.mBaseRevisionCode;
+ mSplitNames = other.mSplitNames != null
+ ? Arrays.copyOf(other.mSplitNames, other.mSplitNames.length) : null;
+ mSplitRevisionCodes = other.mSplitRevisionCodes != null
+ ? Arrays.copyOf(other.mSplitRevisionCodes, other.mSplitRevisionCodes.length) : null;
usesSdkLibraries = other.usesSdkLibraries != null
? Arrays.copyOf(other.usesSdkLibraries,
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 2f2c451..7afc358 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -432,6 +432,13 @@
}
deletedPs.setInstalled(/* installed= */ false, userId);
}
+
+ // Preserve split apk information for downgrade check with DELETE_KEEP_DATA and archived
+ // app cases
+ if (deletedPkg.getSplitNames() != null) {
+ deletedPs.setSplitNames(deletedPkg.getSplitNames());
+ deletedPs.setSplitRevisionCodes(deletedPkg.getSplitRevisionCodes());
+ }
}
// make sure to preserve per-user installed state if this removal was just
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index d8ce38e..95561f5f 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -437,8 +437,9 @@
pkgSetting.setIsOrphaned(true);
}
- // update debuggable to packageSetting
+ // update debuggable and BaseRevisionCode to packageSetting
pkgSetting.setDebuggable(parsedPackage.isDebuggable());
+ pkgSetting.setBaseRevisionCode(parsedPackage.getBaseRevisionCode());
// Take care of first install / last update times.
final long scanFileTime = getLastModifiedTime(parsedPackage);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0d16b00..9177e2b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -325,6 +325,7 @@
private static final String TAG_MIME_TYPE = "mime-type";
private static final String TAG_ARCHIVE_STATE = "archive-state";
private static final String TAG_ARCHIVE_ACTIVITY_INFO = "archive-activity-info";
+ private static final String TAG_SPLIT_VERSION = "split-version";
public static final String ATTR_NAME = "name";
public static final String ATTR_PACKAGE = "package";
@@ -3261,6 +3262,9 @@
if (pkg.isLoading()) {
serializer.attributeBoolean(null, "isLoading", true);
}
+ if (pkg.getBaseRevisionCode() != 0) {
+ serializer.attributeInt(null, "baseRevisionCode", pkg.getBaseRevisionCode());
+ }
serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
serializer.attributeLongHex(null, "loadingCompletedTime", pkg.getLoadingCompletedTime());
@@ -3289,7 +3293,12 @@
writeUpgradeKeySetsLPr(serializer, pkg.getKeySetData());
writeKeySetAliasesLPr(serializer, pkg.getKeySetData());
writeMimeGroupLPr(serializer, pkg.getMimeGroups());
-
+ // If getPkg is not NULL, these values are from the getPkg. And these values are preserved
+ // for the downgrade check for DELETE_KEEP_DATA and archived app cases. If the getPkg is
+ // not NULL, we don't need to preserve it.
+ if (pkg.getPkg() == null) {
+ writeSplitVersionsLPr(serializer, pkg.getSplitNames(), pkg.getSplitRevisionCodes());
+ }
serializer.endTag(null, "package");
}
@@ -4071,6 +4080,7 @@
byte[] restrictUpdateHash = null;
boolean isScannedAsStoppedSystemApp = false;
boolean isSdkLibrary = false;
+ int baseRevisionCode = 0;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
@@ -4116,6 +4126,7 @@
appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath");
appMetadataSource = parser.getAttributeInt(null, "appMetadataSource",
PackageManager.APP_METADATA_SOURCE_UNKNOWN);
+ baseRevisionCode = parser.getAttributeInt(null, "baseRevisionCode", 0);
isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null,
"scannedAsStoppedSystemApp", false);
@@ -4269,6 +4280,7 @@
.setAppMetadataFilePath(appMetadataFilePath)
.setAppMetadataSource(appMetadataSource)
.setTargetSdkVersion(targetSdkVersion)
+ .setBaseRevisionCode(baseRevisionCode)
.setRestrictUpdateHash(restrictUpdateHash)
.setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp);
// Handle legacy string here for single-user mode
@@ -4374,6 +4386,8 @@
readUsesStaticLibLPw(parser, packageSetting);
} else if (tagName.equals(TAG_USES_SDK_LIB)) {
readUsesSdkLibLPw(parser, packageSetting);
+ } else if (tagName.equals(TAG_SPLIT_VERSION)) {
+ readSplitVersionsLPw(parser, packageSetting);
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <package>: " + parser.getName());
@@ -4470,6 +4484,37 @@
}
}
+ private void readSplitVersionsLPw(TypedXmlPullParser parser, PackageSetting outPs)
+ throws IOException, XmlPullParserException {
+ String splitName = parser.getAttributeValue(null, ATTR_NAME);
+ int splitRevision = parser.getAttributeInt(null, ATTR_VERSION, -1);
+ if (splitName != null && splitRevision >= 0) {
+ outPs.setSplitNames(ArrayUtils.appendElement(String.class,
+ outPs.getSplitNames(), splitName));
+ outPs.setSplitRevisionCodes(ArrayUtils.appendInt(
+ outPs.getSplitRevisionCodes(), splitRevision));
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+ }
+
+ private void writeSplitVersionsLPr(TypedXmlSerializer serializer, String[] splitNames,
+ int[] splitRevisionCodes) throws IOException {
+ if (ArrayUtils.isEmpty(splitNames) || ArrayUtils.isEmpty(splitRevisionCodes)
+ || splitNames.length != splitRevisionCodes.length) {
+ return;
+ }
+ final int libLength = splitNames.length;
+ for (int i = 0; i < libLength; i++) {
+ final String splitName = splitNames[i];
+ final int splitRevision = splitRevisionCodes[i];
+ serializer.startTag(null, TAG_SPLIT_VERSION);
+ serializer.attribute(null, ATTR_NAME, splitName);
+ serializer.attributeInt(null, ATTR_VERSION, splitRevision);
+ serializer.endTag(null, TAG_SPLIT_VERSION);
+ }
+ }
+
private void readDisabledComponentsLPw(PackageSetting packageSetting, TypedXmlPullParser parser,
int userId) throws IOException, XmlPullParserException {
int outerDepth = parser.getDepth();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8dc9756..8419a60 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -933,8 +933,7 @@
public void onWakeUp() {
synchronized (mLock) {
if (shouldEnableWakeGestureLp()) {
- performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
- "Wake Up");
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, "Wake Up");
mWindowWakeUpPolicy.wakeUpFromWakeGesture();
}
}
@@ -1403,7 +1402,7 @@
break;
case LONG_PRESS_POWER_GLOBAL_ACTIONS:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
"Power - Long Press - Global Actions");
showGlobalActions();
break;
@@ -1415,14 +1414,14 @@
if (ActivityManager.isUserAMonkey()) {
break;
}
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
"Power - Long Press - Shut Off");
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
break;
case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
"Power - Long Press - Go To Voice Assist");
// Some devices allow the voice assistant intent during setup (and use that intent
// to launch something else, like Settings). So we explicitly allow that via the
@@ -1431,7 +1430,7 @@
break;
case LONG_PRESS_POWER_ASSISTANT:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.ASSISTANT_BUTTON, false,
+ performHapticFeedback(HapticFeedbackConstants.ASSISTANT_BUTTON,
"Power - Long Press - Go To Assistant");
final int powerKeyDeviceId = INVALID_INPUT_DEVICE_ID;
launchAssistAction(null, powerKeyDeviceId, eventTime,
@@ -1446,7 +1445,7 @@
break;
case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
"Power - Very Long Press - Show Global Actions");
showGlobalActions();
break;
@@ -1599,8 +1598,7 @@
case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
mTalkbackShortcutController.toggleTalkback(mCurrentUserId);
if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) {
- performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */
- false, /* reason = */
+ performHapticFeedback(HapticFeedbackConstants.CONFIRM,
"Stem primary - Triple Press - Toggle Accessibility");
}
break;
@@ -1771,7 +1769,7 @@
@Override
public void run() {
mEndCallKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
"End Call - Long Press - Show Global Actions");
showGlobalActionsInternal();
}
@@ -2087,8 +2085,7 @@
return;
}
mHomeConsumed = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
- "Home - Long Press");
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, "Home - Long Press");
switch (mLongPressOnHomeBehavior) {
case LONG_PRESS_HOME_ALL_APPS:
if (mHasFeatureLeanback) {
@@ -2530,7 +2527,7 @@
break;
case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS:
performHapticFeedback(
- HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+ HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
"Power + Volume Up - Global Actions");
showGlobalActions();
mPowerKeyHandled = true;
@@ -5078,8 +5075,7 @@
}
if (useHapticFeedback) {
- performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
- "Virtual Key - Press");
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, "Virtual Key - Press");
}
if (isWakeKey) {
@@ -5971,8 +5967,10 @@
public void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
if (safeMode) {
- performHapticFeedback(HapticFeedbackConstants.SAFE_MODE_ENABLED, true,
- "Safe Mode Enabled");
+ performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
+ HapticFeedbackConstants.SAFE_MODE_ENABLED,
+ "Safe Mode Enabled", HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+ 0 /* privFlags */);
}
}
@@ -6441,9 +6439,9 @@
Settings.Global.THEATER_MODE_ON, 0) == 1;
}
- private boolean performHapticFeedback(int effectId, boolean always, String reason) {
+ private boolean performHapticFeedback(int effectId, String reason) {
return performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
- effectId, always, reason, false /* fromIme */);
+ effectId, reason, 0 /* flags */, 0 /* privFlags */);
}
@Override
@@ -6452,8 +6450,8 @@
}
@Override
- public boolean performHapticFeedback(int uid, String packageName, int effectId,
- boolean always, String reason, boolean fromIme) {
+ public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
+ int flags, int privFlags) {
if (!mVibrator.hasVibrator()) {
return false;
}
@@ -6464,7 +6462,7 @@
}
VibrationAttributes attrs =
mHapticFeedbackVibrationProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ always, fromIme);
+ effectId, flags, privFlags);
VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, effectId);
mVibrator.vibrate(uid, packageName, effect, reason, attrs);
return true;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 6c05d70..1b394f6 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -80,6 +80,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
+import android.view.HapticFeedbackConstants;
import android.view.IDisplayFoldListener;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
@@ -1079,7 +1080,8 @@
* Call from application to perform haptic feedback on its window.
*/
public boolean performHapticFeedback(int uid, String packageName, int effectId,
- boolean always, String reason, boolean fromIme);
+ String reason, @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags);
/**
* Called when we have started keeping the screen on because a window
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ce0120c..6fe1ccd 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -33,6 +33,7 @@
import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN;
import static com.android.server.deviceidle.Flags.disableWakelocksInLightIdle;
+import static com.android.server.display.DisplayDeviceConfig.INVALID_BRIGHTNESS_IN_CONFIG;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -239,9 +240,6 @@
// This should perhaps be a setting.
private static final int SCREEN_BRIGHTNESS_BOOST_TIMEOUT = 5 * 1000;
- // Float.NaN cannot be stored in config.xml so -2 is used instead
- private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
-
// How long a partial wake lock must be held until we consider it a long wake lock.
static final long MIN_LONG_WAKE_CHECK_INTERVAL = 60*1000;
@@ -619,7 +617,6 @@
public final float mScreenBrightnessMinimum;
public final float mScreenBrightnessMaximum;
public final float mScreenBrightnessDefault;
- public final float mScreenBrightnessDoze;
public final float mScreenBrightnessDim;
// Value we store for tracking face down behavior.
@@ -1219,8 +1216,6 @@
.config_screenBrightnessSettingMaximumFloat);
final float def = mContext.getResources().getFloat(com.android.internal.R.dimen
.config_screenBrightnessSettingDefaultFloat);
- final float doze = mContext.getResources().getFloat(com.android.internal.R.dimen
- .config_screenBrightnessDozeFloat);
final float dim = mContext.getResources().getFloat(com.android.internal.R.dimen
.config_screenBrightnessDimFloat);
@@ -1240,13 +1235,6 @@
mScreenBrightnessMaximum = max;
mScreenBrightnessDefault = def;
}
- if (doze == INVALID_BRIGHTNESS_IN_CONFIG) {
- mScreenBrightnessDoze = BrightnessSynchronizer.brightnessIntToFloat(
- mContext.getResources().getInteger(com.android.internal.R.integer
- .config_screenBrightnessDoze));
- } else {
- mScreenBrightnessDoze = doze;
- }
if (dim == INVALID_BRIGHTNESS_IN_CONFIG) {
mScreenBrightnessDim = BrightnessSynchronizer.brightnessIntToFloat(
mContext.getResources().getInteger(com.android.internal.R.integer
@@ -6090,8 +6078,6 @@
return mScreenBrightnessDefault;
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM:
return mScreenBrightnessDim;
- case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE:
- return mScreenBrightnessDoze;
default:
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 6a5a7ac..d34498a 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -47,3 +47,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "add_battery_usage_stats_slice_atom"
+ namespace: "backstage_power"
+ description: "Adds battery_usage_stats_slice atom"
+ bug: "324602949"
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index dfccd1a..bca81f52 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1283,7 +1283,7 @@
}
case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(
- new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/true);
+ new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/false);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
new int[]{TRANSPORT_BLUETOOTH},
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index ddbd809..953aae9 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.trust;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import static android.service.trust.GrantTrustResult.STATUS_UNLOCKED_BY_GRANT;
import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
@@ -84,6 +85,9 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockSettingsStateListener;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.ServiceWatcher;
@@ -159,6 +163,7 @@
/* package */ final TrustArchive mArchive = new TrustArchive();
private final Context mContext;
+ private final LockSettingsInternal mLockSettings;
private final LockPatternUtils mLockPatternUtils;
private final KeyStoreAuthorization mKeyStoreAuthorization;
private final UserManager mUserManager;
@@ -250,6 +255,20 @@
private final StrongAuthTracker mStrongAuthTracker;
+ // Used to subscribe to device credential auth attempts.
+ private final LockSettingsStateListener mLockSettingsStateListener =
+ new LockSettingsStateListener() {
+ @Override
+ public void onAuthenticationSucceeded(int userId) {
+ mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, 1, userId).sendToTarget();
+ }
+
+ @Override
+ public void onAuthenticationFailed(int userId) {
+ mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, 0, userId).sendToTarget();
+ }
+ };
+
private boolean mTrustAgentsCanRun = false;
private int mCurrentUser = UserHandle.USER_SYSTEM;
@@ -294,6 +313,7 @@
mHandler = createHandler(injector.getLooper());
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mLockSettings = LocalServices.getService(LockSettingsInternal.class);
mLockPatternUtils = injector.getLockPatternUtils();
mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
mStrongAuthTracker = new StrongAuthTracker(context, injector.getLooper());
@@ -315,6 +335,9 @@
checkNewAgents();
mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
mReceiver.register(mContext);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ mLockSettings.registerLockSettingsStateListener(mLockSettingsStateListener);
+ }
mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
mFaceManager = mContext.getSystemService(FaceManager.class);
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 8138168..98a2ba0d 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -186,13 +186,13 @@
*
* @param effectId the haptic feedback effect ID whose respective vibration attributes we want
* to get.
- * @param bypassVibrationIntensitySetting {@code true} if the returned attribute should bypass
- * vibration intensity settings. {@code false} otherwise.
- * @param fromIme the haptic feedback is performed from an IME.
+ * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+ * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
* @return the {@link VibrationAttributes} that should be used for the provided haptic feedback.
*/
- public VibrationAttributes getVibrationAttributesForHapticFeedback(
- int effectId, boolean bypassVibrationIntensitySetting, boolean fromIme) {
+ public VibrationAttributes getVibrationAttributesForHapticFeedback(int effectId,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
VibrationAttributes attrs;
switch (effectId) {
case HapticFeedbackConstants.EDGE_SQUEEZE:
@@ -208,7 +208,7 @@
break;
case HapticFeedbackConstants.KEYBOARD_TAP:
case HapticFeedbackConstants.KEYBOARD_RELEASE:
- attrs = createKeyboardVibrationAttributes(fromIme);
+ attrs = createKeyboardVibrationAttributes(privFlags);
break;
case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
case HapticFeedbackConstants.BIOMETRIC_REJECT:
@@ -218,18 +218,23 @@
attrs = TOUCH_VIBRATION_ATTRIBUTES;
}
- int flags = 0;
+ int vibFlags = 0;
+ boolean fromIme =
+ (privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) != 0;
+ boolean bypassVibrationIntensitySetting =
+ (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
if (bypassVibrationIntensitySetting) {
- flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+ vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
}
if (shouldBypassInterruptionPolicy(effectId)) {
- flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+ vibFlags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
}
if (shouldBypassIntensityScale(effectId, fromIme)) {
- flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
+ vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
}
- return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build();
+ return vibFlags == 0 ? attrs : new VibrationAttributes.Builder(attrs)
+ .setFlags(vibFlags).build();
}
/**
@@ -373,12 +378,16 @@
return false;
}
- private VibrationAttributes createKeyboardVibrationAttributes(boolean fromIme) {
- // Use touch attribute when the keyboard category is disable or it's not from an IME.
- if (!Flags.keyboardCategoryEnabled() || !fromIme) {
+ private VibrationAttributes createKeyboardVibrationAttributes(
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
+ // Use touch attribute when the keyboard category is disable.
+ if (!Flags.keyboardCategoryEnabled()) {
return TOUCH_VIBRATION_ATTRIBUTES;
}
-
+ // Use touch attribute when the haptic is not apply to IME.
+ if ((privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) == 0) {
+ return TOUCH_VIBRATION_ATTRIBUTES;
+ }
return new VibrationAttributes.Builder(TOUCH_VIBRATION_ATTRIBUTES)
.setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
.build();
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 5c15ccb..4437a2d 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -65,6 +65,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
+import android.view.HapticFeedbackConstants;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -439,13 +440,13 @@
@Override // Binder call
public void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
- boolean always, String reason, boolean fromIme) {
+ String reason, int flags, int privFlags) {
// Note that the `performHapticFeedback` method does not take a token argument from the
// caller, and instead, uses this service as the token. This is to mitigate performance
// impact that would otherwise be caused due to marshal latency. Haptic feedback effects are
// short-lived, so we don't need to cancel when the process dies.
- performHapticFeedbackInternal(
- uid, deviceId, opPkg, constant, always, reason, /* token= */ this, fromIme);
+ performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
+ this, flags, privFlags);
}
/**
@@ -456,8 +457,8 @@
@VisibleForTesting
@Nullable
HalVibration performHapticFeedbackInternal(
- int uid, int deviceId, String opPkg, int constant, boolean always, String reason,
- IBinder token, boolean fromIme) {
+ int uid, int deviceId, String opPkg, int constant, String reason,
+ IBinder token, int flags, int privFlags) {
HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
if (hapticVibrationProvider == null) {
Slog.e(TAG, "performHapticFeedback; haptic vibration provider not ready.");
@@ -474,9 +475,8 @@
return null;
}
CombinedVibration vib = CombinedVibration.createParallel(effect);
- VibrationAttributes attrs =
- hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
- constant, /* bypassVibrationIntensitySetting= */ always, fromIme);
+ VibrationAttributes attrs = hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+ constant, flags, privFlags);
reason = "performHapticFeedback(constant=" + constant + "): " + reason;
VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, vib, attrs, reason, token);
@@ -2295,10 +2295,11 @@
IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
: mShellCallbacksToken;
+ int flags = commonOptions.force
+ ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0;
HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(),
Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, constant,
- /* always= */ commonOptions.force, /* reason= */ commonOptions.description,
- deathBinder, false /* fromIme */);
+ /* reason= */ commonOptions.description, deathBinder, flags, /* privFlags */ 0);
maybeWaitOnVibration(vib, commonOptions);
return 0;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8768074..3076b87 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7173,7 +7173,7 @@
Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawn()
+ ", isAnimationSet=" + isAnimationSet);
if (!w.isDrawn()) {
- Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
+ Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceControl
+ " pv=" + w.isVisibleByPolicy()
+ " mDrawState=" + winAnimator.drawStateToString()
+ " ph=" + w.isParentWindowHidden() + " th=" + mVisibleRequested
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 59b5da8..ff46b33 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4709,6 +4709,10 @@
// Update stored global config and notify everyone about the change.
mRootWindowContainer.onConfigurationChanged(mTempConfig);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if ((changes & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_ORIENTATION_CHANGED,
+ values.orientation);
+ }
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return changes;
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index 68a4172..2755a80 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -26,13 +26,14 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.CameraCompatTaskInfo;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.window.flags.Flags;
/**
@@ -56,6 +57,9 @@
private boolean mIsCameraCompatTreatmentPending = false;
+ @Nullable
+ private Task mCameraTask;
+
CameraCompatFreeformPolicy(@NonNull DisplayContent displayContent,
@NonNull CameraStateMonitor cameraStateMonitor,
@NonNull ActivityRefresher activityRefresher) {
@@ -116,6 +120,7 @@
final int newCameraCompatMode = getCameraCompatMode(cameraActivity);
if (newCameraCompatMode != existingCameraCompatMode) {
mIsCameraCompatTreatmentPending = true;
+ mCameraTask = cameraActivity.getTask();
cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
.setFreeformCameraCompatMode(newCameraCompatMode);
forceUpdateActivityAndTask(cameraActivity);
@@ -127,18 +132,22 @@
}
@Override
- public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
- if (isActivityForCameraIdRefreshing(cameraId)) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_STATES,
- "Display id=%d is notified that Camera %s is closed but activity is"
- + " still refreshing. Rescheduling an update.",
- mDisplayContent.mDisplayId, cameraId);
- return false;
+ public boolean onCameraClosed(@NonNull String cameraId) {
+ // Top activity in the same task as the camera activity, or `null` if the task is
+ // closed.
+ final ActivityRecord topActivity = mCameraTask != null
+ ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false)
+ : null;
+ if (topActivity != null) {
+ if (isActivityForCameraIdRefreshing(topActivity, cameraId)) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_STATES,
+ "Display id=%d is notified that Camera %s is closed but activity is"
+ + " still refreshing. Rescheduling an update.",
+ mDisplayContent.mDisplayId, cameraId);
+ return false;
+ }
}
- cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
- .setFreeformCameraCompatMode(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE);
- forceUpdateActivityAndTask(cameraActivity);
+ mCameraTask = null;
mIsCameraCompatTreatmentPending = false;
return true;
}
@@ -186,10 +195,9 @@
&& !activity.isEmbedded();
}
- private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) {
- final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
- /* considerKeyguardState= */ true);
- if (topActivity == null || !isTreatmentEnabledForActivity(topActivity)
+ private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord topActivity,
+ @NonNull String cameraId) {
+ if (!isTreatmentEnabledForActivity(topActivity)
|| mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java
index a54141c..068fc00 100644
--- a/services/core/java/com/android/server/wm/CameraStateMonitor.java
+++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java
@@ -61,9 +61,6 @@
@NonNull
private final Handler mHandler;
- @Nullable
- private ActivityRecord mCameraActivity;
-
// Bi-directional map between package names and active camera IDs since we need to 1) get a
// camera id by a package name when resizing the window; 2) get a package name by a camera id
// when camera connection is closed and we need to clean up our records.
@@ -91,13 +88,13 @@
@Override
public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) {
synchronized (mWmService.mGlobalLock) {
- notifyCameraOpened(cameraId, packageId);
+ notifyCameraOpenedWithDelay(cameraId, packageId);
}
}
@Override
public void onCameraClosed(@NonNull String cameraId) {
synchronized (mWmService.mGlobalLock) {
- notifyCameraClosed(cameraId);
+ notifyCameraClosedWithDelay(cameraId);
}
}
};
@@ -131,8 +128,8 @@
mCameraStateListeners.remove(listener);
}
- private void notifyCameraOpened(
- @NonNull String cameraId, @NonNull String packageName) {
+ private void notifyCameraOpenedWithDelay(@NonNull String cameraId,
+ @NonNull String packageName) {
// If an activity is restarting or camera is flipping, the camera connection can be
// quickly closed and reopened.
mScheduledToBeRemovedCameraIdSet.remove(cameraId);
@@ -142,25 +139,30 @@
// Some apps can’t handle configuration changes coming at the same time with Camera setup so
// delaying orientation update to accommodate for that.
mScheduledCompatModeUpdateCameraIdSet.add(cameraId);
- mHandler.postDelayed(
- () -> {
- synchronized (mWmService.mGlobalLock) {
- if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraId)) {
- // Camera compat mode update has happened already or was cancelled
- // because camera was closed.
- return;
- }
- mCameraIdPackageBiMapping.put(packageName, cameraId);
- mCameraActivity = findCameraActivity(packageName);
- if (mCameraActivity == null || mCameraActivity.getTask() == null) {
- return;
- }
- notifyListenersCameraOpened(mCameraActivity, cameraId);
- }
- },
+ mHandler.postDelayed(() -> notifyCameraOpenedInternal(cameraId, packageName),
CAMERA_OPENED_LETTERBOX_UPDATE_DELAY_MS);
}
+ private void notifyCameraOpenedInternal(@NonNull String cameraId, @NonNull String packageName) {
+ synchronized (mWmService.mGlobalLock) {
+ if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraId)) {
+ // Camera compat mode update has happened already or was cancelled
+ // because camera was closed.
+ return;
+ }
+ mCameraIdPackageBiMapping.put(packageName, cameraId);
+ // If there are multiple activities of the same package name and none of
+ // them are the top running activity, we do not apply treatment (rather than
+ // guessing and applying it to the wrong activity).
+ final ActivityRecord cameraActivity =
+ findUniqueActivityWithPackageName(packageName);
+ if (cameraActivity == null || cameraActivity.getTask() == null) {
+ return;
+ }
+ notifyListenersCameraOpened(cameraActivity, cameraId);
+ }
+ }
+
private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity,
@NonNull String cameraId) {
for (int i = 0; i < mCameraStateListeners.size(); i++) {
@@ -174,7 +176,13 @@
}
}
- private void notifyCameraClosed(@NonNull String cameraId) {
+ /**
+ * Processes camera closed, and schedules notifying listeners.
+ *
+ * <p>The delay is introduced to avoid flickering when switching between front and back camera,
+ * and when an activity is refreshed due to camera compat treatment.
+ */
+ private void notifyCameraClosedWithDelay(@NonNull String cameraId) {
ProtoLog.v(WM_DEBUG_STATES,
"Display id=%d is notified that Camera %s is closed.",
mDisplayContent.mDisplayId, cameraId);
@@ -217,9 +225,10 @@
// Already reconnected to this camera, no need to clean up.
return;
}
- if (mCameraActivity != null && mCurrentListenerForCameraActivity != null) {
+
+ if (mCurrentListenerForCameraActivity != null) {
boolean closeSuccessful =
- mCurrentListenerForCameraActivity.onCameraClosed(mCameraActivity, cameraId);
+ mCurrentListenerForCameraActivity.onCameraClosed(cameraId);
if (closeSuccessful) {
mCameraIdPackageBiMapping.removeCameraId(cameraId);
mCurrentListenerForCameraActivity = null;
@@ -231,8 +240,14 @@
}
// TODO(b/335165310): verify that this works in multi instance and permission dialogs.
+ /**
+ * Finds a visible activity with the given package name.
+ *
+ * <p>If there are multiple visible activities with a given package name, and none of them are
+ * the `topRunningActivity`, returns null.
+ */
@Nullable
- private ActivityRecord findCameraActivity(@NonNull String packageName) {
+ private ActivityRecord findUniqueActivityWithPackageName(@NonNull String packageName) {
final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
/* considerKeyguardState= */ true);
if (topActivity != null && topActivity.packageName.equals(packageName)) {
@@ -277,11 +292,11 @@
// TODO(b/336474959): try to decouple `cameraId` from the listeners.
boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
/**
- * Notifies the compat listener that an activity has closed the camera.
+ * Notifies the compat listener that camera is closed.
*
* @return true if cleanup has been successful - the notifier might try again if false.
*/
// TODO(b/336474959): try to decouple `cameraId` from the listeners.
- boolean onCameraClosed(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
+ boolean onCameraClosed(@NonNull String cameraId);
}
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 50ac801..66653ca 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -164,17 +164,16 @@
private void calculateAndCentreInitialBounds(Task task,
LaunchParamsController.LaunchParams outParams) {
// TODO(b/319819547): Account for app constraints so apps do not become letterboxed
- final Rect stableBounds = new Rect();
- task.getDisplayArea().getStableRect(stableBounds);
+ final Rect screenBounds = task.getDisplayArea().getBounds();
// The desired dimensions that a fully resizable window should take when initially entering
// desktop mode. Calculated as a percentage of the available display area as defined by the
// DESKTOP_MODE_INITIAL_BOUNDS_SCALE.
- final int desiredWidth = (int) (stableBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
- final int desiredHeight = (int) (stableBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredWidth = (int) (screenBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight = (int) (screenBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
outParams.mBounds.right = desiredWidth;
outParams.mBounds.bottom = desiredHeight;
- outParams.mBounds.offset(stableBounds.centerX() - outParams.mBounds.centerX(),
- stableBounds.centerY() - outParams.mBounds.centerY());
+ outParams.mBounds.offset(screenBounds.centerX() - outParams.mBounds.centerX(),
+ screenBounds.centerY() - outParams.mBounds.centerY());
}
private void initLogBuilder(Task task, ActivityRecord activity) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3652c4d..9371149 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4082,12 +4082,12 @@
final Transaction t = mWmService.mTransactionFactory.get();
forAllWindows(w -> {
final WindowStateAnimator wsa = w.mWinAnimator;
- if (wsa.mSurfaceController == null) {
+ if (wsa.mSurfaceControl == null) {
return;
}
if (!mWmService.mSessions.contains(wsa.mSession)) {
Slog.w(TAG_WM, "LEAKED SURFACE (session doesn't exist): "
- + w + " surface=" + wsa.mSurfaceController
+ + w + " surface=" + wsa.mSurfaceControl
+ " token=" + w.mToken
+ " pid=" + w.mSession.mPid
+ " uid=" + w.mSession.mUid);
@@ -4096,7 +4096,7 @@
mTmpWindow = w;
} else if (w.mActivityRecord != null && !w.mActivityRecord.isClientVisible()) {
Slog.w(TAG_WM, "LEAKED SURFACE (app token hidden): "
- + w + " surface=" + wsa.mSurfaceController
+ + w + " surface=" + wsa.mSurfaceControl
+ " token=" + w.mActivityRecord);
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE LEAK DESTROY: %s", w);
wsa.destroySurface(t);
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 9998e1a..1a0124a 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -342,12 +342,19 @@
}
@Override
- public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
+ public boolean onCameraClosed(@NonNull String cameraId) {
+ // Top activity in the same task as the camera activity, or `null` if the task is
+ // closed.
+ final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (topActivity == null) {
+ return true;
+ }
+
synchronized (this) {
// TODO(b/336474959): Once refresh is implemented in `CameraCompatFreeformPolicy`,
// consider checking this in CameraStateMonitor before notifying the listeners (this).
- if (isActivityForCameraIdRefreshing(cameraId)) {
+ if (isActivityForCameraIdRefreshing(topActivity, cameraId)) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is notified that camera is closed but activity is"
+ " still refreshing. Rescheduling an update.",
@@ -355,15 +362,15 @@
return false;
}
}
+
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is notified that Camera is closed, updating rotation.",
mDisplayContent.mDisplayId);
- final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
- /* considerKeyguardState= */ true);
- if (topActivity == null
- // Checking whether an activity in fullscreen rather than the task as this
- // camera compat treatment doesn't cover activity embedding.
- || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ // Checking whether an activity in fullscreen rather than the task as this camera compat
+ // treatment doesn't cover activity embedding.
+ // TODO(b/350495350): Consider checking whether this activity is the camera activity, or
+ // whether the top activity has the same task as the one which opened camera.
+ if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
return true;
}
recomputeConfigurationForCameraCompatIfNeeded(topActivity);
@@ -372,14 +379,13 @@
}
// TODO(b/336474959): Do we need cameraId here?
- private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) {
- final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
- /* considerKeyguardState= */ true);
- if (!isTreatmentEnabledForActivity(topActivity)
- || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
+ private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord activity,
+ @NonNull String cameraId) {
+ if (!isTreatmentEnabledForActivity(activity)
+ || !mCameraStateMonitor.isCameraWithIdRunningForActivity(activity, cameraId)) {
return false;
}
- return mActivityRefresher.isActivityRefreshing(topActivity);
+ return mActivityRefresher.isActivityRefreshing(activity);
}
private void recomputeConfigurationForCameraCompatIfNeeded(
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index bf99ccd..d79c11ce 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -408,9 +408,8 @@
final boolean intersectsTopCutout = topDisplayCutout.intersects(
width - (windowWidth / 2), 0,
width + (windowWidth / 2), topDisplayCutout.bottom);
- if (mClingWindow != null &&
- (windowWidth < 0 || (width > 0 && intersectsTopCutout))) {
- final View iconView = mClingWindow.findViewById(R.id.immersive_cling_icon);
+ if (windowWidth < 0 || (width > 0 && intersectsTopCutout)) {
+ final View iconView = findViewById(R.id.immersive_cling_icon);
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)
iconView.getLayoutParams();
lp.topMargin = topDisplayCutout.bottom;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 74dbd15..b496a65 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -623,7 +623,7 @@
// occlusion detection depending on the type or if it's a trusted overlay.
populateOverlayInputInfo(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceControl, inputWindowHandle);
return;
}
// Skip this window because it cannot possibly receive input.
@@ -687,7 +687,7 @@
if (w.mWinAnimator.hasSurface()) {
populateInputWindowHandle(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceControl, inputWindowHandle);
}
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 99697de..f2ccbc4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -667,7 +667,7 @@
boolean reclaimSomeSurfaceMemory(WindowStateAnimator winAnimator, String operation,
boolean secure) {
- final WindowSurfaceController surfaceController = winAnimator.mSurfaceController;
+ final SurfaceControl surfaceControl = winAnimator.mSurfaceControl;
boolean leakedSurface = false;
boolean killedApps = false;
EventLogTags.writeWmNoSurfaceMemory(winAnimator.mWin.toString(),
@@ -692,7 +692,7 @@
return;
}
final WindowStateAnimator wsa = w.mWinAnimator;
- if (wsa.mSurfaceController != null) {
+ if (wsa.mSurfaceControl != null) {
pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid);
}
}, false /* traverseTopToBottom */);
@@ -717,7 +717,7 @@
// app to request another one.
Slog.w(TAG_WM,
"Looks like we have reclaimed some memory, clearing surface for retry.");
- if (surfaceController != null) {
+ if (surfaceControl != null) {
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
"SURFACE RECOVER DESTROY: %s", winAnimator.mWin);
SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 31fda77..db0374e 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -324,7 +324,7 @@
if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) {
return;
}
- t.setSkipScreenshot(w.mWinAnimator.mSurfaceController.mSurfaceControl, skipScreenshot);
+ t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot);
}, false);
if (!skipScreenshot) {
// Use sync apply to apply the change immediately, so that the next
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 75e3e65..c26684f 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -109,8 +109,8 @@
private final String mStringName;
SurfaceSession mSurfaceSession;
private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
- /** Set of visible alert/app-overlay window surfaces connected to this session. */
- private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
+ /** Set of visible alert/app-overlay windows connected to this session. */
+ private final ArraySet<WindowState> mAlertWindows = new ArraySet<>();
private final DragDropController mDragDropController;
final boolean mCanAddInternalSystemWindow;
boolean mCanForceShowingInsets;
@@ -324,19 +324,19 @@
}
@Override
- public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
+ public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
final long ident = Binder.clearCallingIdentity();
try {
- return mService.mPolicy.performHapticFeedback(mUid, mPackageName,
- effectId, always, null, fromIme);
+ return mService.mPolicy.performHapticFeedback(mUid, mPackageName, effectId, null, flags,
+ privFlags);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
- public void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme) {
- performHapticFeedback(effectId, always, fromIme);
+ public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
+ performHapticFeedback(effectId, flags, privFlags);
}
/* Drag/drop */
@@ -769,9 +769,8 @@
return !mAddedWindows.isEmpty();
}
- void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
- boolean visible, int type) {
-
+ void onWindowSurfaceVisibilityChanged(WindowState window, boolean visible) {
+ final int type = window.mAttrs.type;
if (!isSystemAlertWindowType(type)) {
return;
}
@@ -782,7 +781,7 @@
final boolean noSystemOverlayPermission =
!mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay;
if (visible) {
- changed = mAlertWindowSurfaces.add(surfaceController);
+ changed = mAlertWindows.add(window);
if (type == TYPE_APPLICATION_OVERLAY) {
MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type,
false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -791,7 +790,7 @@
true /* only log for non-TYPE_APPLICATION_OVERLAY */);
}
} else {
- changed = mAlertWindowSurfaces.remove(surfaceController);
+ changed = mAlertWindows.remove(window);
if (type == TYPE_APPLICATION_OVERLAY) {
MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type,
false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -802,7 +801,7 @@
}
if (changed && noSystemOverlayPermission) {
- if (mAlertWindowSurfaces.isEmpty()) {
+ if (mAlertWindows.isEmpty()) {
cancelAlertWindowNotification();
} else if (mAlertWindowNotification == null && !isSatellitePointingUiPackage()) {
mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
@@ -815,7 +814,7 @@
if (changed && mPid != WindowManagerService.MY_PID) {
// Notify activity manager that the process contains overlay/alert windows, so it can
// adjust the importance score for the process.
- setHasOverlayUi(!mAlertWindowSurfaces.isEmpty());
+ setHasOverlayUi(!mAlertWindows.isEmpty());
}
}
@@ -859,7 +858,7 @@
}
mSurfaceSession = null;
mAddedWindows.clear();
- mAlertWindowSurfaces.clear();
+ mAlertWindows.clear();
setHasOverlayUi(false);
cancelAlertWindowNotification();
}
@@ -880,7 +879,7 @@
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size());
pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
- pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
+ pw.print(" mAlertWindows="); pw.print(mAlertWindows);
pw.print(" mClientDead="); pw.print(mClientDead);
pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName);
@@ -896,9 +895,9 @@
/** @return {@code true} if there is an alert window surface on the given display. */
boolean hasAlertWindowSurfaces(DisplayContent displayContent) {
- for (int i = mAlertWindowSurfaces.size() - 1; i >= 0; i--) {
- final WindowSurfaceController surfaceController = mAlertWindowSurfaces.valueAt(i);
- if (surfaceController.mAnimator.mWin.getDisplayContent() == displayContent) {
+ for (int i = mAlertWindows.size() - 1; i >= 0; i--) {
+ final WindowState window = mAlertWindows.valueAt(i);
+ if (window.mDisplayContent == displayContent) {
return true;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 439c7bb..561ff7d 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -1169,16 +1169,6 @@
}
}
- @Override
- public boolean isActivityEmbedded(IBinder activityToken) {
- synchronized (mGlobalLock) {
- final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken);
- return activity != null
- ? activity.isEmbeddedInHostContainer()
- : false;
- }
- }
-
@VisibleForTesting
@NonNull
IApplicationThread getAppThread(int pid, int uid) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2a3e945..f6a68d5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -1897,7 +1898,8 @@
}
}
- private void overrideAnimationOptionsToInfoIfNecessary(@NonNull TransitionInfo info) {
+ @VisibleForTesting
+ void overrideAnimationOptionsToInfoIfNecessary(@NonNull TransitionInfo info) {
if (mOverrideOptions == null) {
return;
}
@@ -1914,12 +1916,28 @@
changes.get(i).setAnimationOptions(mOverrideOptions);
// TODO(b/295805497): Extract mBackgroundColor from AnimationOptions.
changes.get(i).setBackgroundColor(mOverrideOptions.getBackgroundColor());
+ } else if (shouldApplyAnimOptionsToEmbeddedTf(container.asTaskFragment())) {
+ // We only override AnimationOptions because backgroundColor should be from
+ // TaskFragmentAnimationParams.
+ changes.get(i).setAnimationOptions(mOverrideOptions);
}
}
}
updateActivityTargetForCrossProfileAnimation(info);
}
+ private boolean shouldApplyAnimOptionsToEmbeddedTf(@Nullable TaskFragment taskFragment) {
+ if (taskFragment == null || !taskFragment.isEmbedded()) {
+ return false;
+ }
+ if (taskFragment.getAnimationParams().hasOverrideAnimation()) {
+ // Always respect animation overrides from TaskFragmentAnimationParams.
+ return false;
+ }
+ // ActivityEmbedding animation adapter only support custom animation
+ return mOverrideOptions != null && mOverrideOptions.getType() == ANIM_CUSTOM;
+ }
+
/**
* Updates activity open target if {@link #mOverrideOptions} is
* {@link ANIM_OPEN_CROSS_PROFILE_APPS}.
@@ -1929,8 +1947,7 @@
return;
}
for (int i = 0; i < mTargets.size(); ++i) {
- final ActivityRecord activity = mTargets.get(i).mContainer
- .asActivityRecord();
+ final ActivityRecord activity = mTargets.get(i).mContainer.asActivityRecord();
final TransitionInfo.Change change = info.getChanges().get(i);
if (activity == null || change.getMode() != TRANSIT_OPEN) {
continue;
@@ -2126,6 +2143,16 @@
}
/**
+
+ * Wallpaper will set itself as target if it wants to keep itself visible without a target.
+ */
+ private static boolean wallpaperIsOwnTarget(WallpaperWindowToken wallpaper) {
+ final WindowState target =
+ wallpaper.getDisplayContent().mWallpaperController.getWallpaperTarget();
+ return target != null && target.isDescendantOf(wallpaper);
+ }
+
+ /**
* Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones
*/
private void commitVisibleWallpapers(SurfaceControl.Transaction t) {
@@ -2133,8 +2160,13 @@
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
if (wallpaper != null) {
- if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
+ if (!wallpaper.isVisible() && (wallpaper.isVisibleRequested()
+ || (Flags.ensureWallpaperInTransitions() && showWallpaper))) {
wallpaper.commitVisibility(showWallpaper);
+ } else if (Flags.ensureWallpaperInTransitions() && wallpaper.isVisible()
+ && !showWallpaper && !wallpaper.getDisplayContent().isKeyguardLocked()
+ && !wallpaperIsOwnTarget(wallpaper)) {
+ wallpaper.setVisibleRequested(false);
}
if (showWallpaper && Flags.ensureWallpaperInTransitions()
&& wallpaper.isVisibleRequested()
@@ -2556,11 +2588,10 @@
if (wc.asWindowState() != null) continue;
final ChangeInfo changeInfo = changes.get(wc);
- // Reject no-ops, unless wallpaper
- if (!changeInfo.hasChanged()
- && (!Flags.ensureWallpaperInTransitions() || wc.asWallpaperToken() == null)) {
+ // Reject no-ops
+ if (!changeInfo.hasChanged()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " Rejecting as no-op: %s", wc);
+ " Rejecting as no-op: %s vis: %b", wc, wc.isVisibleRequested());
continue;
}
targets.add(changeInfo);
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 3044abd..cdb14ab 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -162,10 +162,6 @@
mTransparentPolicyState.clearInheritedCompatDisplayInsets();
}
- TransparentPolicyState getTransparentPolicyState() {
- return mTransparentPolicyState;
- }
-
/**
* In case of translucent activities, it consumes the {@link ActivityRecord} of the first opaque
* activity beneath using the given consumer and returns {@code true}.
@@ -176,7 +172,7 @@
@NonNull
Optional<ActivityRecord> getFirstOpaqueActivity() {
- return isRunning() ? Optional.of(mTransparentPolicyState.mFirstOpaqueActivity)
+ return isRunning() ? Optional.ofNullable(mTransparentPolicyState.mFirstOpaqueActivity)
: Optional.empty();
}
@@ -216,10 +212,6 @@
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
}
- private void inheritConfiguration(ActivityRecord firstOpaque) {
- mTransparentPolicyState.inheritFromOpaque(firstOpaque);
- }
-
/**
* Encapsulate the state for the current translucent activity when the transparent policy
* has started.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 700c069..ebbf6e3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2570,7 +2570,7 @@
// surface, let the client use that, but don't create new surface at this
// point.
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
- winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
+ winAnimator.getSurfaceControl(outSurfaceControl);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} else {
if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
@@ -2766,15 +2766,15 @@
result |= RELAYOUT_RES_SURFACE_CHANGED;
}
- WindowSurfaceController surfaceController;
+ SurfaceControl surfaceControl;
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
- surfaceController = winAnimator.createSurfaceLocked();
+ surfaceControl = winAnimator.createSurfaceLocked();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- if (surfaceController != null) {
- surfaceController.getSurfaceControl(outSurfaceControl);
+ if (surfaceControl != null) {
+ winAnimator.getSurfaceControl(outSurfaceControl);
ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);
} else {
@@ -6773,11 +6773,11 @@
if (windowState == null) {
return false;
}
- WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
- if (surfaceController == null) {
+ final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
return false;
}
- return surfaceController.clearWindowContentFrameStats();
+ return surfaceControl.clearContentFrameStats();
}
}
@@ -6792,15 +6792,15 @@
if (windowState == null) {
return null;
}
- WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
- if (surfaceController == null) {
+ final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
return null;
}
if (mTempWindowRenderStats == null) {
mTempWindowRenderStats = new WindowContentFrameStats();
}
WindowContentFrameStats stats = mTempWindowRenderStats;
- if (!surfaceController.getWindowContentFrameStats(stats)) {
+ if (!surfaceControl.getContentFrameStats(stats)) {
return null;
}
return stats;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 228eb76..9ebb89d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -98,6 +98,7 @@
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+import static com.android.input.flags.Flags.removeInputChannelFromWindowstate;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -633,6 +634,9 @@
// Input channel and input window handle used by the input dispatcher.
final InputWindowHandleWrapper mInputWindowHandle;
+ /**
+ * Only populated if flag REMOVE_INPUT_CHANNEL_FROM_WINDOWSTATE is disabled.
+ */
InputChannel mInputChannel;
/**
@@ -1497,7 +1501,7 @@
if (isDrawn()) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Orientation not waiting for draw in %s, surfaceController %s", this,
- winAnimator.mSurfaceController);
+ winAnimator.mSurfaceControl);
setOrientationChanging(false);
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mWmService.mDisplayFreezeTime);
@@ -1877,6 +1881,10 @@
* Input Manager uses when discarding windows from input consideration.
*/
boolean isPotentialDragTarget(boolean targetInterceptsGlobalDrag) {
+ if (removeInputChannelFromWindowstate()) {
+ return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
+ && mInputChannelToken != null && mInputWindowHandle != null;
+ }
return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
&& mInputChannel != null && mInputWindowHandle != null;
}
@@ -2417,7 +2425,7 @@
ProtoLog.v(WM_DEBUG_FOCUS, "Remove client=%x, surfaceController=%s Callers=%s",
System.identityHashCode(mClient.asBinder()),
- mWinAnimator.mSurfaceController,
+ mWinAnimator.mSurfaceControl,
Debug.getCallers(5));
final DisplayContent displayContent = getDisplayContent();
@@ -2428,10 +2436,10 @@
mOnBackInvokedCallbackInfo = null;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
+ "mDisplayFrozen=%b callers=%s",
- this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
+ this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
@@ -2608,7 +2616,7 @@
+ isVisibleRequestedOrAdding() + " isVisible: " + (isVisible()
&& mActivityRecord != null && mActivityRecord.isVisible()));
if (!isVisibleRequestedOrAdding()) {
- Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController
+ Slog.i(TAG_WM, " mSurfaceControl=" + mWinAnimator.mSurfaceControl
+ " relayoutCalled=" + mRelayoutCalled
+ " viewVis=" + mViewVisibility
+ " policyVis=" + isVisibleByPolicy()
@@ -2626,6 +2634,19 @@
}
void openInputChannel(@NonNull InputChannel outInputChannel) {
+ if (mInputChannelToken != null) {
+ throw new IllegalStateException("Window already has an input channel token.");
+ }
+ if (removeInputChannelFromWindowstate()) {
+ String name = getName();
+ InputChannel channel = mWmService.mInputManager.createInputChannel(name);
+ mInputChannelToken = channel.getToken();
+ mInputWindowHandle.setToken(mInputChannelToken);
+ mWmService.mInputToWindowMap.put(mInputChannelToken, this);
+ channel.copyTo(outInputChannel);
+ channel.dispose();
+ return;
+ }
if (mInputChannel != null) {
throw new IllegalStateException("Window already has an input channel.");
}
@@ -2657,9 +2678,11 @@
mInputChannelToken = null;
}
- if (mInputChannel != null) {
- mInputChannel.dispose();
- mInputChannel = null;
+ if (!removeInputChannelFromWindowstate()) {
+ if (mInputChannel != null) {
+ mInputChannel.dispose();
+ mInputChannel = null;
+ }
}
mInputWindowHandle.setToken(null);
}
@@ -4434,7 +4457,7 @@
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
- if (c.mWinAnimator.mSurfaceController != null) {
+ if (c.mWinAnimator.mSurfaceControl != null) {
c.performShowLocked();
// It hadn't been shown, which means layout not performed on it, so now we
// want to make sure to do a layout. If called from within the transaction
@@ -4891,7 +4914,7 @@
Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawn()
+ ", animating=" + isAnimating(TRANSITION | PARENTS));
if (!isDrawn()) {
- Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController
+ Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceControl
+ " pv=" + isVisibleByPolicy()
+ " mDrawState=" + mWinAnimator.mDrawState
+ " ph=" + isParentWindowHidden()
@@ -5512,13 +5535,13 @@
// been defined and so we can use static layers and leave it that way.
if (w.mAttrs.type == TYPE_APPLICATION_MEDIA) {
if (mWinAnimator.hasSurface()) {
- w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -2);
+ w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -2);
} else {
w.assignLayer(t, -2);
}
} else if (w.mAttrs.type == TYPE_APPLICATION_MEDIA_OVERLAY) {
if (mWinAnimator.hasSurface()) {
- w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -1);
+ w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -1);
} else {
w.assignLayer(t, -1);
}
@@ -5694,7 +5717,7 @@
}
SurfaceControl getClientViewRootSurface() {
- return mWinAnimator.getSurfaceControl();
+ return mWinAnimator.mSurfaceControl;
}
/** Drops a buffer for this window's view-root from a transaction */
@@ -6094,11 +6117,10 @@
}
getPendingTransaction().setSecure(mSurfaceControl, isSecure);
} else {
- if (mWinAnimator.mSurfaceController == null
- || mWinAnimator.mSurfaceController.mSurfaceControl == null) {
+ if (mWinAnimator.mSurfaceControl == null) {
return;
}
- getPendingTransaction().setSecure(mWinAnimator.mSurfaceController.mSurfaceControl,
+ getPendingTransaction().setSecure(mWinAnimator.mSurfaceControl,
isSecure);
}
if (mDisplayContent != null) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 397a6357..24a2a62 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.SurfaceControl.METADATA_OWNER_PID;
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -44,6 +48,7 @@
import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
+import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -52,6 +57,7 @@
import android.graphics.Rect;
import android.os.Debug;
import android.os.Trace;
+import android.util.EventLog;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Surface.OutOfResourcesException;
@@ -95,12 +101,13 @@
final Session mSession;
final WindowManagerPolicy mPolicy;
final Context mContext;
- final boolean mIsWallpaper;
private final WallpaperController mWallpaperControllerLocked;
boolean mAnimationIsEntrance;
- WindowSurfaceController mSurfaceController;
+ SurfaceControl mSurfaceControl;
+ private boolean mSurfaceShown;
+ private String mTitle;
float mShownAlpha = 0;
float mAlpha = 0;
@@ -164,7 +171,6 @@
mWin = win;
mSession = win.mSession;
mAttrType = win.mAttrs.type;
- mIsWallpaper = win.mIsWallpaper;
mWallpaperControllerLocked = win.getDisplayContent().mWallpaperController;
}
@@ -198,14 +204,30 @@
}
void hide(SurfaceControl.Transaction transaction, String reason) {
- if (!mLastHidden) {
- //dump();
- mLastHidden = true;
-
- if (mSurfaceController != null) {
- mSurfaceController.hide(transaction, reason);
- }
+ if (mLastHidden) {
+ return;
}
+ mLastHidden = true;
+ if (mSurfaceControl == null || !mSurfaceShown) {
+ return;
+ }
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, mTitle);
+
+ setShown(false);
+ transaction.hide(mSurfaceControl);
+ if (mWin.mIsWallpaper) {
+ final DisplayContent dc = mWin.getDisplayContent();
+ EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+ dc.mDisplayId, 0 /* request hidden */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+ }
+ }
+
+ private void setShown(boolean surfaceShown) {
+ mSurfaceShown = surfaceShown;
+ mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mWin, surfaceShown);
+ mWin.onSurfaceShownChanged(surfaceShown);
+ mSession.onWindowSurfaceVisibilityChanged(mWin, mSurfaceShown);
}
boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
@@ -221,7 +243,7 @@
if (mDrawState == DRAW_PENDING) {
ProtoLog.v(WM_DEBUG_DRAW,
"finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", mWin,
- mSurfaceController);
+ mSurfaceControl);
if (startingWindow) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);
}
@@ -248,7 +270,7 @@
return false;
}
ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
- mSurfaceController);
+ mSurfaceControl);
mDrawState = READY_TO_SHOW;
boolean result = false;
final ActivityRecord activity = mWin.mActivityRecord;
@@ -271,11 +293,11 @@
}
}
- WindowSurfaceController createSurfaceLocked() {
+ SurfaceControl createSurfaceLocked() {
final WindowState w = mWin;
- if (mSurfaceController != null) {
- return mSurfaceController;
+ if (mSurfaceControl != null) {
+ return mSurfaceControl;
}
w.setHasSurface(false);
@@ -312,10 +334,22 @@
final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
- mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
- flags, this, attrs.type);
+ mTitle = attrs.getTitle().toString();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+ mSurfaceControl = mWin.makeSurface()
+ .setParent(mWin.mSurfaceControl)
+ .setName(mTitle)
+ .setFormat(format)
+ .setFlags(flags)
+ .setMetadata(METADATA_WINDOW_TYPE, attrs.type)
+ .setMetadata(METADATA_OWNER_UID, mSession.mUid)
+ .setMetadata(METADATA_OWNER_PID, mSession.mPid)
+ .setCallsite("WindowSurfaceController")
+ .setBLASTLayer().build();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
if (!setScPropertiesInClient()) {
- mSurfaceController.setColorSpaceAgnostic(w.getPendingTransaction(),
+ setColorSpaceAgnosticLocked(
(attrs.privateFlags & LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
}
@@ -326,7 +360,7 @@
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
" CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x / %s",
- mSurfaceController, mSession.mSurfaceSession, mSession.mPid, attrs.format,
+ mSurfaceControl, mSession.mSurfaceSession, mSession.mPid, attrs.format,
flags, this);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "OutOfResourcesException creating surface");
@@ -340,7 +374,7 @@
}
if (DEBUG) {
- Slog.v(TAG, "Got surface: " + mSurfaceController
+ Slog.v(TAG, "Got surface: " + mSurfaceControl
+ ", set left=" + w.getFrame().left + " top=" + w.getFrame().top);
}
@@ -353,15 +387,19 @@
mLastHidden = true;
if (DEBUG) Slog.v(TAG, "Created surface " + this);
- return mSurfaceController;
+ return mSurfaceControl;
}
boolean hasSurface() {
- return mSurfaceController != null && mSurfaceController.hasSurface();
+ return mSurfaceControl != null;
+ }
+
+ void getSurfaceControl(SurfaceControl outSurfaceControl) {
+ outSurfaceControl.copyFrom(mSurfaceControl, "WindowStateAnimator.getSurfaceControl");
}
void destroySurfaceLocked(SurfaceControl.Transaction t) {
- if (mSurfaceController == null) {
+ if (mSurfaceControl == null) {
return;
}
@@ -370,7 +408,7 @@
try {
if (DEBUG_VISIBILITY) {
logWithStack(TAG, "Window " + this + " destroying surface "
- + mSurfaceController + ", session " + mSession);
+ + mSurfaceControl + ", session " + mSession);
}
ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s",
mWin, new RuntimeException().fillInStackTrace());
@@ -384,23 +422,13 @@
}
} catch (RuntimeException e) {
Slog.w(TAG, "Exception thrown when destroying Window " + this
- + " surface " + mSurfaceController + " session " + mSession + ": "
+ + " surface " + mSurfaceControl + " session " + mSession + ": "
+ e.toString());
}
-
- // Whether the surface was preserved (and copied to mPendingDestroySurface) or not, it
- // needs to be cleared to match the WindowState.mHasSurface state. It is also necessary
- // so it can be recreated successfully in mPendingDestroySurface case.
- mWin.setHasSurface(false);
- if (mSurfaceController != null) {
- mSurfaceController.setShown(false);
- }
- mSurfaceController = null;
- mDrawState = NO_SURFACE;
}
void computeShownFrameLocked() {
- if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
+ if (mWin.mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
return;
} else if (mWin.isDragResizeChanged()) {
// This window is awaiting a relayout because user just started (or ended)
@@ -454,14 +482,13 @@
mLastAlpha = mShownAlpha;
ProtoLog.i(WM_SHOW_TRANSACTIONS,
"SURFACE controller=%s alpha=%f HScale=%f, VScale=%f: %s",
- mSurfaceController, mShownAlpha, w.mHScale, w.mVScale, w);
+ mSurfaceControl, mShownAlpha, w.mHScale, w.mVScale, w);
- boolean prepared =
- mSurfaceController.prepareToShowInTransaction(t, mShownAlpha);
+ t.setAlpha(mSurfaceControl, mShownAlpha);
- if (prepared && mDrawState == HAS_DRAWN) {
+ if (mDrawState == HAS_DRAWN) {
if (mLastHidden) {
- mSurfaceController.showRobustly(t);
+ showRobustly(t);
mLastHidden = false;
final DisplayContent displayContent = w.getDisplayContent();
if (!displayContent.getLastHasContent()) {
@@ -494,18 +521,38 @@
}
}
- void setOpaqueLocked(boolean isOpaque) {
- if (mSurfaceController == null) {
+ private void showRobustly(SurfaceControl.Transaction t) {
+ if (mSurfaceShown) {
return;
}
- mSurfaceController.setOpaque(isOpaque);
+
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", mTitle);
+ if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this + " during relayout");
+ setShown(true);
+ t.show(mSurfaceControl);
+ if (mWin.mIsWallpaper) {
+ final DisplayContent dc = mWin.mDisplayContent;
+ EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+ dc.mDisplayId, 1 /* request shown */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+ }
+ }
+
+ void setOpaqueLocked(boolean isOpaque) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, mTitle);
+ mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
+ mService.scheduleAnimationLocked();
}
void setColorSpaceAgnosticLocked(boolean agnostic) {
- if (mSurfaceController == null) {
+ if (mSurfaceControl == null) {
return;
}
- mSurfaceController.setColorSpaceAgnostic(mWin.getPendingTransaction(), agnostic);
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, mTitle);
+ mWin.getPendingTransaction().setColorSpaceAgnostic(mSurfaceControl, agnostic);
}
void applyEnterAnimationLocked() {
@@ -521,7 +568,7 @@
// should be controlled by ActivityRecord in general. Wallpaper is also excluded because
// WallpaperController should handle it. Also skip play enter animation for the window
// below starting window.
- if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper
+ if (mAttrType != TYPE_BASE_APPLICATION && !mWin.mIsWallpaper
&& !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) {
applyAnimationLocked(transit, true);
}
@@ -614,8 +661,10 @@
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- if (mSurfaceController != null) {
- mSurfaceController.dumpDebug(proto, SURFACE);
+ if (mSurfaceControl != null) {
+ final long dumpToken = proto.start(SURFACE);
+ proto.write(SHOWN, mSurfaceShown);
+ proto.end(dumpToken);
}
proto.write(DRAW_STATE, mDrawState);
mSystemDecorRect.dumpDebug(proto, SYSTEM_DECOR_RECT);
@@ -626,8 +675,11 @@
if (mAnimationIsEntrance) {
pw.print(prefix); pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance);
}
- if (mSurfaceController != null) {
- mSurfaceController.dump(pw, prefix, dumpAll);
+ if (mSurfaceControl != null) {
+ if (dumpAll) {
+ pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
+ }
+ pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
}
if (dumpAll) {
pw.print(prefix); pw.print("mDrawState="); pw.print(drawStateToString());
@@ -659,31 +711,24 @@
}
boolean getShown() {
- if (mSurfaceController != null) {
- return mSurfaceController.getShown();
- }
- return false;
+ return mSurfaceControl != null && mSurfaceShown;
}
void destroySurface(SurfaceControl.Transaction t) {
- try {
- if (mSurfaceController != null) {
- mSurfaceController.destroy(t);
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception thrown when destroying surface " + this
- + " surface " + mSurfaceController + " session " + mSession + ": " + e);
- } finally {
- mWin.setHasSurface(false);
- mSurfaceController = null;
- mDrawState = NO_SURFACE;
+ if (mSurfaceControl == null) {
+ return;
}
- }
-
- SurfaceControl getSurfaceControl() {
- if (!hasSurface()) {
- return null;
+ ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
+ "Destroying surface %s called by %s", this, Debug.getCallers(8));
+ if (mWin.mIsWallpaper && !mWin.mWindowRemovalAllowed && !mWin.mRemoveOnExit) {
+ // The wallpaper surface should have the same lifetime as its window.
+ Slog.e(TAG, "Unexpected removing wallpaper surface of " + mWin
+ + " by " + Debug.getCallers(8));
}
- return mSurfaceController.mSurfaceControl;
+ t.remove(mSurfaceControl);
+ setShown(false);
+ mSurfaceControl = null;
+ mWin.setHasSurface(false);
+ mDrawState = NO_SURFACE;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
deleted file mode 100644
index d9766e0..0000000
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2015 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.wm;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.SurfaceControl.METADATA_OWNER_PID;
-import static android.view.SurfaceControl.METADATA_OWNER_UID;
-import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
-
-import android.os.Debug;
-import android.os.Trace;
-import android.util.EventLog;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl;
-import android.view.WindowContentFrameStats;
-
-import com.android.internal.protolog.ProtoLog;
-
-import java.io.PrintWriter;
-
-class WindowSurfaceController {
- static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfaceController" : TAG_WM;
-
- final WindowStateAnimator mAnimator;
-
- SurfaceControl mSurfaceControl;
-
- // Should only be set from within setShown().
- private boolean mSurfaceShown = false;
-
- private final String title;
-
- private final WindowManagerService mService;
-
- private final int mWindowType;
- private final Session mWindowSession;
-
-
- WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
- int windowType) {
- mAnimator = animator;
-
- title = name;
-
- mService = animator.mService;
- final WindowState win = animator.mWin;
- mWindowType = windowType;
- mWindowSession = win.mSession;
-
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
- mSurfaceControl = win.makeSurface()
- .setParent(win.getSurfaceControl())
- .setName(name)
- .setFormat(format)
- .setFlags(flags)
- .setMetadata(METADATA_WINDOW_TYPE, windowType)
- .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
- .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
- .setCallsite("WindowSurfaceController")
- .setBLASTLayer().build();
-
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- void hide(SurfaceControl.Transaction transaction, String reason) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, title);
-
- if (mSurfaceShown) {
- hideSurface(transaction);
- }
- }
-
- private void hideSurface(SurfaceControl.Transaction transaction) {
- if (mSurfaceControl == null) {
- return;
- }
- setShown(false);
- try {
- transaction.hide(mSurfaceControl);
- if (mAnimator.mIsWallpaper) {
- final DisplayContent dc = mAnimator.mWin.getDisplayContent();
- EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- dc.mDisplayId, 0 /* request hidden */,
- String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception hiding surface in " + this);
- }
- }
-
- void destroy(SurfaceControl.Transaction t) {
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
- "Destroying surface %s called by %s", this, Debug.getCallers(8));
- try {
- if (mSurfaceControl != null) {
- if (mAnimator.mIsWallpaper && !mAnimator.mWin.mWindowRemovalAllowed
- && !mAnimator.mWin.mRemoveOnExit) {
- // The wallpaper surface should have the same lifetime as its window.
- Slog.e(TAG, "Unexpected removing wallpaper surface of " + mAnimator.mWin
- + " by " + Debug.getCallers(8));
- }
- t.remove(mSurfaceControl);
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error destroying surface in: " + this, e);
- } finally {
- setShown(false);
- mSurfaceControl = null;
- }
- }
-
- boolean prepareToShowInTransaction(SurfaceControl.Transaction t, float alpha) {
- if (mSurfaceControl == null) {
- return false;
- }
-
- t.setAlpha(mSurfaceControl, alpha);
- return true;
- }
-
- void setOpaque(boolean isOpaque) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title);
-
- if (mSurfaceControl == null) {
- return;
- }
-
- mAnimator.mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
- mService.scheduleAnimationLocked();
- }
-
- void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, title);
-
- if (mSurfaceControl == null) {
- return;
- }
- t.setColorSpaceAgnostic(mSurfaceControl, agnostic);
- }
-
- void showRobustly(SurfaceControl.Transaction t) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
- if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
- + " during relayout");
-
- if (mSurfaceShown) {
- return;
- }
-
- setShown(true);
- t.show(mSurfaceControl);
- if (mAnimator.mIsWallpaper) {
- final DisplayContent dc = mAnimator.mWin.getDisplayContent();
- EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- dc.mDisplayId, 1 /* request shown */,
- String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
- }
- }
-
- boolean clearWindowContentFrameStats() {
- if (mSurfaceControl == null) {
- return false;
- }
- return mSurfaceControl.clearContentFrameStats();
- }
-
- boolean getWindowContentFrameStats(WindowContentFrameStats outStats) {
- if (mSurfaceControl == null) {
- return false;
- }
- return mSurfaceControl.getContentFrameStats(outStats);
- }
-
- boolean hasSurface() {
- return mSurfaceControl != null;
- }
-
- void getSurfaceControl(SurfaceControl outSurfaceControl) {
- outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
- }
-
- boolean getShown() {
- return mSurfaceShown;
- }
-
- void setShown(boolean surfaceShown) {
- mSurfaceShown = surfaceShown;
-
- mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mAnimator.mWin, surfaceShown);
-
- mAnimator.mWin.onSurfaceShownChanged(surfaceShown);
-
- if (mWindowSession != null) {
- mWindowSession.onWindowSurfaceVisibilityChanged(this, mSurfaceShown, mWindowType);
- }
- }
-
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(SHOWN, mSurfaceShown);
- proto.end(token);
- }
-
- public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
- if (dumpAll) {
- pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
- }
- pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
- }
-
- @Override
- public String toString() {
- return mSurfaceControl.toString();
- }
-}
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index ec7406a..4231149 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -179,6 +179,19 @@
<xs:element name="supportsVrr" type="xs:boolean" minOccurs="0">
<xs:annotation name="final"/>
</xs:element>
+ <!-- Table that translates doze brightness sensor values to brightness values in
+ the float scale [0, 1]; -1 means the current brightness should be kept.
+ The following formula should be used for conversion between nits and the float
+ scale: float = (nits - minNits) / (maxNits - minNits). minNits and maxNits are
+ defined in screenBrightnessMap. -->
+ <xs:element type="float-array" name="dozeBrightnessSensorValueToBrightness">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- The default screen brightness in the scale [0, 1] to use while the device is
+ dozing. -->
+ <xs:element type="nonNegativeDecimal" name="defaultDozeBrightness">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -859,6 +872,12 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="float-array">
+ <xs:sequence>
+ <xs:element name="item" type="nonNegativeDecimal" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="usiVersion">
<xs:element name="majorVersion" type="xs:nonNegativeInteger"
minOccurs="1" maxOccurs="1">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 68d74cf..cec2787 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -125,9 +125,11 @@
method public final java.math.BigInteger getAmbientLightHorizonLong();
method public final java.math.BigInteger getAmbientLightHorizonShort();
method public com.android.server.display.config.AutoBrightness getAutoBrightness();
+ method public final java.math.BigDecimal getDefaultDozeBrightness();
method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
+ method public final com.android.server.display.config.FloatArray getDozeBrightnessSensorValueToBrightness();
method public final com.android.server.display.config.EvenDimmerMode getEvenDimmer();
method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
@@ -163,9 +165,11 @@
method public final void setAmbientLightHorizonLong(java.math.BigInteger);
method public final void setAmbientLightHorizonShort(java.math.BigInteger);
method public void setAutoBrightness(com.android.server.display.config.AutoBrightness);
+ method public final void setDefaultDozeBrightness(java.math.BigDecimal);
method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
+ method public final void setDozeBrightnessSensorValueToBrightness(com.android.server.display.config.FloatArray);
method public final void setEvenDimmer(com.android.server.display.config.EvenDimmerMode);
method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
@@ -214,6 +218,11 @@
method public void setTransitionPoint(java.math.BigDecimal);
}
+ public class FloatArray {
+ ctor public FloatArray();
+ method public java.util.List<java.math.BigDecimal> getItem();
+ }
+
public class HbmTiming {
ctor public HbmTiming();
method @NonNull public final java.math.BigInteger getTimeMaxSecs_all();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e122fe0..032d6b5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1131,9 +1131,9 @@
}
@Override
- public void onUserUnlocked(@NonNull TargetUser user) {
- if (user.isPreCreated()) return;
- mService.handleOnUserUnlocked(user.getUserIdentifier());
+ public void onUserSwitching(@NonNull TargetUser from, @NonNull TargetUser to) {
+ if (to.isPreCreated()) return;
+ mService.handleOnUserSwitching(from.getUserIdentifier(), to.getUserIdentifier());
}
}
@@ -3831,8 +3831,8 @@
mDevicePolicyEngine.handleUnlockUser(userId);
}
- void handleOnUserUnlocked(int userId) {
- showNewUserDisclaimerIfNecessary(userId);
+ void handleOnUserSwitching(int fromUserId, int toUserId) {
+ showNewUserDisclaimerIfNecessary(toUserId);
}
void handleStopUser(int userId) {
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index 8ff1a7d..114fe32 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,9 +4,4 @@
<version>2</version>
<fqname>IAltitudeService/default</fqname>
</hal>
- <hal format="aidl">
- <name>android.frameworks.vibrator</name>
- <version>1</version>
- <fqname>IVibratorControlService/default</fqname>
- </hal>
</manifest>
diff --git a/services/manifest_services_android.frameworks.vibrator.xml b/services/manifest_services_android.frameworks.vibrator.xml
new file mode 100644
index 0000000..c287643
--- /dev/null
+++ b/services/manifest_services_android.frameworks.vibrator.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="framework">
+ <hal format="aidl">
+ <name>android.frameworks.vibrator</name>
+ <version>1</version>
+ <fqname>IVibratorControlService/default</fqname>
+ </hal>
+</manifest>
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 3d40f64..927146d 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -21,6 +21,7 @@
":services.net-sources",
],
static_libs: [
+ "modules-utils-build_system",
"netd-client",
"networkstack-client",
"net-utils-services-common",
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 8f630af..ce68b86 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -21,6 +21,7 @@
import static com.android.compatibility.common.util.SystemUtil.eventually;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -33,18 +34,20 @@
import android.graphics.Insets;
import android.os.RemoteException;
import android.provider.Settings;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
import android.util.Log;
import android.view.WindowManagerGlobal;
+import android.view.WindowManagerPolicyConstants;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
+import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
import com.android.apps.inputmethod.simpleime.testing.TestActivity;
@@ -66,6 +69,10 @@
private static final String TAG = "SimpleIMSTest";
private static final String INPUT_METHOD_SERVICE_NAME = ".SimpleInputMethodService";
private static final String EDIT_TEXT_DESC = "Input box";
+ private static final String INPUT_METHOD_NAV_BACK_ID =
+ "android:id/input_method_nav_back";
+ private static final String INPUT_METHOD_NAV_IME_SWITCHER_ID =
+ "android:id/input_method_nav_ime_switcher";
private static final long TIMEOUT_IN_SECONDS = 3;
private static final String ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
"settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 1";
@@ -697,6 +704,151 @@
assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
}
+ /**
+ * Verifies that clicking on the IME navigation bar back button hides the IME.
+ */
+ @Test
+ public void testBackButtonClick() throws Exception {
+ boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService()
+ .hasNavigationBar(mInputMethodService.getDisplayId());
+ assumeTrue("Must have a navigation bar", hasNavigationBar);
+ assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
+
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ // Ensure the IME navigation bar and the IME switch button are drawn.
+ mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(
+ InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR
+ | InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN
+ );
+ assertThat(mActivity.showImeWithWindowInsetsController()).isTrue();
+ },
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var backButtonUiObject = getUiObjectById(INPUT_METHOD_NAV_BACK_ID);
+ backButtonUiObject.click();
+ mInstrumentation.waitForIdleSync();
+
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
+
+ /**
+ * Verifies that long clicking on the IME navigation bar back button hides the IME.
+ */
+ @Test
+ public void testBackButtonLongClick() throws Exception {
+ boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService()
+ .hasNavigationBar(mInputMethodService.getDisplayId());
+ assumeTrue("Must have a navigation bar", hasNavigationBar);
+ assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
+
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ // Ensure the IME navigation bar and the IME switch button are drawn.
+ mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(
+ InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR
+ | InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN
+ );
+ assertThat(mActivity.showImeWithWindowInsetsController()).isTrue();
+ },
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var backButtonUiObject = getUiObjectById(INPUT_METHOD_NAV_BACK_ID);
+ backButtonUiObject.longClick();
+ mInstrumentation.waitForIdleSync();
+
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
+
+ /**
+ * Verifies that clicking on the IME switch button shows the Input Method Switcher Menu.
+ */
+ @Test
+ public void testImeSwitchButtonClick() throws Exception {
+ boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService()
+ .hasNavigationBar(mInputMethodService.getDisplayId());
+ assumeTrue("Must have a navigation bar", hasNavigationBar);
+ assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
+
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ // Ensure the IME navigation bar and the IME switch button are drawn.
+ mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(
+ InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR
+ | InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN
+ );
+ assertThat(mActivity.showImeWithWindowInsetsController()).isTrue();
+ },
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var imm = mContext.getSystemService(InputMethodManager.class);
+
+ final var imeSwitchButtonUiObject = getUiObjectById(INPUT_METHOD_NAV_IME_SWITCHER_ID);
+ imeSwitchButtonUiObject.click();
+ mInstrumentation.waitForIdleSync();
+
+ assertWithMessage("Input Method Switcher Menu is shown")
+ .that(isInputMethodPickerShown(imm))
+ .isTrue();
+
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ // Hide the Picker menu before finishing.
+ mUiDevice.pressBack();
+ }
+
+ /**
+ * Verifies that long clicking on the IME switch button shows the Input Method Switcher Menu.
+ */
+ @Test
+ public void testImeSwitchButtonLongClick() throws Exception {
+ boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService()
+ .hasNavigationBar(mInputMethodService.getDisplayId());
+ assumeTrue("Must have a navigation bar", hasNavigationBar);
+ assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
+
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ // Ensure the IME navigation bar and the IME switch button are drawn.
+ mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(
+ InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR
+ | InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN
+ );
+ assertThat(mActivity.showImeWithWindowInsetsController()).isTrue();
+ },
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var imm = mContext.getSystemService(InputMethodManager.class);
+
+ final var imeSwitchButtonUiObject = getUiObjectById(INPUT_METHOD_NAV_IME_SWITCHER_ID);
+ imeSwitchButtonUiObject.longClick();
+ mInstrumentation.waitForIdleSync();
+
+ assertWithMessage("Input Method Switcher Menu is shown")
+ .that(isInputMethodPickerShown(imm))
+ .isTrue();
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ // Hide the Picker menu before finishing.
+ mUiDevice.pressBack();
+ }
+
private void verifyInputViewStatus(
Runnable runnable, boolean expected, boolean inputViewStarted)
throws InterruptedException {
@@ -844,6 +996,32 @@
return SystemUtil.runShellCommandOrThrow(cmd);
}
+ /**
+ * Checks if the Input Method Switcher Menu is shown. This runs by adopting the Shell's
+ * permission to ensure we have TEST_INPUT_METHOD permission.
+ */
+ private static boolean isInputMethodPickerShown(@NonNull InputMethodManager imm) {
+ return SystemUtil.runWithShellPermissionIdentity(imm::isInputMethodPickerShown);
+ }
+
+ @NonNull
+ private UiObject2 getUiObjectById(@NonNull String id) {
+ final var uiObject = mUiDevice.wait(
+ Until.findObject(By.res(id)),
+ TimeUnit.SECONDS.toMillis(TIMEOUT_IN_SECONDS));
+ assertThat(uiObject).isNotNull();
+ return uiObject;
+ }
+
+ /**
+ * Returns {@code true} if the navigation mode is gesture nav, and {@code false} otherwise.
+ */
+ private boolean isGestureNavEnabled() {
+ return mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode)
+ == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+ }
+
private void clickOnEditorText() {
// Find the editText and click it.
UiObject2 editTextUiObject =
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index bacde10..c2a069d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -68,7 +68,6 @@
import com.android.internal.view.IInputMethodManager;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
-import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.input.InputManagerInternal;
import com.android.server.pm.UserManagerInternal;
@@ -161,7 +160,7 @@
.strictness(Strictness.LENIENT)
.spyStatic(InputMethodUtils.class)
.mockStatic(ServiceManager.class)
- .mockStatic(SystemServerInitThreadPool.class)
+ .spyStatic(AdditionalSubtypeMapRepository.class)
.startMocking();
mContext = spy(InstrumentationRegistry.getInstrumentation().getContext());
@@ -234,9 +233,8 @@
doNothing().when(() -> InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
any(PackageManager.class), anyList()));
- // Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(),
- // which is ok to be mocked out for now.
- doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString()));
+ // The background writer thread in AdditionalSubtypeMapRepository should be stubbed out.
+ doNothing().when(AdditionalSubtypeMapRepository::startWriterThread);
mServiceThread =
new ServiceThread(
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index e81cf9d..dc03732 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -16,15 +16,26 @@
package com.android.server.inputmethod;
+import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_AUTO;
+import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_RECENT;
+import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_STATIC;
+import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.SwitchMode;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -35,6 +46,7 @@
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -51,6 +63,9 @@
private static final String SYSTEM_LOCALE = "en_US";
private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@NonNull
private static InputMethodSubtype createTestSubtype(@NonNull String locale) {
return new InputMethodSubtypeBuilder()
@@ -170,7 +185,7 @@
subtype = createTestSubtype(currentItem.mSubtypeName.toString());
}
final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme,
- currentItem.mImi, subtype);
+ currentItem.mImi, subtype, MODE_STATIC, true /* forward */);
assertEquals(nextItem, nextIme);
}
@@ -185,15 +200,16 @@
}
}
- private void onUserAction(@NonNull ControllerImpl controller,
+ private boolean onUserAction(@NonNull ControllerImpl controller,
@NonNull ImeSubtypeListItem subtypeListItem) {
InputMethodSubtype subtype = null;
if (subtypeListItem.mSubtypeName != null) {
subtype = createTestSubtype(subtypeListItem.mSubtypeName.toString());
}
- controller.onUserActionLocked(subtypeListItem.mImi, subtype);
+ return controller.onUserActionLocked(subtypeListItem.mImi, subtype);
}
+ @RequiresFlagsDisabled(Flags.FLAG_IME_SWITCHER_REVAMP)
@Test
public void testControllerImpl() {
final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes();
@@ -213,7 +229,7 @@
final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7);
final ControllerImpl controller = ControllerImpl.createFrom(
- null /* currentInstance */, enabledItems);
+ null /* currentInstance */, enabledItems, new ArrayList<>());
// switching-aware loop
assertRotationOrder(controller, false /* onlyCurrentIme */,
@@ -257,6 +273,7 @@
disabledSubtypeUnawareIme, null);
}
+ @RequiresFlagsDisabled(Flags.FLAG_IME_SWITCHER_REVAMP)
@Test
public void testControllerImplWithUserAction() {
final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
@@ -270,7 +287,7 @@
final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7);
final ControllerImpl controller = ControllerImpl.createFrom(
- null /* currentInstance */, enabledItems);
+ null /* currentInstance */, enabledItems, new ArrayList<>());
// === switching-aware loop ===
assertRotationOrder(controller, false /* onlyCurrentIme */,
@@ -320,7 +337,7 @@
// Rotation order should be preserved when created with the same subtype list.
final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes();
final ControllerImpl newController = ControllerImpl.createFrom(controller,
- sameEnabledItems);
+ sameEnabledItems, new ArrayList<>());
assertRotationOrder(newController, false /* onlyCurrentIme */,
subtypeAwareIme, latinIme_fr, latinIme_en_us, japaneseIme_ja_jp);
assertRotationOrder(newController, false /* onlyCurrentIme */,
@@ -332,7 +349,7 @@
latinIme_en_us, latinIme_fr, subtypeAwareIme, switchingUnawareLatinIme_en_uk,
switchUnawareJapaneseIme_ja_jp, subtypeUnawareIme);
final ControllerImpl anotherController = ControllerImpl.createFrom(controller,
- differentEnabledItems);
+ differentEnabledItems, new ArrayList<>());
assertRotationOrder(anotherController, false /* onlyCurrentIme */,
latinIme_en_us, latinIme_fr, subtypeAwareIme);
assertRotationOrder(anotherController, false /* onlyCurrentIme */,
@@ -370,6 +387,7 @@
assertFalse(item_en_us_allcaps.mIsSystemLanguage);
}
+ @RequiresFlagsDisabled(Flags.FLAG_IME_SWITCHER_REVAMP)
@SuppressWarnings("SelfComparison")
@Test
public void testImeSubtypeListComparator() {
@@ -471,4 +489,739 @@
assertNotEquals(ime2, ime1);
}
}
+
+ /** Verifies the static mode. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testModeStatic() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var english = items.get(0);
+ final var french = items.get(1);
+ final var italian = items.get(2);
+ final var simple = items.get(3);
+ final var latinIme = List.of(english, french, italian);
+ final var simpleIme = List.of(simple);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareLatinIme", "HardwareLatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(hardwareItems, "HardwareSimpleIme", "HardwareSimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareEnglish = hardwareItems.get(0);
+ final var hardwareFrench = hardwareItems.get(1);
+ final var hardwareItalian = hardwareItems.get(2);
+ final var hardwareSimple = hardwareItems.get(3);
+ final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
+ final var hardwareSimpleIme = List.of(hardwareSimple);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ final int mode = MODE_STATIC;
+
+ // Static mode matches the given items order.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // Set french IME as most recent.
+ assertTrue("Recency updated for french IME", onUserAction(controller, french));
+
+ // Static mode is not influenced by recency updates on non-hardware item.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertTrue("Recency updated for french hardware IME",
+ onUserAction(controller, hardwareFrench));
+
+ // Static mode is not influenced by recency updates on hardware item.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+ }
+
+ /** Verifies the recency mode. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testModeRecent() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var english = items.get(0);
+ final var french = items.get(1);
+ final var italian = items.get(2);
+ final var simple = items.get(3);
+ final var latinIme = List.of(english, french, italian);
+ final var simpleIme = List.of(simple);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareLatinIme", "HardwareLatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(hardwareItems, "HardwareSimpleIme", "HardwareSimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareEnglish = hardwareItems.get(0);
+ final var hardwareFrench = hardwareItems.get(1);
+ final var hardwareItalian = hardwareItems.get(2);
+ final var hardwareSimple = hardwareItems.get(3);
+ final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
+ final var hardwareSimpleIme = List.of(hardwareSimple);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ final int mode = MODE_RECENT;
+
+ // Recency order is initialized to static order.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertTrue("Recency updated for french IME", onUserAction(controller, french));
+ final var recencyItems = List.of(french, english, italian, simple);
+ final var recencyLatinIme = List.of(french, english, italian);
+ final var recencySimpleIme = List.of(simple);
+
+ // The order of non-hardware items is updated.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ // The order of hardware items remains unchanged for an action on a non-hardware item.
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertFalse("Recency not updated again for same IME", onUserAction(controller, french));
+
+ // The order of non-hardware items remains unchanged.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ // The order of hardware items remains unchanged.
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertTrue("Recency updated for french hardware IME",
+ onUserAction(controller, hardwareFrench));
+
+ final var recencyHardwareItems =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian, hardwareSimple);
+ final var recencyHardwareLatinIme =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian);
+ final var recencyHardwareSimpleIme = List.of(hardwareSimple);
+
+ // The order of non-hardware items is unchanged.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ // The order of hardware items is updated.
+ assertNextOrder(controller, true /* forHardware */, mode,
+ recencyHardwareItems, List.of(recencyHardwareLatinIme, recencyHardwareSimpleIme));
+ }
+
+ /** Verifies the auto mode. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testModeAuto() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var english = items.get(0);
+ final var french = items.get(1);
+ final var italian = items.get(2);
+ final var simple = items.get(3);
+ final var latinIme = List.of(english, french, italian);
+ final var simpleIme = List.of(simple);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareLatinIme", "HardwareLatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(hardwareItems, "HardwareSimpleIme", "HardwareSimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareEnglish = hardwareItems.get(0);
+ final var hardwareFrench = hardwareItems.get(1);
+ final var hardwareItalian = hardwareItems.get(2);
+ final var hardwareSimple = hardwareItems.get(3);
+ final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
+ final var hardwareSimpleIme = List.of(hardwareSimple);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ final int mode = MODE_AUTO;
+
+ // Auto mode resolves to static order initially.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // User action on french IME.
+ assertTrue("Recency updated for french IME", onUserAction(controller, french));
+
+ final var recencyItems = List.of(french, english, italian, simple);
+ final var recencyLatinIme = List.of(french, english, italian);
+ final var recencySimpleIme = List.of(simple);
+
+ // Auto mode resolves to recency order for the first forward after user action, and to
+ // static order for the backwards direction.
+ assertNextOrder(controller, false /* forHardware */, mode, true /* forward */,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+ assertNextOrder(controller, false /* forHardware */, mode, false /* forward */,
+ items.reversed(), List.of(latinIme.reversed(), simpleIme.reversed()));
+
+ // Auto mode resolves to recency order for the first forward after user action,
+ // but the recency was not updated for hardware items, so it's equivalent to static order.
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // Change IME, reset user action having happened.
+ controller.onInputMethodSubtypeChanged();
+
+ // Auto mode resolves to static order as there was no user action since changing IMEs.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // User action on french IME again.
+ assertFalse("Recency not updated again for same IME", onUserAction(controller, french));
+
+ // Auto mode still resolves to static order, as a user action on the currently most
+ // recent IME has no effect.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // User action on hardware french IME.
+ assertTrue("Recency updated for french hardware IME",
+ onUserAction(controller, hardwareFrench));
+
+ final var recencyHardware =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian, hardwareSimple);
+ final var recencyHardwareLatin =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian);
+ final var recencyHardwareSimple = List.of(hardwareSimple);
+
+ // Auto mode resolves to recency order for the first forward direction after a user action
+ // on a hardware IME, and to static order for the backwards direction.
+ assertNextOrder(controller, false /* forHardware */, mode, true /* forward */,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+ assertNextOrder(controller, false /* forHardware */, mode, false /* forward */,
+ items.reversed(), List.of(latinIme.reversed(), simpleIme.reversed()));
+
+ assertNextOrder(controller, true /* forHardware */, mode, true /* forward */,
+ recencyHardware, List.of(recencyHardwareLatin, recencyHardwareSimple));
+
+ assertNextOrder(controller, true /* forHardware */, mode, false /* forward */,
+ hardwareItems.reversed(),
+ List.of(hardwareLatinIme.reversed(), hardwareSimpleIme.reversed()));
+ }
+
+ /**
+ * Verifies that the recency order is preserved only when updating with an equal list of items.
+ */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testUpdateList() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var english = items.get(0);
+ final var french = items.get(1);
+ final var italian = items.get(2);
+ final var simple = items.get(3);
+
+ final var latinIme = List.of(english, french, italian);
+ final var simpleIme = List.of(simple);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareLatinIme", "HardwareLatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(hardwareItems, "HardwareSimpleIme", "HardwareSimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareEnglish = hardwareItems.get(0);
+ final var hardwareFrench = hardwareItems.get(1);
+ final var hardwareItalian = hardwareItems.get(2);
+ final var hardwareSimple = hardwareItems.get(3);
+
+ final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
+ final var hardwareSimpleIme = List.of(hardwareSimple);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ final int mode = MODE_RECENT;
+
+ // Recency order is initialized to static order.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // User action on french IME.
+ assertTrue("Recency updated for french IME", onUserAction(controller, french));
+
+ final var equalItems = new ArrayList<>(items);
+ final var otherItems = new ArrayList<>(items);
+ otherItems.remove(simple);
+
+ final var equalController = ControllerImpl.createFrom(controller, equalItems,
+ hardwareItems);
+ final var otherController = ControllerImpl.createFrom(controller, otherItems,
+ hardwareItems);
+
+ final var recencyItems = List.of(french, english, italian, simple);
+ final var recencyLatinIme = List.of(french, english, italian);
+ final var recencySimpleIme = List.of(simple);
+
+ assertNextOrder(controller, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ // The order of equal non-hardware items is unchanged.
+ assertNextOrder(equalController, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ // The order of other hardware items is reset.
+ assertNextOrder(otherController, false /* forHardware */, mode,
+ latinIme, List.of(latinIme));
+
+ // The order of hardware remains unchanged.
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertNextOrder(equalController, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertNextOrder(otherController, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertTrue("Recency updated for french hardware IME",
+ onUserAction(controller, hardwareFrench));
+
+ final var equalHardwareItems = new ArrayList<>(hardwareItems);
+ final var otherHardwareItems = new ArrayList<>(hardwareItems);
+ otherHardwareItems.remove(hardwareSimple);
+
+ final var equalHardwareController = ControllerImpl.createFrom(controller, items,
+ equalHardwareItems);
+ final var otherHardwareController = ControllerImpl.createFrom(controller, items,
+ otherHardwareItems);
+
+ final var recencyHardwareItems =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian, hardwareSimple);
+ final var recencyHardwareLatinIme =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian);
+ final var recencyHardwareSimpleIme = List.of(hardwareSimple);
+
+ // The order of non-hardware items remains unchanged.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ assertNextOrder(equalHardwareController, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ assertNextOrder(otherHardwareController, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ recencyHardwareItems, List.of(recencyHardwareLatinIme, recencyHardwareSimpleIme));
+
+ // The order of equal hardware items is unchanged.
+ assertNextOrder(equalHardwareController, true /* forHardware */, mode,
+ recencyHardwareItems, List.of(recencyHardwareLatinIme, recencyHardwareSimpleIme));
+
+ // The order of other hardware items is reset.
+ assertNextOrder(otherHardwareController, true /* forHardware */, mode,
+ hardwareLatinIme, List.of(hardwareLatinIme));
+ }
+
+ /** Verifies that switch aware and switch unaware IMEs are combined together. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testSwitchAwareAndUnawareCombined() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "switchAware", "switchAware",
+ null, true /* supportsSwitchingToNextInputMethod*/);
+ addTestImeSubtypeListItems(items, "switchUnaware", "switchUnaware",
+ null, false /* supportsSwitchingToNextInputMethod*/);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "hardwareSwitchAware", "hardwareSwitchAware",
+ null, true /* supportsSwitchingToNextInputMethod*/);
+ addTestImeSubtypeListItems(hardwareItems, "hardwareSwitchUnaware", "hardwareSwitchUnaware",
+ null, false /* supportsSwitchingToNextInputMethod*/);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ for (int mode = MODE_STATIC; mode <= MODE_AUTO; mode++) {
+ assertNextOrder(controller, false /* forHardware */, false /* onlyCurrentIme */,
+ mode, true /* forward */, items);
+ assertNextOrder(controller, false /* forHardware */, false /* onlyCurrentIme */,
+ mode, false /* forward */, items.reversed());
+
+ assertNextOrder(controller, true /* forHardware */, false /* onlyCurrentIme */,
+ mode, true /* forward */, hardwareItems);
+ assertNextOrder(controller, true /* forHardware */, false /* onlyCurrentIme */,
+ mode, false /* forward */, hardwareItems.reversed());
+ }
+ }
+
+ /** Verifies that an empty controller can't take any actions. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testEmptyList() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, List.of(),
+ List.of());
+
+ assertNoAction(controller, false /* forHardware */, items);
+ assertNoAction(controller, true /* forHardware */, hardwareItems);
+ }
+
+ /** Verifies that a controller with a single item can't take any actions. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testSingleItemList() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */,
+ List.of(items.get(0)), List.of(hardwareItems.get(0)));
+
+ assertNoAction(controller, false /* forHardware */, items);
+ assertNoAction(controller, true /* forHardware */, hardwareItems);
+ }
+
+ /** Verifies that a controller can't take any actions for unknown items. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testUnknownItems() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+ final var unknownItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(unknownItems, "UnknownIme", "UnknownIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+ final var unknownHardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(unknownHardwareItems, "HardwareUnknownIme", "HardwareUnknownIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ assertNoAction(controller, false /* forHardware */, unknownItems);
+ assertNoAction(controller, true /* forHardware */, unknownHardwareItems);
+ }
+
+ /** Verifies that the IME name does influence the comparison order. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareImeName() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var imeX = createTestItem(component, "ImeX", "A", "en_US", 0);
+ final var imeY = createTestItem(component, "ImeY", "A", "en_US", 0);
+
+ assertTrue("Smaller IME name should be smaller.", imeX.compareTo(imeY) < 0);
+ assertTrue("Larger IME name should be larger.", imeY.compareTo(imeX) > 0);
+ }
+
+ /** Verifies that the IME ID does influence the comparison order. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareImeId() {
+ final var component1 = new ComponentName("com.example.ime1", "Ime");
+ final var component2 = new ComponentName("com.example.ime2", "Ime");
+ final var ime1 = createTestItem(component1, "Ime", "A", "en_US", 0);
+ final var ime2 = createTestItem(component2, "Ime", "A", "en_US", 0);
+
+ assertTrue("Smaller IME ID should be smaller.", ime1.compareTo(ime2) < 0);
+ assertTrue("Larger IME ID should be larger.", ime2.compareTo(ime1) > 0);
+ }
+
+ /** Verifies that comparison on self returns an equal order. */
+ @SuppressWarnings("SelfComparison")
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareSelf() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var item = createTestItem(component, "Ime", "A", "en_US", 0);
+
+ assertEquals("Item should have the same order to itself.", 0, item.compareTo(item));
+ }
+
+ /** Verifies that comparison on an equivalent item returns an equal order. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareEquivalent() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var item = createTestItem(component, "Ime", "A", "en_US", 0);
+ final var equivalent = createTestItem(component, "Ime", "A", "en_US", 0);
+
+ assertEquals("Equivalent items should have the same order.", 0, item.compareTo(equivalent));
+ }
+
+ /**
+ * Verifies that the system locale and system language do not the influence comparison order.
+ */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareSystemLocaleSystemLanguage() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var japanese = createTestItem(component, "Ime", "A", "ja_JP", 0);
+ final var systemLanguage = createTestItem(component, "Ime", "A", "en_GB", 0);
+ final var systemLocale = createTestItem(component, "Ime", "A", "en_US", 0);
+
+ assertFalse(japanese.mIsSystemLanguage);
+ assertFalse(japanese.mIsSystemLocale);
+ assertTrue(systemLanguage.mIsSystemLanguage);
+ assertFalse(systemLanguage.mIsSystemLocale);
+ assertTrue(systemLocale.mIsSystemLanguage);
+ assertTrue(systemLocale.mIsSystemLocale);
+
+ assertEquals("System language shouldn't influence comparison over non-system language.",
+ 0, japanese.compareTo(systemLanguage));
+ assertEquals("System locale shouldn't influence comparison over non-system locale.",
+ 0, japanese.compareTo(systemLocale));
+ assertEquals("System locale shouldn't influence comparison over system language.",
+ 0, systemLanguage.compareTo(systemLocale));
+ }
+
+ /** Verifies that the subtype name does not influence the comparison order. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareSubtypeName() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var subtypeA = createTestItem(component, "Ime", "A", "en_US", 0);
+ final var subtypeB = createTestItem(component, "Ime", "B", "en_US", 0);
+
+ assertEquals("Subtype name shouldn't influence comparison.",
+ 0, subtypeA.compareTo(subtypeB));
+ }
+
+ /** Verifies that the subtype index does not influence the comparison order. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareSubtypeIndex() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var subtype0 = createTestItem(component, "Ime1", "A", "en_US", 0);
+ final var subtype1 = createTestItem(component, "Ime1", "A", "en_US", 1);
+
+ assertEquals("Subtype index shouldn't influence comparison.",
+ 0, subtype0.compareTo(subtype1));
+ }
+
+ /**
+ * Verifies that the controller's next item order matches the given one, and cycles back at
+ * the end, both across all IMEs, and also per each IME. If a single item is given, verifies
+ * that no next item is returned.
+ *
+ * @param controller the controller to use for finding the next items.
+ * @param forHardware whether to find the next hardware item, or software item.
+ * @param mode the switching mode.
+ * @param forward whether to search forwards or backwards in the list.
+ * @param allItems the list of items across all IMEs.
+ * @param perImeItems the list of lists of items per IME.
+ */
+ private static void assertNextOrder(@NonNull ControllerImpl controller, boolean forHardware,
+ @SwitchMode int mode, boolean forward, @NonNull List<ImeSubtypeListItem> allItems,
+ @NonNull List<List<ImeSubtypeListItem>> perImeItems) {
+ assertNextOrder(controller, forHardware, false /* onlyCurrentIme */, mode,
+ forward, allItems);
+
+ for (var imeItems : perImeItems) {
+ assertNextOrder(controller, forHardware, true /* onlyCurrentIme */, mode,
+ forward, imeItems);
+ }
+ }
+
+ /**
+ * Verifies that the controller's next item order matches the given one, and cycles back at
+ * the end, both across all IMEs, and also per each IME. This checks the forward direction
+ * with the given items, and the backwards order with the items reversed. If a single item is
+ * given, verifies that no next item is returned.
+ *
+ * @param controller the controller to use for finding the next items.
+ * @param forHardware whether to find the next hardware item, or software item.
+ * @param mode the switching mode.
+ * @param allItems the list of items across all IMEs.
+ * @param perImeItems the list of lists of items per IME.
+ */
+ private static void assertNextOrder(@NonNull ControllerImpl controller, boolean forHardware,
+ @SwitchMode int mode, @NonNull List<ImeSubtypeListItem> allItems,
+ @NonNull List<List<ImeSubtypeListItem>> perImeItems) {
+ assertNextOrder(controller, forHardware, false /* onlyCurrentIme */, mode,
+ true /* forward */, allItems);
+ assertNextOrder(controller, forHardware, false /* onlyCurrentIme */, mode,
+ false /* forward */, allItems.reversed());
+
+ for (var imeItems : perImeItems) {
+ assertNextOrder(controller, forHardware, true /* onlyCurrentIme */, mode,
+ true /* forward */, imeItems);
+ assertNextOrder(controller, forHardware, true /* onlyCurrentIme */, mode,
+ false /* forward */, imeItems.reversed());
+ }
+ }
+
+ /**
+ * Verifies that the controller's next item order (starting from the first one in {@code items}
+ * matches the given on, and cycles back at the end. If a single item is given, verifies that
+ * no next item is returned.
+ *
+ * @param controller the controller to use for finding the next items.
+ * @param forHardware whether to find the next hardware item, or software item.
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param mode the switching mode.
+ * @param forward whether to search forwards or backwards in the list.
+ * @param items the list of items to verify, in the expected order.
+ */
+ private static void assertNextOrder(@NonNull ControllerImpl controller,
+ boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward,
+ @NonNull List<ImeSubtypeListItem> items) {
+ final int numItems = items.size();
+ if (numItems == 0) {
+ return;
+ } else if (numItems == 1) {
+ // Single item controllers should never return a next item.
+ assertNextItem(controller, forHardware, onlyCurrentIme, mode, forward, items.get(0),
+ null /* expectedNext*/);
+ return;
+ }
+
+ var item = items.get(0);
+
+ final var expectedNextItems = new ArrayList<>(items);
+ // Add first item in the last position of expected order, to ensure the order is cyclic.
+ expectedNextItems.add(item);
+
+ final var nextItems = new ArrayList<>();
+ // Add first item in the first position of actual order, to ensure the order is cyclic.
+ nextItems.add(item);
+
+ // Compute the nextItems starting from the first given item, and compare the order.
+ for (int i = 0; i < numItems; i++) {
+ item = getNextItem(controller, forHardware, onlyCurrentIme, mode, forward, item);
+ assertNotNull("Next item shouldn't be null.", item);
+ nextItems.add(item);
+ }
+
+ assertEquals("Rotation order doesn't match.", expectedNextItems, nextItems);
+ }
+
+ /**
+ * Verifies that the controller gets the expected next value from the given item.
+ *
+ * @param controller the controller to sue for finding the next value.
+ * @param forHardware whether to find the next hardware item, or software item.
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param mode the switching mode.
+ * @param forward whether to search forwards or backwards in the list.
+ * @param item the item to find the next value from.
+ * @param expectedNext the expected next value.
+ */
+ private static void assertNextItem(@NonNull ControllerImpl controller,
+ boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward,
+ @NonNull ImeSubtypeListItem item, @Nullable ImeSubtypeListItem expectedNext) {
+ final var nextItem = getNextItem(controller, forHardware, onlyCurrentIme, mode, forward,
+ item);
+ assertEquals("Next item doesn't match.", expectedNext, nextItem);
+ }
+
+ /**
+ * Gets the next value from the given item.
+ *
+ * @param controller the controller to use for finding the next value.
+ * @param forHardware whether to find the next hardware item, or software item.
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param mode the switching mode.
+ * @param forward whether to search forwards or backwards in the list.
+ * @param item the item to find the next value from.
+ * @return the next item found, otherwise {@code null}.
+ */
+ @Nullable
+ private static ImeSubtypeListItem getNextItem(@NonNull ControllerImpl controller,
+ boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward,
+ @NonNull ImeSubtypeListItem item) {
+ final var subtype = item.mSubtypeName != null
+ ? createTestSubtype(item.mSubtypeName.toString()) : null;
+ return forHardware
+ ? controller.getNextInputMethodForHardware(
+ onlyCurrentIme, item.mImi, subtype, mode, forward)
+ : controller.getNextInputMethod(
+ onlyCurrentIme, item.mImi, subtype, mode, forward);
+ }
+
+ /**
+ * Verifies that no next items can be found, and the recency cannot be updated for the
+ * given items.
+ *
+ * @param controller the controller to verify the items on.
+ * @param forHardware whether to try finding the next hardware item, or software item.
+ * @param items the list of items to verify.
+ */
+ private void assertNoAction(@NonNull ControllerImpl controller, boolean forHardware,
+ @NonNull List<ImeSubtypeListItem> items) {
+ for (var item : items) {
+ for (int mode = MODE_STATIC; mode <= MODE_AUTO; mode++) {
+ assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode,
+ false /* forward */, item, null /* expectedNext */);
+ assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode,
+ true /* forward */, item, null /* expectedNext */);
+ assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode,
+ false /* forward */, item, null /* expectedNext */);
+ assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode,
+ true /* forward */, item, null /* expectedNext */);
+ }
+
+ assertFalse("User action shouldn't have updated the recency.",
+ onUserAction(controller, item));
+ }
+ }
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index 81fb1a0..d59f28b 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -98,8 +98,8 @@
assertThat(allUserData.get(0).mBindingController.getUserId()).isEqualTo(ANY_USER_ID);
}
- private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) {
- final var collected = new ArrayList<UserDataRepository.UserData>();
+ private List<UserData> collectUserData(UserDataRepository repository) {
+ final var collected = new ArrayList<UserData>();
repository.forAllUserData(userData -> collected.add(userData));
return collected;
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 7138306..dec4634 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1046,6 +1046,77 @@
}
@Test
+ public void testWriteReadBaseRevisionCode() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+ packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+ .setUid(packageSetting.getAppId())
+ .hideAsFinal());
+
+ final int revisionCode = 311;
+ packageSetting.setBaseRevisionCode(revisionCode);
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ assertThat(settings.getPackageLPr(PACKAGE_NAME_1).getBaseRevisionCode(), is(revisionCode));
+ }
+
+ @Test
+ public void testHasPkg_writeReadSplitVersions() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+ packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+ .setUid(packageSetting.getAppId())
+ .hideAsFinal());
+
+ final String splitOne = "one";
+ final String splitTwo = "two";
+ final int revisionOne = 311;
+ final int revisionTwo = 330;
+ packageSetting.setSplitNames(new String[] { splitOne, splitTwo});
+ packageSetting.setSplitRevisionCodes(new int[] { revisionOne, revisionTwo});
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+ assertThat(resultSetting.getSplitNames().length, is(0));
+ assertThat(resultSetting.getSplitRevisionCodes().length, is(0));
+ }
+
+ @Test
+ public void testNoPkg_writeReadSplitVersions() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+ final String splitOne = "one";
+ final String splitTwo = "two";
+ final int revisionOne = 311;
+ final int revisionTwo = 330;
+ packageSetting.setSplitNames(new String[] { splitOne, splitTwo});
+ packageSetting.setSplitRevisionCodes(new int[] { revisionOne, revisionTwo});
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+ assertThat(resultSetting.getSplitNames()[0], is(splitOne));
+ assertThat(resultSetting.getSplitNames()[1], is(splitTwo));
+ assertThat(resultSetting.getSplitRevisionCodes()[0], is(revisionOne));
+ assertThat(resultSetting.getSplitRevisionCodes()[1], is(revisionTwo));
+ }
+
+ @Test
public void testWriteReadArchiveState() {
Settings settings = makeSettings();
PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 3437923..d450683 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -965,6 +965,51 @@
assertThat(supportedModeData.vsyncRate).isEqualTo(240);
}
+ @Test
+ public void testDozeBrightness_Ddc() throws IOException {
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ assertArrayEquals(new float[]{ -1, 0.1f, 0.2f, 0.3f, 0.4f },
+ mDisplayDeviceConfig.getDozeBrightnessSensorValueToBrightness(), SMALL_DELTA);
+ assertEquals(0.25f, mDisplayDeviceConfig.getDefaultDozeBrightness(), SMALL_DELTA);
+ }
+
+ @Test
+ public void testDefaultDozeBrightness_FallBackToConfigXmlFloat() throws IOException {
+ setupDisplayDeviceConfigFromConfigResourceFile();
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ when(mResources.getFloat(com.android.internal.R.dimen.config_screenBrightnessDozeFloat))
+ .thenReturn(0.31f);
+ when(mResources.getInteger(com.android.internal.R.integer.config_screenBrightnessDoze))
+ .thenReturn(90);
+
+ // Empty display config file
+ setupDisplayDeviceConfigFromDisplayConfigFile(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<displayConfiguration />\n");
+
+ assertEquals(0.31f, mDisplayDeviceConfig.getDefaultDozeBrightness(), ZERO_DELTA);
+ }
+
+ @Test
+ public void testDefaultDozeBrightness_FallBackToConfigXmlInt() throws IOException {
+ setupDisplayDeviceConfigFromConfigResourceFile();
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ when(mResources.getFloat(com.android.internal.R.dimen.config_screenBrightnessDozeFloat))
+ .thenReturn(DisplayDeviceConfig.INVALID_BRIGHTNESS_IN_CONFIG);
+ when(mResources.getInteger(com.android.internal.R.integer.config_screenBrightnessDoze))
+ .thenReturn(90);
+
+ // Empty display config file
+ setupDisplayDeviceConfigFromDisplayConfigFile(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<displayConfiguration />\n");
+
+ assertEquals(brightnessIntToFloat(90),
+ mDisplayDeviceConfig.getDefaultDozeBrightness(), ZERO_DELTA);
+ }
+
private String getValidLuxThrottling() {
return "<luxThrottling>\n"
+ " <brightnessLimitMap>\n"
@@ -1708,6 +1753,16 @@
+ "</point>"
+ "</luxThresholds>"
+ "</idleScreenRefreshRateTimeout>"
+ + "<dozeBrightnessSensorValueToBrightness>\n"
+ + "<item>-1</item>\n"
+ + "<item>0.1</item>\n"
+ + "<item>0.2</item>\n"
+ + "<item>0.3</item>\n"
+ + "<item>0.4</item>\n"
+ + "</dozeBrightnessSensorValueToBrightness>\n"
+ + "<defaultDozeBrightness>"
+ + "0.25"
+ + "</defaultDozeBrightness>\n"
+ "</displayConfiguration>\n";
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 5c29156..624c897 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -117,6 +117,7 @@
private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private static final float PROX_SENSOR_MAX_RANGE = 5;
private static final float DOZE_SCALE_FACTOR = 0.34f;
+ private static final float DEFAULT_DOZE_BRIGHTNESS = 0.121f;
private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
@@ -2051,9 +2052,6 @@
@Test
public void testDefaultDozeBrightness() {
- float brightness = 0.121f;
- when(mPowerManagerMock.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness);
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -2069,15 +2067,25 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.animator).animateTo(eq(brightness), /* linearSecondTarget= */ anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ verify(mHolder.animator).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS),
+ /* linearSecondTarget= */ anyFloat(), eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE),
+ eq(false));
+
+ // The display device changes and the default doze brightness changes
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mHolder.config, /* isEnabled= */ true);
+ when(mHolder.config.getDefaultDozeBrightness()).thenReturn(DEFAULT_DOZE_BRIGHTNESS / 2);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS / 2),
+ /* linearSecondTarget= */ anyFloat(), eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE),
+ eq(false));
}
@Test
public void testDefaultDozeBrightness_ShouldNotBeUsedIfAutoBrightnessAllowedInDoze() {
- float brightness = 0.121f;
- when(mPowerManagerMock.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness);
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -2093,7 +2101,7 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.animator, never()).animateTo(eq(brightness),
+ verify(mHolder.animator, never()).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS),
/* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
/* ignoreAnimationLimits= */ anyBoolean());
}
@@ -2151,6 +2159,8 @@
new SensorData(Sensor.STRING_TYPE_LIGHT, null));
when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
.thenReturn(new int[0]);
+ when(displayDeviceConfigMock.getDefaultDozeBrightness())
+ .thenReturn(DEFAULT_DOZE_BRIGHTNESS);
when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
.thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 4149e44..5b2c0c6 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -53,6 +53,7 @@
"mockingservicestests-utils-mockito",
"mockito-target-extended-minus-junit4",
"platform-compat-test-rules",
+ "platform-parametric-runner-lib",
"platform-test-annotations",
"PlatformProperties",
"service-blobstore",
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 00daf41..1a398c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.trust;
+import static android.security.Flags.FLAG_SHOULD_TRUST_MANAGER_LISTEN_FOR_PRIMARY_AUTH;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -73,6 +75,8 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.security.KeyStoreAuthorization;
import android.service.trust.GrantTrustResult;
@@ -91,6 +95,7 @@
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockSettingsStateListener;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -101,6 +106,7 @@
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
@@ -112,7 +118,16 @@
import java.util.List;
import java.util.Map;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+@RunWith(ParameterizedAndroidJunit4.class)
public class TrustManagerServiceTest {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_SHOULD_TRUST_MANAGER_LISTEN_FOR_PRIMARY_AUTH);
+ }
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
@@ -122,6 +137,9 @@
.build();
@Rule
+ public final SetFlagsRule mSetFlagsRule;
+
+ @Rule
public final MockContext mMockContext = new MockContext(
ApplicationProvider.getApplicationContext());
@@ -162,6 +180,10 @@
private ITrustManager mTrustManager;
private ActivityManagerInternal mPreviousActivityManagerInternal;
+ public TrustManagerServiceTest(FlagsParameterization flags) {
+ mSetFlagsRule = new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT, flags);
+ }
+
@Before
public void setUp() throws Exception {
when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
@@ -594,11 +616,27 @@
}
private void attemptSuccessfulUnlock(int userId) throws RemoteException {
- mTrustManager.reportUnlockAttempt(/* successful= */ true, userId);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ ArgumentCaptor<LockSettingsStateListener> captor =
+ ArgumentCaptor.forClass(LockSettingsStateListener.class);
+ verify(mLockSettingsInternal).registerLockSettingsStateListener(captor.capture());
+ LockSettingsStateListener listener = captor.getValue();
+ listener.onAuthenticationSucceeded(userId);
+ } else {
+ mTrustManager.reportUnlockAttempt(/* successful= */ true, userId);
+ }
}
private void attemptFailedUnlock(int userId) throws RemoteException {
- mTrustManager.reportUnlockAttempt(/* successful= */ false, userId);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ ArgumentCaptor<LockSettingsStateListener> captor =
+ ArgumentCaptor.forClass(LockSettingsStateListener.class);
+ verify(mLockSettingsInternal).registerLockSettingsStateListener(captor.capture());
+ LockSettingsStateListener listener = captor.getValue();
+ listener.onAuthenticationFailed(userId);
+ } else {
+ mTrustManager.reportUnlockAttempt(/* successful= */ false, userId);
+ }
}
private void grantRenewableTrust(ITrustAgentServiceCallback callback) throws RemoteException {
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 753db12..b9e99dd 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -36,6 +36,8 @@
"-Werror",
],
static_libs: [
+ "a11ychecker-protos-java-proto-lite",
+ "aatf",
"cts-input-lib",
"frameworks-base-testutils",
"services.accessibility",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
new file mode 100644
index 0000000..90d4275
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.a11ychecker;
+
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_VERSION_CODE;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_VERSION_CODE;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_WINDOW_TITLE;
+import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClassNameCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClickableSpanCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityCheckerUtilsTest {
+
+ PackageManager mMockPackageManager;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ mMockPackageManager = getMockPackageManagerWithInstalledApps();
+ }
+
+ @Test
+ public void processResults_happyPath_setsAllFields() {
+ AccessibilityNodeInfo mockNodeInfo =
+ new MockAccessibilityNodeInfoBuilder()
+ .setViewIdResourceName("TargetNode")
+ .build();
+ AccessibilityHierarchyCheckResult result1 =
+ new AccessibilityHierarchyCheckResult(
+ SpeakableTextPresentCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.WARNING, null, 1,
+ null);
+ AccessibilityHierarchyCheckResult result2 =
+ new AccessibilityHierarchyCheckResult(
+ TouchTargetSizeCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.ERROR, null, 2, null);
+ AccessibilityHierarchyCheckResult result3 =
+ new AccessibilityHierarchyCheckResult(
+ ClassNameCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.INFO, null, 5, null);
+ AccessibilityHierarchyCheckResult result4 =
+ new AccessibilityHierarchyCheckResult(
+ ClickableSpanCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.NOT_RUN, null, 5,
+ null);
+
+ Set<A11yCheckerProto.AccessibilityCheckResultReported> atoms =
+ AccessibilityCheckerUtils.processResults(
+ mockNodeInfo,
+ List.of(result1, result2, result3, result4),
+ null,
+ mMockPackageManager,
+ new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+ TEST_A11Y_SERVICE_CLASS_NAME));
+
+ assertThat(atoms).containsExactly(
+ createAtom(A11yCheckerProto.AccessibilityCheckClass.SPEAKABLE_TEXT_PRESENT_CHECK,
+ A11yCheckerProto.AccessibilityCheckResultType.WARNING, 1),
+ createAtom(A11yCheckerProto.AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK,
+ A11yCheckerProto.AccessibilityCheckResultType.ERROR, 2)
+ );
+ }
+
+ @Test
+ public void processResults_packageNameNotFound_returnsEmptySet()
+ throws PackageManager.NameNotFoundException {
+ when(mMockPackageManager.getPackageInfo("com.uninstalled.app", 0))
+ .thenThrow(PackageManager.NameNotFoundException.class);
+ AccessibilityNodeInfo mockNodeInfo =
+ new MockAccessibilityNodeInfoBuilder()
+ .setPackageName("com.uninstalled.app")
+ .setViewIdResourceName("TargetNode")
+ .build();
+ AccessibilityHierarchyCheckResult result1 =
+ new AccessibilityHierarchyCheckResult(
+ TouchTargetSizeCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.WARNING, null, 1,
+ null);
+ AccessibilityHierarchyCheckResult result2 =
+ new AccessibilityHierarchyCheckResult(
+ TouchTargetSizeCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.ERROR, null, 2, null);
+
+ Set<A11yCheckerProto.AccessibilityCheckResultReported> atoms =
+ AccessibilityCheckerUtils.processResults(
+ mockNodeInfo,
+ List.of(result1, result2),
+ null,
+ mMockPackageManager,
+ new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+ TEST_A11Y_SERVICE_CLASS_NAME));
+
+ assertThat(atoms).isEmpty();
+ }
+
+ @Test
+ public void getActivityName_hasWindowStateChangedEvent_returnsActivityName() {
+ AccessibilityEvent accessibilityEvent =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ accessibilityEvent.setPackageName(TEST_APP_PACKAGE_NAME);
+ accessibilityEvent.setClassName(TEST_ACTIVITY_NAME);
+
+ assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager,
+ accessibilityEvent)).isEqualTo("MainActivity");
+ }
+
+ // Makes sure the AccessibilityHierarchyCheck class to enum mapping is up to date with the
+ // latest prod preset.
+ @Test
+ public void checkClassToEnumMap_hasAllLatestPreset() {
+ ImmutableSet<AccessibilityHierarchyCheck> checkPreset =
+ AccessibilityCheckPreset.getAccessibilityHierarchyChecksForPreset(
+ AccessibilityCheckPreset.LATEST);
+ Set<Class<? extends AccessibilityHierarchyCheck>> latestCheckClasses =
+ checkPreset.stream().map(AccessibilityHierarchyCheck::getClass).collect(
+ Collectors.toUnmodifiableSet());
+
+ assertThat(AccessibilityCheckerUtils.CHECK_CLASS_TO_ENUM_MAP.keySet())
+ .containsExactlyElementsIn(latestCheckClasses);
+ }
+
+
+ private static A11yCheckerProto.AccessibilityCheckResultReported createAtom(
+ A11yCheckerProto.AccessibilityCheckClass checkClass,
+ A11yCheckerProto.AccessibilityCheckResultType resultType,
+ int resultId) {
+ return A11yCheckerProto.AccessibilityCheckResultReported.newBuilder()
+ .setPackageName(TEST_APP_PACKAGE_NAME)
+ .setAppVersionCode(TEST_APP_VERSION_CODE)
+ .setUiElementPath(TEST_APP_PACKAGE_NAME + ":TargetNode")
+ .setWindowTitle(TEST_WINDOW_TITLE)
+ .setActivityName("")
+ .setSourceComponentName(new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+ TEST_A11Y_SERVICE_CLASS_NAME).flattenToString())
+ .setSourceVersionCode(TEST_A11Y_SERVICE_SOURCE_VERSION_CODE)
+ .setResultCheckClass(checkClass)
+ .setResultType(resultType)
+ .setResultId(resultId)
+ .build();
+ }
+
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
similarity index 80%
rename from core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
rename to services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
index 438277b..a53f42e 100644
--- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package android.view.accessibility.a11ychecker;
+package com.android.server.accessibility.a11ychecker;
-import static android.view.accessibility.a11ychecker.MockAccessibilityNodeInfoBuilder.PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
@@ -36,7 +36,7 @@
@RunWith(AndroidJUnit4.class)
public class AccessibilityNodePathBuilderTest {
- public static final String RESOURCE_ID_PREFIX = PACKAGE_NAME + ":id/";
+ public static final String RESOURCE_ID_PREFIX = TEST_APP_PACKAGE_NAME + ":id/";
@Test
public void createNodePath_pathWithResourceNames() {
@@ -55,11 +55,11 @@
.build();
assertThat(AccessibilityNodePathBuilder.createNodePath(child))
- .isEqualTo(PACKAGE_NAME + ":root_node/parent_node[1]/child_node[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node/parent_node[1]/child_node[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
- .isEqualTo(PACKAGE_NAME + ":root_node/parent_node[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node/parent_node[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(root))
- .isEqualTo(PACKAGE_NAME + ":root_node");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node");
}
@Test
@@ -81,11 +81,11 @@
.build();
assertThat(AccessibilityNodePathBuilder.createNodePath(child))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout/RecyclerView[1]/TextView[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/RecyclerView[1]/TextView[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout/RecyclerView[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/RecyclerView[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(root))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout");
}
@Test
@@ -105,11 +105,11 @@
.build();
assertThat(AccessibilityNodePathBuilder.createNodePath(child1))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout/child1[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/child1[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(child2))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout/TextView[2]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/TextView[2]");
assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout");
}
@Test
@@ -133,13 +133,13 @@
.build();
assertThat(AccessibilityNodePathBuilder.createNodePath(child1))
- .isEqualTo(PACKAGE_NAME + ":parentId/childId[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/childId[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(child2))
- .isEqualTo(PACKAGE_NAME + ":parentId/child/Id/With/Slash[2]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/child/Id/With/Slash[2]");
assertThat(AccessibilityNodePathBuilder.createNodePath(child3))
- .isEqualTo(PACKAGE_NAME + ":parentId/childIdWithoutPrefix[3]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/childIdWithoutPrefix[3]");
assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
- .isEqualTo(PACKAGE_NAME + ":parentId");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId");
}
}
diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
similarity index 72%
rename from core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
rename to services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
index e363f0c..7cd3535 100644
--- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
@@ -14,21 +14,33 @@
* limitations under the License.
*/
-package android.view.accessibility.a11ychecker;
+package com.android.server.accessibility.a11ychecker;
+
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_WINDOW_TITLE;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
import java.util.List;
final class MockAccessibilityNodeInfoBuilder {
- static final String PACKAGE_NAME = "com.example.app";
private final AccessibilityNodeInfo mMockNodeInfo = mock(AccessibilityNodeInfo.class);
MockAccessibilityNodeInfoBuilder() {
- when(mMockNodeInfo.getPackageName()).thenReturn(PACKAGE_NAME);
+ setPackageName(TEST_APP_PACKAGE_NAME);
+
+ AccessibilityWindowInfo windowInfo = new AccessibilityWindowInfo();
+ windowInfo.setTitle(TEST_WINDOW_TITLE);
+ when(mMockNodeInfo.getWindow()).thenReturn(windowInfo);
+ }
+
+ MockAccessibilityNodeInfoBuilder setPackageName(String packageName) {
+ when(mMockNodeInfo.getPackageName()).thenReturn(packageName);
+ return this;
}
MockAccessibilityNodeInfoBuilder setClassName(String className) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS
new file mode 100644
index 0000000..7bdc029
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS
@@ -0,0 +1 @@
+include /services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
new file mode 100644
index 0000000..a04bbee
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.a11ychecker;
+
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import org.mockito.Mockito;
+
+public class TestUtils {
+ static final String TEST_APP_PACKAGE_NAME = "com.example.app";
+ static final int TEST_APP_VERSION_CODE = 12321;
+ static final String TEST_ACTIVITY_NAME = "com.example.app.MainActivity";
+ static final String TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME = "com.assistive.app";
+ static final String TEST_A11Y_SERVICE_CLASS_NAME = "MyA11yService";
+ static final int TEST_A11Y_SERVICE_SOURCE_VERSION_CODE = 333555;
+ static final String TEST_WINDOW_TITLE = "Example window";
+
+ static PackageManager getMockPackageManagerWithInstalledApps()
+ throws PackageManager.NameNotFoundException {
+ PackageManager mockPackageManager = Mockito.mock(PackageManager.class);
+ ActivityInfo testActivityInfo = getTestActivityInfo();
+ ComponentName testActivityComponentName = new ComponentName(TEST_APP_PACKAGE_NAME,
+ TEST_ACTIVITY_NAME);
+
+ when(mockPackageManager.getActivityInfo(testActivityComponentName, 0))
+ .thenReturn(testActivityInfo);
+ when(mockPackageManager.getPackageInfo(TEST_APP_PACKAGE_NAME, 0))
+ .thenReturn(createPackageInfo(TEST_APP_PACKAGE_NAME, TEST_APP_VERSION_CODE,
+ testActivityInfo));
+ when(mockPackageManager.getPackageInfo(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, 0))
+ .thenReturn(createPackageInfo(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+ TEST_A11Y_SERVICE_SOURCE_VERSION_CODE, null));
+ return mockPackageManager;
+ }
+
+ static ActivityInfo getTestActivityInfo() {
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = TEST_APP_PACKAGE_NAME;
+ activityInfo.name = TEST_ACTIVITY_NAME;
+ return activityInfo;
+ }
+
+ static PackageInfo createPackageInfo(String packageName, int versionCode,
+ @Nullable ActivityInfo activityInfo) {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ packageInfo.setLongVersionCode(versionCode);
+ if (activityInfo != null) {
+ packageInfo.activities = new ActivityInfo[]{activityInfo};
+ }
+ return packageInfo;
+
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
index 8863d27..41cb6fd 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
@@ -18,11 +18,15 @@
import static com.google.common.truth.Truth.assertThat;
+import android.chre.flags.Flags;
import android.hardware.location.NanoAppMessage;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,6 +37,8 @@
public class ContextHubEventLoggerTest {
private static final ContextHubEventLogger sInstance = ContextHubEventLogger.getInstance();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testLogNanoappLoad() {
ContextHubEventLogger.NanoappLoadEvent[] events =
@@ -46,10 +52,10 @@
sInstance.clear();
sInstance.logNanoappLoad(-1, 42, -34, 100, false);
sInstance.logNanoappLoad(0, 123, 321, 001, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -66,10 +72,10 @@
sInstance.clear();
sInstance.logNanoappUnload(-1, 47, false);
sInstance.logNanoappUnload(1, 0xFFFFFFFF, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -90,10 +96,10 @@
sInstance.clear();
sInstance.logMessageFromNanoapp(-123, message1, false);
sInstance.logMessageFromNanoapp(321, message2, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -114,10 +120,54 @@
sInstance.clear();
sInstance.logMessageToNanoapp(888, message1, true);
sInstance.logMessageToNanoapp(999, message2, false);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
+ }
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_RELIABLE_MESSAGE,
+ Flags.FLAG_RELIABLE_MESSAGE_IMPLEMENTATION})
+ public void testLogReliableMessageToNanoappStatus() {
+ NanoAppMessage message1 = NanoAppMessage.createMessageToNanoApp(1, 0,
+ new byte[] {0x00, 0x11, 0x22, 0x33});
+ NanoAppMessage message2 = NanoAppMessage.createMessageToNanoApp(0, 1,
+ new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF});
+ message1.setIsReliable(true);
+ message2.setIsReliable(true);
+ message1.setMessageSequenceNumber(0);
+ message2.setMessageSequenceNumber(1);
+
+ ContextHubEventLogger.NanoappMessageEvent[] events =
+ new ContextHubEventLogger.NanoappMessageEvent[] {
+ new ContextHubEventLogger.NanoappMessageEvent(23, 888, message1, true),
+ new ContextHubEventLogger.NanoappMessageEvent(34, 999, message2, false)
+ };
+ String[] eventStrings = generateEventDumpStrings(events);
+
+ // log events and test sInstance.toString() contains event details
+ sInstance.clear();
+ sInstance.logMessageToNanoapp(888, message1, true);
+ sInstance.logMessageToNanoapp(999, message2, false);
+ String instanceDump = sInstance.toString();
+ for (String eventString: eventStrings) {
+ assertThat(eventString.length() > 0).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
+ }
+
+ // set the error codes for the events and verify
+ sInstance.logReliableMessageToNanoappStatus(0, (byte) 0x02);
+ sInstance.logReliableMessageToNanoappStatus(1, (byte) 0x03);
+ events[0].setErrorCode((byte) 0x02);
+ events[1].setErrorCode((byte) 0x03);
+ eventStrings = generateEventDumpStrings(events);
+
+ instanceDump = sInstance.toString();
+ for (String eventString: eventStrings) {
+ assertThat(eventString.length() > 0).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -134,10 +184,10 @@
sInstance.clear();
sInstance.logContextHubRestart(1);
sInstance.logContextHubRestart(2);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 633a3c9..901c036 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -54,6 +54,7 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.AtomicFile;
import android.util.SparseArray;
+import android.view.HapticFeedbackConstants;
import androidx.test.InstrumentationRegistry;
@@ -292,7 +293,7 @@
for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, /* fromIme= */ false);
+ effectId, /* flags */ 0, /* privFlags */ 0);
assertThat(attrs.getUsage()).isEqualTo(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
}
}
@@ -302,8 +303,7 @@
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false,
- false /* fromIme*/);
+ SAFE_MODE_ENABLED, /* flags */ 0, /* privFlags */ 0);
assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isFalse();
}
@@ -313,7 +313,8 @@
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true, false /* fromIme*/);
+ SAFE_MODE_ENABLED,
+ /* flags */ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, /* privFlags */ 0);
assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isTrue();
}
@@ -325,7 +326,7 @@
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+ effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isTrue();
}
@@ -338,7 +339,7 @@
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+ effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected no FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isFalse();
}
@@ -351,7 +352,8 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+ effectId, /* flags */ 0,
+ HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
.that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId)
@@ -366,7 +368,7 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+ effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
.that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
@@ -381,7 +383,8 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+ effectId, /* flags */ 0,
+ HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
.that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
@@ -398,7 +401,8 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+ effectId, /* flags */ 0,
+ HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+ effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
@@ -414,7 +418,7 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+ effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+ effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
@@ -430,7 +434,8 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+ effectId, /* flags */ 0,
+ HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
assertWithMessage("Expected FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+ effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isTrue();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 1875284..ef944db 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2661,9 +2661,10 @@
private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service,
int constant, boolean always) throws InterruptedException {
- HalVibration vib =
- service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME,
- constant, always, "some reason", service, false /* fromIme */);
+ HalVibration vib = service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT,
+ PACKAGE_NAME, constant, "some reason", service,
+ always ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0 /* flags */,
+ 0 /* privFlags */);
if (vib != null) {
vib.waitForEnd();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index 564c29f..11e6d90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -23,7 +23,6 @@
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -176,6 +175,7 @@
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity);
mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
@@ -185,34 +185,6 @@
}
@Test
- public void testReconnectedToDifferentCamera_activatesCameraCompatModeAndRefresh()
- throws Exception {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1);
- callOnActivityConfigurationChanging(mActivity);
-
- assertInCameraCompatMode();
- assertActivityRefreshRequested(/* refreshRequested */ true);
- }
-
- @Test
- public void testCameraDisconnected_deactivatesCameraCompatMode() {
- configureActivityAndDisplay(SCREEN_ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE,
- WINDOWING_MODE_FREEFORM);
- // Open camera and test for compat treatment
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertInCameraCompatMode();
-
- // Close camera and test for revert
- mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
-
- assertNotInCameraCompatMode();
- }
-
- @Test
public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
index e468fd8..1c8dc05 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
@@ -256,8 +256,7 @@
}
@Override
- public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
+ public boolean onCameraClosed(@NonNull String cameraId) {
mOnCameraClosedCounter++;
boolean returnValue = mOnCameraClosedReturnValue;
// If false, return false only the first time, so it doesn't fall in the infinite retry
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index a4bec64..ad3fba3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -166,9 +166,9 @@
ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
final int desiredWidth =
- (int) (DISPLAY_STABLE_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ (int) (DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
final int desiredHeight =
- (int) (DISPLAY_STABLE_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ (int) (DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
assertEquals(desiredWidth, mResult.mBounds.width());
assertEquals(desiredHeight, mResult.mBounds.height());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index c1be5ca..63e3e5c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -714,7 +714,7 @@
// Simulate when the window is exiting and cleanupAnimation invoked
// (e.g. screen off during RecentsAnimation animating), will expect the window receives
// onExitAnimationDone to destroy the surface when the removal is allowed.
- win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
+ win1.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
win1.mHasSurface = true;
win1.mAnimatingExit = true;
win1.mRemoveOnExit = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 11d9629..0bf27d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -43,8 +43,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -758,12 +756,9 @@
// Simulating win1 has shown IME and being IME layering/input target
mDisplayContent.setImeLayeringTarget(win1);
mDisplayContent.setImeInputTarget(win1);
- mImeWindow.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test");
spyOn(mDisplayContent);
- doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController).hasSurface();
- doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController)
- .prepareToShowInTransaction(any(), anyFloat());
+ mImeWindow.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
makeWindowVisibleAndDrawn(mImeWindow);
assertTrue(mImeWindow.isOnScreen());
assertFalse(mImeWindow.isParentWindowHidden());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 38ad9a7..4b0668f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -270,8 +270,8 @@
}
@Override
- public boolean performHapticFeedback(int uid, String packageName, int effectId,
- boolean always, String reason, boolean fromIme) {
+ public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
+ int flags, int privFlags) {
return false;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 95d37eb..7d01b79 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -35,6 +35,7 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_CONFIG_AT_END;
+import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
@@ -77,13 +78,14 @@
import android.app.ActivityManager;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.SurfaceControl;
@@ -95,6 +97,7 @@
import android.window.ITransitionPlayer;
import android.window.RemoteTransition;
import android.window.SystemPerformanceHinter;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentOrganizer;
import android.window.TransitionInfo;
@@ -104,7 +107,6 @@
import com.android.internal.graphics.ColorUtils;
import com.android.window.flags.Flags;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -120,16 +122,15 @@
* Build/Install/Run:
* atest WmTests:TransitionTests
*/
-@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class TransitionTests extends WindowTestsBase {
final SurfaceControl.Transaction mMockT = mock(SurfaceControl.Transaction.class);
private BLASTSyncEngine mSyncEngine;
+ private Transition mTransition;
+ private TransitionInfo mInfo;
- @Rule
- public SetFlagsRule mRule = new SetFlagsRule();
private Transition createTestTransition(int transitType, TransitionController controller) {
final Transition transition = new Transition(transitType, 0 /* flags */, controller,
controller.mSyncEngine);
@@ -1994,6 +1995,221 @@
assertEquals(expectedBackgroundColor, info.getChanges().get(1).getBackgroundColor());
}
+ @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_disableAnimOptionsPerChange() {
+ initializeOverrideAnimationOptionsTest();
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCommonAnimOptions("testPackage");
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ assertEquals(options, mInfo.getAnimationOptions());
+ }
+
+ @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_nonCustomAnimOptions() {
+ initializeOverrideAnimationOptionsTest();
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCommonAnimOptions("testPackage");
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+ final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+ final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+ final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+ assertNull("Display change's AnimationOptions must not be overridden.",
+ displayChange.getAnimationOptions());
+ assertNull("Task change's AnimationOptions must not be overridden.",
+ taskChange.getAnimationOptions());
+ assertNull("Embedded TF change's AnimationOptions must not be overridden.",
+ embeddedTfChange.getAnimationOptions());
+ assertEquals("Activity change's AnimationOptions must be overridden.",
+ options, activityChange.getAnimationOptions());
+ }
+
+ @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_crossProfileAnimOptions() {
+ initializeOverrideAnimationOptionsTest();
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCrossProfileAnimOptions();
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+ final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+ final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+ final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+ activityChange.setMode(TRANSIT_OPEN);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ assertNull("Display change's AnimationOptions must not be overridden.",
+ displayChange.getAnimationOptions());
+ assertNull("Task change's AnimationOptions must not be overridden.",
+ taskChange.getAnimationOptions());
+ assertNull("Embedded TF change's AnimationOptions must not be overridden.",
+ embeddedTfChange.getAnimationOptions());
+ assertEquals("Activity change's AnimationOptions must be overridden.",
+ options, activityChange.getAnimationOptions());
+ assertTrue(activityChange.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL));
+ }
+
+ @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptions() {
+ initializeOverrideAnimationOptionsTest();
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ Color.GREEN, false /* overrideTaskTransition */);
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+ final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+ final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+ final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+ assertNull("Display change's AnimationOptions must not be overridden.",
+ displayChange.getAnimationOptions());
+ assertNull("Task change's AnimationOptions must not be overridden.",
+ taskChange.getAnimationOptions());
+ assertEquals("Embedded TF change's AnimationOptions must be overridden.",
+ options, embeddedTfChange.getAnimationOptions());
+ assertEquals("Embedded TF change's background color must not be overridden.",
+ 0, embeddedTfChange.getBackgroundColor());
+ assertEquals("Activity change's AnimationOptions must be overridden.",
+ options, activityChange.getAnimationOptions());
+ assertEquals("Activity change's background color must be overridden.",
+ options.getBackgroundColor(), activityChange.getBackgroundColor());
+ }
+
+ @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_haveTaskFragmentAnimParams() {
+ initializeOverrideAnimationOptionsTest();
+
+ final TaskFragment embeddedTf = mTransition.mTargets.get(2).mContainer.asTaskFragment();
+ embeddedTf.setAnimationParams(new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(Color.RED)
+ .setOpenAnimationResId(0x12345678)
+ .build());
+
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ Color.GREEN, false /* overrideTaskTransition */);
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+ final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+ final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+ final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+ final int expectedColor = embeddedTf.getAnimationParams().getAnimationBackgroundColor();
+ embeddedTfChange.setBackgroundColor(expectedColor);
+ final TransitionInfo.AnimationOptions expectedOptions = TransitionInfo.AnimationOptions
+ .makeCustomAnimOptions("testPackage", 0x12345678,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ 0, false /* overrideTaskTransition */);
+ embeddedTfChange.setAnimationOptions(expectedOptions);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ assertNull("Display change's AnimationOptions must not be overridden.",
+ displayChange.getAnimationOptions());
+ assertNull("Task change's AnimationOptions must not be overridden.",
+ taskChange.getAnimationOptions());
+ assertEquals("Embedded TF change's AnimationOptions must be overridden.",
+ expectedOptions, embeddedTfChange.getAnimationOptions());
+ assertEquals("Embedded TF change's background color must not be overridden.",
+ expectedColor, embeddedTfChange.getBackgroundColor());
+ assertEquals("Activity change's AnimationOptions must be overridden.",
+ options, activityChange.getAnimationOptions());
+ assertEquals("Activity change's background color must be overridden.",
+ options.getBackgroundColor(), activityChange.getBackgroundColor());
+ }
+
+ @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptionsWithTaskOverride() {
+ initializeOverrideAnimationOptionsTest();
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ Color.GREEN, true /* overrideTaskTransition */);
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+ final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+ final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+ final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+ assertNull("Display change's AnimationOptions must not be overridden.",
+ displayChange.getAnimationOptions());
+ assertEquals("Task change's AnimationOptions must be overridden.",
+ options, taskChange.getAnimationOptions());
+ assertEquals("Task change's background color must be overridden.",
+ options.getBackgroundColor(), taskChange.getBackgroundColor());
+ assertEquals("Embedded TF change's AnimationOptions must be overridden.",
+ options, embeddedTfChange.getAnimationOptions());
+ assertEquals("Embedded TF change's background color must be overridden.",
+ 0, embeddedTfChange.getBackgroundColor());
+ assertEquals("Activity change's AnimationOptions must be overridden.",
+ options, activityChange.getAnimationOptions());
+ assertEquals("Activity change's background color must be overridden.",
+ options.getBackgroundColor(), activityChange.getBackgroundColor());
+ }
+
+ private void initializeOverrideAnimationOptionsTest() {
+ mTransition = createTestTransition(TRANSIT_OPEN);
+
+ // Test set AnimationOptions for Activity and Task.
+ final Task task = createTask(mDisplayContent);
+ // Create an embedded TaskFragment.
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ registerTaskFragmentOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
+ mWm.mCurrentUserId = nonEmbeddedActivity.mUserId;
+
+ mTransition.mTargets = new ArrayList<>();
+ mTransition.mTargets.add(new Transition.ChangeInfo(mDisplayContent));
+ mTransition.mTargets.add(new Transition.ChangeInfo(task));
+ mTransition.mTargets.add(new Transition.ChangeInfo(embeddedTf));
+ mTransition.mTargets.add(new Transition.ChangeInfo(nonEmbeddedActivity));
+
+ mInfo = new TransitionInfo(TRANSIT_OPEN, 0 /* flags */);
+ mInfo.addChange(new TransitionInfo.Change(mDisplayContent.mRemoteToken
+ .toWindowContainerToken(), mDisplayContent.getAnimationLeash()));
+ mInfo.addChange(new TransitionInfo.Change(task.mRemoteToken.toWindowContainerToken(),
+ task.getAnimationLeash()));
+ mInfo.addChange(new TransitionInfo.Change(embeddedTf.mRemoteToken.toWindowContainerToken(),
+ embeddedTf.getAnimationLeash()));
+ mInfo.addChange(new TransitionInfo.Change(null /* container */,
+ nonEmbeddedActivity.getAnimationLeash()));
+ }
+
@Test
public void testTransitionVisibleChange() {
registerTestTransitionPlayer();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9b48cb9..c65b76e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -31,7 +31,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.window.flags.Flags.multiCrop;
import static com.google.common.truth.Truth.assertThat;
@@ -551,10 +550,9 @@
}
private static void makeWallpaperWindowShown(WindowState w) {
- final WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class);
- w.mWinAnimator.mSurfaceController = windowSurfaceController;
w.mWinAnimator.mLastAlpha = 1;
- when(windowSurfaceController.getShown()).thenReturn(true);
+ spyOn(w.mWinAnimator);
+ doReturn(true).when(w.mWinAnimator).getShown();
}
private WindowState createWallpaperWindow(DisplayContent dc, int width, int height) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 4958b90..89abe2f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -253,22 +253,18 @@
final Session session = createTestSession(mAtm, wpc.getPid(), wpc.mUid);
spyOn(session);
assertTrue(session.mCanAddInternalSystemWindow);
- final WindowSurfaceController winSurface = mock(WindowSurfaceController.class);
- session.onWindowSurfaceVisibilityChanged(winSurface, true /* visible */,
- LayoutParams.TYPE_PHONE);
+ final WindowState window = createWindow(null, LayoutParams.TYPE_PHONE, "win");
+ session.onWindowSurfaceVisibilityChanged(window, true /* visible */);
verify(session).setHasOverlayUi(true);
- session.onWindowSurfaceVisibilityChanged(winSurface, false /* visible */,
- LayoutParams.TYPE_PHONE);
+ session.onWindowSurfaceVisibilityChanged(window, false /* visible */);
verify(session).setHasOverlayUi(false);
}
@Test
public void testRelayoutExitingWindow() {
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
- final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
- win.mWinAnimator.mSurfaceController = surfaceController;
win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
- doReturn(true).when(surfaceController).hasSurface();
+ win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
spyOn(win.mTransitionController);
doReturn(true).when(win.mTransitionController).isShellTransitionsEnabled();
doReturn(true).when(win.mTransitionController).inTransition(
@@ -306,7 +302,7 @@
// and WMS#tryStartExitingAnimation() will destroy the surface directly.
assertFalse(win.mAnimatingExit);
assertFalse(win.mHasSurface);
- assertNull(win.mWinAnimator.mSurfaceController);
+ assertNull(win.mWinAnimator.mSurfaceControl);
// Invisible requested activity should not get the last config even if its view is visible.
mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index aebae4e..4b83b65 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1078,6 +1078,11 @@
* @hide
*/
public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5;
+ /**
+ * Datagram type indicating that the message to be sent or received is of type SMS.
+ * @hide
+ */
+ public static final int DATAGRAM_TYPE_SMS = 6;
/** @hide */
@IntDef(prefix = "DATAGRAM_TYPE_", value = {
@@ -1086,7 +1091,8 @@
DATAGRAM_TYPE_LOCATION_SHARING,
DATAGRAM_TYPE_KEEP_ALIVE,
DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
- DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED
+ DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED,
+ DATAGRAM_TYPE_SMS
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatagramType {}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 238f2af..5121f667 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -119,7 +119,7 @@
): UiObject2? {
if (
wmHelper.getWindow(innerHelper)?.windowingMode !=
- WindowingMode.WINDOWING_MODE_FREEFORM.value
+ WindowingMode.WINDOWING_MODE_FREEFORM.value
)
error("expected a freeform window with caption but window is not in freeform mode")
val captions =
@@ -147,7 +147,17 @@
val endX = startX + horizontalChange
// drag the specified corner of the window to the end coordinate.
- device.drag(startX, startY, endX, endY, 100)
+ dragWindow(startX, startY, endX, endY, wmHelper, device)
+ }
+
+ /** Drag a window from a source coordinate to a destination coordinate. */
+ fun dragWindow(
+ startX: Int, startY: Int,
+ endX: Int, endY: Int,
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice
+ ) {
+ device.drag(startX, startY, endX, endY, /* steps= */ 100)
wmHelper
.StateSyncBuilder()
.withAppTransitionIdle()
diff --git a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
index d0148fb..abfe549 100644
--- a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
+++ b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
@@ -136,11 +136,20 @@
assumeTrue(enableVectorCursors())
assumeTrue(enableVectorCursorA11ySettings())
+ val theme: Resources.Theme = context.getResources().newTheme()
+ theme.setTo(context.getTheme())
+ theme.applyStyle(
+ PointerIcon.vectorFillStyleToResource(PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BLACK),
+ /* force= */ true)
+ theme.applyStyle(
+ PointerIcon.vectorStrokeStyleToResource(
+ PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_WHITE),
+ /* force= */ true)
val pointerScale = 2f
val pointerIcon =
PointerIcon.getLoadedSystemIcon(
- context,
+ ContextThemeWrapper(context, theme),
PointerIcon.TYPE_ARROW,
/* useLargeIcons= */ false,
pointerScale)
diff --git a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
index 2c9361d..f9e004b 100644
--- a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
+++ b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
@@ -17,6 +17,7 @@
import android.app.trust.TrustManager
import android.content.Context
+import android.security.Flags.shouldTrustManagerListenForPrimaryAuth
import android.trust.BaseTrustAgentService
import android.trust.TrustTestActivity
import android.trust.test.lib.LockStateTrackingRule
@@ -154,13 +155,17 @@
private fun triggerSuccessfulUnlock() {
screenLockRule.successfulScreenLockAttempt()
- trustAgentRule.reportSuccessfulUnlock()
+ if (!shouldTrustManagerListenForPrimaryAuth()) {
+ trustAgentRule.reportSuccessfulUnlock()
+ }
await()
}
private fun triggerFailedUnlock() {
screenLockRule.failedScreenLockAttempt()
- trustAgentRule.reportFailedUnlock()
+ if (!shouldTrustManagerListenForPrimaryAuth()) {
+ trustAgentRule.reportFailedUnlock()
+ }
await()
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index f385179..5f9a710 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -54,6 +54,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -110,6 +111,7 @@
}
@Test
+ @Ignore("b/352823913")
public void passengerShowImeNotAffectDriver() throws Exception {
assertDriverImeHidden();
assertPassengerImeHidden();
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index cbf2c2f..382b088 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -198,6 +198,7 @@
android::DiagMessage(el->line_number)
<< "Cannot find symbol for android:configChanges with min sdk: "
<< context->GetMinSdkVersion());
+ return false;
}
std::stringstream new_value;
diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md
index a5ac2be..18bda92 100644
--- a/tools/lint/fix/README.md
+++ b/tools/lint/fix/README.md
@@ -6,7 +6,7 @@
It's a python script that runs the framework linter,
and then (optionally) copies modified files back into the source tree.\
-Why python, you ask? Because python is cool ¯\_(ツ)_/¯.
+Why python, you ask? Because python is cool ¯\\\_(ツ)\_/¯.
Incidentally, this exposes a much simpler way to run individual lint checks
against individual modules, so it's useful beyond applying fixes.
@@ -15,7 +15,7 @@
Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag.
As a compromise, soong zips up the (potentially) modified sources and leaves them in an intermediate
-directory. This script runs the lint, unpacks those files, and copies them back into the tree.
+directory. This script runs the lint, unpacks those files, and copies them back into the tree.
## How do I run it?
**WARNING: You probably want to commit/stash any changes to your working tree before doing this...**