Merge "Revert "Register Dumpables for Region Sampler"" into tm-qpr-dev
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index b09d2e9..7aacb9b 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -16,6 +16,7 @@
package android.service.quickaccesswallet;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
@@ -24,28 +25,73 @@
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
/**
* A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit
* card, library card, transit pass, etc. Cards are identified by a String identifier and contain a
- * card image, card image content description, and a {@link PendingIntent} to be used if the user
- * clicks on the card. Cards may be displayed with an icon and label, though these are optional.
+ * card type, card image, card image content description, and a {@link PendingIntent} to be used if
+ * the user clicks on the card. Cards may be displayed with an icon and label, though these are
+ * optional. Valuable cards will also have a second image that will be displayed when the card is
+ * tapped.
*/
+
public final class WalletCard implements Parcelable {
+ /**
+ * Unknown cards refer to cards whose types are unspecified.
+ * @hide
+ */
+ public static final int CARD_TYPE_UNKNOWN = 0;
+
+ /**
+ * Payment cards refer to credit cards, debit cards or any other cards in the wallet used to
+ * make cash-equivalent payments.
+ * @hide
+ */
+ public static final int CARD_TYPE_PAYMENT = 1;
+
+ /**
+ * Valuable cards refer to any cards that are not used for cash-equivalent payment.
+ * This includes event tickets, flights, offers, loyalty cards, gift cards and transit tickets.
+ * @hide
+ */
+ public static final int CARD_TYPE_VALUABLE = 2;
+
private final String mCardId;
+ private final int mCardType;
private final Icon mCardImage;
private final CharSequence mContentDescription;
private final PendingIntent mPendingIntent;
private final Icon mCardIcon;
private final CharSequence mCardLabel;
+ private final Icon mValuableCardSecondaryImage;
private WalletCard(Builder builder) {
this.mCardId = builder.mCardId;
+ this.mCardType = builder.mCardType;
this.mCardImage = builder.mCardImage;
this.mContentDescription = builder.mContentDescription;
this.mPendingIntent = builder.mPendingIntent;
this.mCardIcon = builder.mCardIcon;
this.mCardLabel = builder.mCardLabel;
+ this.mValuableCardSecondaryImage = builder.mValuableCardSecondaryImage;
+ }
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CARD_TYPE_"}, value = {
+ CARD_TYPE_UNKNOWN,
+ CARD_TYPE_PAYMENT,
+ CARD_TYPE_VALUABLE
+ })
+ public @interface CardType {
}
@Override
@@ -56,29 +102,44 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mCardId);
+ dest.writeInt(mCardType);
mCardImage.writeToParcel(dest, flags);
TextUtils.writeToParcel(mContentDescription, dest, flags);
PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
- if (mCardIcon == null) {
+ writeIconIfNonNull(mCardIcon, dest, flags);
+ TextUtils.writeToParcel(mCardLabel, dest, flags);
+ writeIconIfNonNull(mValuableCardSecondaryImage, dest, flags);
+
+ }
+
+ /** Utility function called by writeToParcel
+ */
+ private void writeIconIfNonNull(Icon icon, Parcel dest, int flags) {
+ if (icon == null) {
dest.writeByte((byte) 0);
} else {
dest.writeByte((byte) 1);
- mCardIcon.writeToParcel(dest, flags);
+ icon.writeToParcel(dest, flags);
}
- TextUtils.writeToParcel(mCardLabel, dest, flags);
}
private static WalletCard readFromParcel(Parcel source) {
String cardId = source.readString();
+ int cardType = source.readInt();
Icon cardImage = Icon.CREATOR.createFromParcel(source);
CharSequence contentDesc = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source);
Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- return new Builder(cardId, cardImage, contentDesc, pendingIntent)
+ Icon valuableCardSecondaryImage = source.readByte() == 0 ? null :
+ Icon.CREATOR.createFromParcel(source);
+ Builder builder = new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent)
.setCardIcon(cardIcon)
- .setCardLabel(cardLabel)
- .build();
+ .setCardLabel(cardLabel);
+
+ return cardType == CARD_TYPE_VALUABLE
+ ? builder.setValuableCardSecondaryImage(valuableCardSecondaryImage).build() :
+ builder.build();
}
@NonNull
@@ -104,6 +165,16 @@
}
/**
+ * Returns the card type.
+ * @hide
+ */
+ @NonNull
+ @CardType
+ public int getCardType() {
+ return mCardType;
+ }
+
+ /**
* The visual representation of the card. If the card image Icon is a bitmap, it should have a
* width of {@link GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
* GetWalletCardsRequest#getCardHeightPx()}.
@@ -158,23 +229,37 @@
}
/**
- * Builder for {@link WalletCard} objects. You must to provide cardId, cardImage,
+ * Visual representation of the card when it is tapped. Includes a barcode to scan the card in
+ * addition to the information in the primary image.
+ * @hide
+ */
+ @Nullable
+ public Icon getValuableCardSecondaryImage() {
+ return mValuableCardSecondaryImage;
+ }
+
+ /**
+ * Builder for {@link WalletCard} objects. You must provide cardId, cardImage,
* contentDescription, and pendingIntent. If the card is opaque and should be shown with
* elevation, set hasShadow to true. cardIcon and cardLabel are optional.
*/
public static final class Builder {
private String mCardId;
+ private int mCardType;
private Icon mCardImage;
private CharSequence mContentDescription;
private PendingIntent mPendingIntent;
private Icon mCardIcon;
private CharSequence mCardLabel;
+ private Icon mValuableCardSecondaryImage;
/**
* @param cardId The card id must be non-null and unique within the list of
* cards returned. <b>Note:
* </b> this card ID should <b>not</b> contain PII (Personally
* Identifiable Information, such as username or email address).
+ * @param cardType Integer representing the card type. The card type must be
+ * non-null. If not provided, it defaults to unknown.
* @param cardImage The visual representation of the card. If the card image Icon
* is a bitmap, it should have a width of {@link
* GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
@@ -193,15 +278,30 @@
* request device unlock before sending the pending intent. It is
* recommended that the pending intent be immutable (use {@link
* PendingIntent#FLAG_IMMUTABLE}).
+ * @hide
+ */
+ public Builder(@NonNull String cardId,
+ @NonNull @CardType int cardType,
+ @NonNull Icon cardImage,
+ @NonNull CharSequence contentDescription,
+ @NonNull PendingIntent pendingIntent
+ ) {
+ mCardId = cardId;
+ mCardType = cardType;
+ mCardImage = cardImage;
+ mContentDescription = contentDescription;
+ mPendingIntent = pendingIntent;
+ }
+
+ /**
+ * Called when a card type is not provided.
*/
public Builder(@NonNull String cardId,
@NonNull Icon cardImage,
@NonNull CharSequence contentDescription,
@NonNull PendingIntent pendingIntent) {
- mCardId = cardId;
- mCardImage = cardImage;
- mContentDescription = contentDescription;
- mPendingIntent = pendingIntent;
+ this(cardId, WalletCard.CARD_TYPE_UNKNOWN, cardImage, contentDescription,
+ pendingIntent);
}
/**
@@ -236,6 +336,19 @@
}
/**
+ * Visual representation of the card when it is tapped. Includes a barcode to scan the card
+ * in addition to the information in the primary image.
+ * @hide
+ */
+ @NonNull
+ public Builder setValuableCardSecondaryImage(@Nullable Icon valuableCardSecondaryImage) {
+ Preconditions.checkState(mCardType == CARD_TYPE_VALUABLE,
+ "This field can only be set on valuable cards");
+ mValuableCardSecondaryImage = valuableCardSecondaryImage;
+ return this;
+ }
+
+ /**
* Builds a new {@link WalletCard} instance.
*
* @return A built response.
diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java
index b7873b7..61f29a4 100644
--- a/core/java/android/util/SparseSetArray.java
+++ b/core/java/android/util/SparseSetArray.java
@@ -139,4 +139,9 @@
public T valueAt(int intIndex, int valueIndex) {
return mData.valueAt(intIndex).valueAt(valueIndex);
}
+
+ /** @return The set of values for key at position {@code intIndex}. */
+ public ArraySet<T> valuesAt(int intIndex) {
+ return mData.valueAt(intIndex);
+ }
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 4f7f8ba..b9373be 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -567,11 +567,6 @@
public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
/**
- * (boolean) Whether the clipboard overlay is enabled.
- */
- public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
-
- /**
* (boolean) Whether widget provider info would be saved to / loaded from system persistence
* layer as opposed to individual manifests in respective apps.
*/
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index 8e4006a..e029af4 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -110,11 +110,20 @@
// All of this type/caption enabled for current profiles.
repeated android.content.ComponentNameProto enabled = 3;
-
repeated ManagedServiceInfoProto live_services = 4;
+ // Was: repeated ComponentNameProto, when snoozed services were not per-user-id.
+ reserved 5;
+
+ message SnoozedServices {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 user_id = 1;
+ repeated android.content.ComponentNameProto snoozed = 2;
+ }
+
// Snoozed for current profiles.
- repeated android.content.ComponentNameProto snoozed = 5;
+ repeated SnoozedServices snoozed = 6;
}
message RankingHelperProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6a80d1c..b10df60 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3893,7 +3893,7 @@
<p>Should only be requested by the System, should be required by
TileService declarations.-->
<permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|recents" />
<!-- Allows SystemUI to request third party controls.
<p>Should only be requested by the System and required by
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index 50e6f33..a5ff470 100644
--- a/core/res/res/layout/resolve_grid_item.xml
+++ b/core/res/res/layout/resolve_grid_item.xml
@@ -17,6 +17,7 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/item"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index 4b27bf2..fe296c7 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -18,7 +18,6 @@
<bool name="kg_enable_camera_default_widget">true</bool>
<bool name="kg_center_small_widgets_vertically">false</bool>
<bool name="kg_top_align_page_shrink_on_bouncer_visible">true</bool>
- <bool name="kg_wake_on_acquire_start">false</bool>
<bool name="action_bar_embed_tabs">true</bool>
<bool name="split_action_bar_is_narrow">true</bool>
<bool name="preferences_prefer_dual_pane">false</bool>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a6be07d..d4b151d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -652,6 +652,16 @@
The default is false. -->
<bool name="config_lidControlsSleep">false</bool>
+ <!-- The device states (supplied by DeviceStateManager) that should be treated as open by the
+ device fold controller. Default is empty. -->
+ <integer-array name="config_openDeviceStates">
+ <!-- Example:
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ -->
+ </integer-array>
+
<!-- The device states (supplied by DeviceStateManager) that should be treated as folded by the
display fold controller. Default is empty. -->
<integer-array name="config_foldedDeviceStates">
@@ -672,6 +682,16 @@
-->
</integer-array>
+ <!-- The device states (supplied by DeviceStateManager) that should be treated as a rear display
+ state. Default is empty. -->
+ <integer-array name="config_rearDisplayDeviceStates">
+ <!-- Example:
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ -->
+ </integer-array>
+
<!-- Indicates whether the window manager reacts to half-fold device states by overriding
rotation. -->
<bool name="config_windowManagerHalfFoldAutoRotateOverride">false</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9410e06..ba54183 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5154,6 +5154,14 @@
<!-- Cling help message confirmation button when hiding the navigation bar entering immersive mode [CHAR LIMIT=30] -->
<string name="immersive_cling_positive">Got it</string>
+ <!-- Text on a toast shown after the system rotates the screen for camera app
+ compatibility. [CHAR LIMIT=NONE] -->
+ <string name="display_rotation_camera_compat_toast_after_rotation">Rotate for a better view</string>
+
+ <!-- Text on a toast shown when a camera view is started within the app that may not be able
+ to display the camera preview correctly while in split screen. [CHAR LIMIT=NONE] -->
+ <string name="display_rotation_camera_compat_toast_in_split_screen">Exit split screen for a better view</string>
+
<!-- Label for button to confirm chosen date or time [CHAR LIMIT=30] -->
<string name="done_label">Done</string>
<!--
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index be9f6d2..f95b9cf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2841,7 +2841,6 @@
<java-symbol type="dimen" name="fast_scroller_minimum_touch_target" />
<java-symbol type="array" name="config_cdma_international_roaming_indicators" />
<java-symbol type="string" name="kg_text_message_separator" />
- <java-symbol type="bool" name="kg_wake_on_acquire_start" />
<java-symbol type="bool" name="config_use_sim_language_file" />
<java-symbol type="bool" name="config_LTE_eri_for_network_name" />
@@ -4015,8 +4014,10 @@
<java-symbol type="integer" name="config_maxScanTasksForHomeVisibility" />
<!-- For Foldables -->
+ <java-symbol type="array" name="config_openDeviceStates" />
<java-symbol type="array" name="config_foldedDeviceStates" />
<java-symbol type="array" name="config_halfFoldedDeviceStates" />
+ <java-symbol type="array" name="config_rearDisplayDeviceStates" />
<java-symbol type="bool" name="config_windowManagerHalfFoldAutoRotateOverride" />
<java-symbol type="array" name="config_deviceStatesOnWhichToWakeUp" />
<java-symbol type="array" name="config_deviceStatesOnWhichToSleep" />
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
index 416287d..53a8bb1 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
@@ -17,5 +17,4 @@
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/white" />
- <corners android:radius="20dp" />
</shape>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
index 2a4cc02..da31a46 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
@@ -17,26 +17,14 @@
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/desktop_mode_caption"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
android:background="@drawable/desktop_mode_decor_title">
<Button
- style="@style/CaptionButtonStyle"
- android:id="@+id/back_button"
- android:contentDescription="@string/back_button_text"
- android:background="@drawable/decor_back_button_dark"
- />
- <Button
android:id="@+id/caption_handle"
android:layout_width="128dp"
android:layout_height="32dp"
- android:layout_margin="5dp"
- android:padding="4dp"
android:contentDescription="@string/handle_text"
android:background="@drawable/decor_handle_dark"/>
- <Button
- style="@style/CaptionButtonStyle"
- android:id="@+id/close_window"
- android:contentDescription="@string/close_button_text"
- android:background="@drawable/decor_close_button_dark"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index a1da649..8908959 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -364,8 +364,8 @@
<!-- Height of button (32dp) + 2 * margin (5dp each). -->
<dimen name="freeform_decor_caption_height">42dp</dimen>
- <!-- Width of buttons (64dp) + handle (128dp) + padding (24dp total). -->
- <dimen name="freeform_decor_caption_width">216dp</dimen>
+ <!-- Width of buttons (32dp each) + padding (128dp total). -->
+ <dimen name="freeform_decor_caption_menu_width">256dp</dimen>
<dimen name="freeform_resize_handle">30dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 09dc68a..2534498 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -21,6 +21,7 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import android.annotation.DimenRes;
+import android.annotation.Hide;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
@@ -180,7 +181,7 @@
@VisibleForTesting(visibility = PRIVATE)
public Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo,
final int desiredHeight, final int desiredHeightResId, @Nullable final String title,
- int taskId, @Nullable final String locus, Executor mainExecutor,
+ int taskId, @Nullable final String locus, boolean isClearable, Executor mainExecutor,
final Bubbles.BubbleMetadataFlagListener listener) {
Objects.requireNonNull(key);
Objects.requireNonNull(shortcutInfo);
@@ -189,6 +190,7 @@
mKey = key;
mGroupKey = null;
mLocusId = locus != null ? new LocusId(locus) : null;
+ mIsClearable = isClearable;
mFlags = 0;
mUser = shortcutInfo.getUserHandle();
mPackageName = shortcutInfo.getPackage();
@@ -245,6 +247,11 @@
return mKey;
}
+ @Hide
+ public boolean isClearable() {
+ return mIsClearable;
+ }
+
/**
* @see StatusBarNotification#getGroupKey()
* @return the group key for this bubble, if one exists.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index 3a59614..e3aefa5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -109,7 +109,8 @@
b.rawDesiredHeightResId,
b.title,
b.taskId,
- b.locusId?.id
+ b.locusId?.id,
+ b.isClearable
)
}
}
@@ -205,6 +206,7 @@
entity.title,
entity.taskId,
entity.locus,
+ entity.isClearable,
mainExecutor,
bubbleMetadataFlagListener
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
index 186b9b1..f3abc27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
@@ -27,5 +27,6 @@
@DimenRes val desiredHeightResId: Int,
val title: String? = null,
val taskId: Int,
- val locus: String? = null
+ val locus: String? = null,
+ val isClearable: Boolean = false
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
index f4fa183..14c053c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
@@ -44,6 +44,9 @@
private const val ATTR_TASK_ID = "tid"
private const val ATTR_LOCUS = "l"
+// TODO rename it to dismissable to follow NotificationEntry namings
+private const val ATTR_CLEARABLE = "d"
+
/**
* Writes the bubbles in xml format into given output stream.
*/
@@ -84,6 +87,7 @@
bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) }
serializer.attribute(null, ATTR_TASK_ID, bubble.taskId.toString())
bubble.locus?.let { serializer.attribute(null, ATTR_LOCUS, it) }
+ serializer.attribute(null, ATTR_CLEARABLE, bubble.isClearable.toString())
serializer.endTag(null, TAG_BUBBLE)
} catch (e: IOException) {
throw RuntimeException(e)
@@ -142,7 +146,8 @@
parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null,
parser.getAttributeWithName(ATTR_TITLE),
parser.getAttributeWithName(ATTR_TASK_ID)?.toInt() ?: INVALID_TASK_ID,
- parser.getAttributeWithName(ATTR_LOCUS)
+ parser.getAttributeWithName(ATTR_LOCUS),
+ parser.getAttributeWithName(ATTR_CLEARABLE)?.toBoolean() ?: false
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 129924a..f77ac81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -143,8 +143,8 @@
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
|| (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
- && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode()
- == WINDOWING_MODE_FREEFORM);
+ && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode()
+ == WINDOWING_MODE_FREEFORM);
}
private void createWindowDecoration(
@@ -175,16 +175,18 @@
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
+ windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT);
setupCaptionColor(taskInfo, windowDecoration);
}
private class CaptionTouchEventListener implements
- View.OnClickListener, View.OnTouchListener {
+ View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
private final int mTaskId;
private final WindowContainerToken mTaskToken;
private final DragResizeCallback mDragResizeCallback;
+ private final DragDetector mDragDetector;
private int mDragPointerId = -1;
@@ -194,6 +196,7 @@
mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token;
mDragResizeCallback = dragResizeCallback;
+ mDragDetector = new DragDetector(this);
}
@Override
@@ -216,7 +219,7 @@
if (v.getId() != R.id.caption) {
return false;
}
- handleEventForMove(e);
+ mDragDetector.onMotionEvent(e);
if (e.getAction() != MotionEvent.ACTION_DOWN) {
return false;
@@ -235,10 +238,11 @@
* @param e {@link MotionEvent} to process
* @return {@code true} if a drag is happening; or {@code false} if it is not
*/
- private void handleEventForMove(MotionEvent e) {
+ @Override
+ public boolean handleMotionEvent(MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- return;
+ return false;
}
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
@@ -261,6 +265,7 @@
break;
}
}
+ return true;
}
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index d26f1fc..f94fbfc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -49,7 +49,7 @@
private View.OnTouchListener mOnCaptionTouchListener;
private DragResizeCallback mDragResizeCallback;
private DragResizeInputListener mDragResizeListener;
- private final DragDetector mDragDetector;
+ private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
private final RelayoutResult<WindowDecorLinearLayout> mResult =
@@ -69,7 +69,6 @@
mHandler = handler;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
- mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
}
void setCaptionListeners(
@@ -83,6 +82,11 @@
mDragResizeCallback = dragResizeCallback;
}
+ void setDragDetector(DragDetector dragDetector) {
+ mDragDetector = dragDetector;
+ mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
+ }
+
@Override
void relayout(RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 2863adc..c0c0ab9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -205,7 +205,7 @@
}
private class DesktopModeTouchEventListener implements
- View.OnClickListener, View.OnTouchListener {
+ View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
private final int mTaskId;
private final WindowContainerToken mTaskToken;
@@ -216,23 +216,18 @@
private DesktopModeTouchEventListener(
RunningTaskInfo taskInfo,
- DragResizeCallback dragResizeCallback,
- DragDetector dragDetector) {
+ DragResizeCallback dragResizeCallback) {
mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token;
mDragResizeCallback = dragResizeCallback;
- mDragDetector = dragDetector;
+ mDragDetector = new DragDetector(this);
}
@Override
public void onClick(View v) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
- if (id == R.id.close_window) {
- mTaskOperations.closeTask(mTaskToken);
- } else if (id == R.id.back_button) {
- mTaskOperations.injectBackKey();
- } else if (id == R.id.caption_handle) {
+ if (id == R.id.caption_handle) {
decoration.createHandleMenu();
} else if (id == R.id.desktop_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
@@ -254,8 +249,7 @@
return false;
}
if (id == R.id.caption_handle) {
- isDrag = mDragDetector.detectDragEvent(e);
- handleEventForMove(e);
+ isDrag = mDragDetector.onMotionEvent(e);
}
if (e.getAction() != MotionEvent.ACTION_DOWN) {
return isDrag;
@@ -272,19 +266,19 @@
/**
* @param e {@link MotionEvent} to process
- * @return {@code true} if a drag is happening; or {@code false} if it is not
+ * @return {@code true} if the motion event is handled.
*/
- private void handleEventForMove(MotionEvent e) {
+ @Override
+ public boolean handleMotionEvent(MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (DesktopModeStatus.isProto2Enabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- return;
+ return false;
}
if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
- && mDesktopModeController.get().getDisplayAreaWindowingMode(
- taskInfo.displayId)
+ && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
== WINDOWING_MODE_FULLSCREEN) {
- return;
+ return false;
}
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
@@ -324,6 +318,7 @@
break;
}
}
+ return true;
}
}
@@ -560,10 +555,10 @@
final TaskPositioner taskPositioner =
new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
final DesktopModeTouchEventListener touchEventListener =
- new DesktopModeTouchEventListener(
- taskInfo, taskPositioner, windowDecoration.getDragDetector());
+ new DesktopModeTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
+ windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT);
incrementEventReceiverTasks(taskInfo.displayId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 1a38d24..31b56d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -25,7 +25,6 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PointF;
-import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.view.Choreographer;
@@ -57,9 +56,10 @@
private View.OnTouchListener mOnCaptionTouchListener;
private DragResizeCallback mDragResizeCallback;
private DragResizeInputListener mDragResizeListener;
- private final DragDetector mDragDetector;
+ private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
+ private final int mCaptionMenuWidthId = R.dimen.freeform_decor_caption_menu_width;
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
@@ -81,7 +81,6 @@
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mDesktopActive = DesktopModeStatus.isActive(mContext);
- mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
}
void setCaptionListeners(
@@ -95,8 +94,9 @@
mDragResizeCallback = dragResizeCallback;
}
- DragDetector getDragDetector() {
- return mDragDetector;
+ void setDragDetector(DragDetector dragDetector) {
+ mDragDetector = dragDetector;
+ mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
}
@Override
@@ -131,22 +131,10 @@
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
- mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
if (isDragResizeable) {
mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
}
- final Resources resources = mDecorWindowContext.getResources();
- final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
- final int captionHeight = loadDimensionPixelSize(resources,
- mRelayoutParams.mCaptionHeightId);
- final int captionWidth = loadDimensionPixelSize(resources,
- mRelayoutParams.mCaptionWidthId);
- final int captionLeft = taskBounds.width() / 2
- - captionWidth / 2;
- final int captionTop = taskBounds.top
- <= captionHeight / 2 ? 0 : -captionHeight / 2;
- mRelayoutParams.setCaptionPosition(captionLeft, captionTop);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -212,10 +200,6 @@
private void setupRootView() {
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
caption.setOnTouchListener(mOnCaptionTouchListener);
- final View close = caption.findViewById(R.id.close_window);
- close.setOnClickListener(mOnCaptionButtonClickListener);
- final View back = caption.findViewById(R.id.back_button);
- back.setOnClickListener(mOnCaptionButtonClickListener);
final View handle = caption.findViewById(R.id.caption_handle);
handle.setOnTouchListener(mOnCaptionTouchListener);
handle.setOnClickListener(mOnCaptionButtonClickListener);
@@ -230,8 +214,6 @@
desktop.setOnClickListener(mOnCaptionButtonClickListener);
final View split = menu.findViewById(R.id.split_screen_button);
split.setOnClickListener(mOnCaptionButtonClickListener);
- final View more = menu.findViewById(R.id.more_button);
- more.setOnClickListener(mOnCaptionButtonClickListener);
}
/**
@@ -264,10 +246,6 @@
void setButtonVisibility(boolean visible) {
final int visibility = visible ? View.VISIBLE : View.GONE;
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- final View back = caption.findViewById(R.id.back_button);
- final View close = caption.findViewById(R.id.close_window);
- back.setVisibility(visibility);
- close.setVisibility(visibility);
final int buttonTintColorRes =
mDesktopActive ? R.color.decor_button_dark_color
: R.color.decor_button_light_color;
@@ -297,14 +275,18 @@
void createHandleMenu() {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
final Resources resources = mDecorWindowContext.getResources();
- final int x = mRelayoutParams.mCaptionX;
- final int y = mRelayoutParams.mCaptionY;
- final int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
- final int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ final int captionWidth = mTaskInfo.getConfiguration()
+ .windowConfiguration.getBounds().width();
+ final int menuWidth = loadDimensionPixelSize(
+ resources, mCaptionMenuWidthId);
+ final int height = loadDimensionPixelSize(
+ resources, mRelayoutParams.mCaptionHeightId);
+ final int x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
+ - mResult.mDecorContainerOffsetX;
+ final int y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
String namePrefix = "Caption Menu";
- mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t,
- x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
- width, height);
+ mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y,
+ menuWidth, height);
mSyncQueue.runInSync(transaction -> {
transaction.merge(t);
t.close();
@@ -370,7 +352,7 @@
if (mResult.mRootView == null) return false;
final PointF inputPoint = offsetCaptionLocation(ev);
final View view = mResult.mRootView.findViewById(layoutId);
- return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0);
+ return view != null && pointInView(view, inputPoint.x, inputPoint.y);
}
boolean checkTouchEventInHandle(MotionEvent ev) {
@@ -387,32 +369,39 @@
*/
void checkClickEvent(MotionEvent ev) {
if (mResult.mRootView == null) return;
- final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- final PointF inputPoint = offsetCaptionLocation(ev);
if (!isHandleMenuActive()) {
+ final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
final View handle = caption.findViewById(R.id.caption_handle);
- clickIfPointInView(inputPoint, handle);
+ clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
} else {
final View menu = mHandleMenu.mWindowViewHost.getView();
+ final int captionWidth = mTaskInfo.getConfiguration().windowConfiguration
+ .getBounds().width();
+ final int menuX = mRelayoutParams.mCaptionX + (captionWidth / 2)
+ - (menu.getWidth() / 2);
+ final PointF inputPoint = new PointF(ev.getX() - menuX, ev.getY());
final View fullscreen = menu.findViewById(R.id.fullscreen_button);
if (clickIfPointInView(inputPoint, fullscreen)) return;
final View desktop = menu.findViewById(R.id.desktop_button);
if (clickIfPointInView(inputPoint, desktop)) return;
final View split = menu.findViewById(R.id.split_screen_button);
if (clickIfPointInView(inputPoint, split)) return;
- final View more = menu.findViewById(R.id.more_button);
- clickIfPointInView(inputPoint, more);
}
}
private boolean clickIfPointInView(PointF inputPoint, View v) {
- if (v.pointInView(inputPoint.x - v.getLeft(), inputPoint.y, 0)) {
+ if (pointInView(v, inputPoint.x, inputPoint.y)) {
mOnCaptionButtonClickListener.onClick(v);
return true;
}
return false;
}
+ private boolean pointInView(View v, float x, float y) {
+ return v != null && v.getLeft() <= x && v.getRight() >= x
+ && v.getTop() <= y && v.getBottom() >= y;
+ }
+
@Override
public void close() {
closeDragResizeListener();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index 0abe8ab..4fac843 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
@@ -25,63 +26,82 @@
import android.view.MotionEvent;
/**
- * A detector for touch inputs that differentiates between drag and click inputs.
+ * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow
+ * of {@link MotionEvent} and generates a new flow of motion events with slop in consideration to
+ * the event handler. In particular, it always passes down, up and cancel events. It'll pass move
+ * events only when there is at least one move event that's beyond the slop threshold. For the
+ * purpose of convenience it also passes all events of other actions.
+ *
* All touch events must be passed through this class to track a drag event.
*/
-public class DragDetector {
+class DragDetector {
+ private final MotionEventHandler mEventHandler;
+
+ private final PointF mInputDownPoint = new PointF();
private int mTouchSlop;
- private PointF mInputDownPoint;
private boolean mIsDragEvent;
private int mDragPointerId;
- public DragDetector(int touchSlop) {
- mTouchSlop = touchSlop;
- mInputDownPoint = new PointF();
- mIsDragEvent = false;
- mDragPointerId = -1;
+
+ private boolean mResultOfDownAction;
+
+ DragDetector(MotionEventHandler eventHandler) {
+ resetState();
+ mEventHandler = eventHandler;
}
/**
- * Determine if {@link MotionEvent} is part of a drag event.
- * @return {@code true} if this is a drag event, {@code false} if not
- */
- public boolean detectDragEvent(MotionEvent ev) {
- switch (ev.getAction()) {
+ * The receiver of the {@link MotionEvent} flow.
+ *
+ * @return the result returned by {@link #mEventHandler}, or the result when
+ * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
+ */
+ boolean onMotionEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
case ACTION_DOWN: {
+ // Only touch screens generate noisy moves.
+ mIsDragEvent = (ev.getSource() & SOURCE_TOUCHSCREEN) != SOURCE_TOUCHSCREEN;
mDragPointerId = ev.getPointerId(0);
float rawX = ev.getRawX(0);
float rawY = ev.getRawY(0);
mInputDownPoint.set(rawX, rawY);
- return false;
+ mResultOfDownAction = mEventHandler.handleMotionEvent(ev);
+ return mResultOfDownAction;
}
case ACTION_MOVE: {
if (!mIsDragEvent) {
int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
- if (Math.hypot(dx, dy) > mTouchSlop) {
- mIsDragEvent = true;
- }
+ mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop;
}
- return mIsDragEvent;
+ if (mIsDragEvent) {
+ return mEventHandler.handleMotionEvent(ev);
+ } else {
+ return mResultOfDownAction;
+ }
}
- case ACTION_UP: {
- boolean result = mIsDragEvent;
- mIsDragEvent = false;
- mInputDownPoint.set(0, 0);
- mDragPointerId = -1;
- return result;
- }
+ case ACTION_UP:
case ACTION_CANCEL: {
- mIsDragEvent = false;
- mInputDownPoint.set(0, 0);
- mDragPointerId = -1;
- return false;
+ resetState();
+ return mEventHandler.handleMotionEvent(ev);
}
+ default:
+ return mEventHandler.handleMotionEvent(ev);
}
- return mIsDragEvent;
}
- public void setTouchSlop(int touchSlop) {
+ void setTouchSlop(int touchSlop) {
mTouchSlop = touchSlop;
}
+
+ private void resetState() {
+ mIsDragEvent = false;
+ mInputDownPoint.set(0, 0);
+ mDragPointerId = -1;
+ mResultOfDownAction = false;
+ }
+
+ interface MotionEventHandler {
+ boolean handleMotionEvent(MotionEvent ev);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index d3f1332..2963763 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -48,7 +48,6 @@
* Task edges are for resizing with a mouse.
* Task corners are for resizing with touch input.
*/
-// TODO(b/251270585): investigate how to pass taps in corners to the tasks
class DragResizeInputListener implements AutoCloseable {
private static final String TAG = "DragResizeInputListener";
@@ -115,7 +114,8 @@
mInputEventReceiver = new TaskResizeInputEventReceiver(
mInputChannel, mHandler, mChoreographer);
mCallback = callback;
- mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
+ mDragDetector = new DragDetector(mInputEventReceiver);
+ mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
}
/**
@@ -223,12 +223,12 @@
}
}
- private class TaskResizeInputEventReceiver extends InputEventReceiver {
+ private class TaskResizeInputEventReceiver extends InputEventReceiver
+ implements DragDetector.MotionEventHandler {
private final Choreographer mChoreographer;
private final Runnable mConsumeBatchEventRunnable;
private boolean mConsumeBatchEventScheduled;
private boolean mShouldHandleEvents;
- private boolean mDragging;
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -270,15 +270,15 @@
if (!(inputEvent instanceof MotionEvent)) {
return false;
}
+ return mDragDetector.onMotionEvent((MotionEvent) inputEvent);
+ }
- MotionEvent e = (MotionEvent) inputEvent;
+ @Override
+ public boolean handleMotionEvent(MotionEvent e) {
boolean result = false;
// Check if this is a touch event vs mouse event.
// Touch events are tracked in four corners. Other events are tracked in resize edges.
boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
- if (isTouch) {
- mDragging = mDragDetector.detectDragEvent(e);
- }
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
float x = e.getX(0);
@@ -305,24 +305,17 @@
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
- if (!isTouch) {
- // For all other types allow immediate dragging.
- mDragging = true;
- }
- if (mDragging) {
- mCallback.onDragResizeMove(rawX, rawY);
- result = true;
- }
+ mCallback.onDragResizeMove(rawX, rawY);
+ result = true;
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- if (mShouldHandleEvents && mDragging) {
+ if (mShouldHandleEvents) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
mCallback.onDragResizeEnd(
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
}
- mDragging = false;
mShouldHandleEvents = false;
mDragPointerId = -1;
result = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 20631f8..8cd2a59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -40,9 +40,7 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mResizeStartPoint = new PointF();
private final Rect mResizeTaskBounds = new Rect();
- // Whether the |dragResizing| hint should be sent with the next bounds change WCT.
- // Used to optimized fluid resizing of freeform tasks.
- private boolean mPendingDragResizeHint = false;
+ private boolean mHasMoved = false;
private int mCtrlType;
private DragStartListener mDragStartListener;
@@ -60,11 +58,7 @@
@Override
public void onDragResizeStart(int ctrlType, float x, float y) {
- if (ctrlType != CTRL_TYPE_UNDEFINED) {
- // The task is being resized, send the |dragResizing| hint to core with the first
- // bounds-change wct.
- mPendingDragResizeHint = true;
- }
+ mHasMoved = false;
mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType;
@@ -78,30 +72,44 @@
public void onDragResizeMove(float x, float y) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (changeBounds(wct, x, y)) {
- if (mPendingDragResizeHint) {
+ // The task is being resized, send the |dragResizing| hint to core with the first
+ // bounds-change wct.
+ if (!mHasMoved && mCtrlType != CTRL_TYPE_UNDEFINED) {
// This is the first bounds change since drag resize operation started.
wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
- mPendingDragResizeHint = false;
}
mTaskOrganizer.applyTransaction(wct);
+ mHasMoved = true;
}
}
@Override
public void onDragResizeEnd(float x, float y) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
- changeBounds(wct, x, y);
- mTaskOrganizer.applyTransaction(wct);
+ // |mHasMoved| being false means there is no real change to the task bounds in WM core, so
+ // we don't need a WCT to finish it.
+ if (mHasMoved) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
+ changeBounds(wct, x, y);
+ mTaskOrganizer.applyTransaction(wct);
+ }
- mCtrlType = 0;
+ mCtrlType = CTRL_TYPE_UNDEFINED;
mTaskBoundsAtDragStart.setEmpty();
mResizeStartPoint.set(0, 0);
- mPendingDragResizeHint = false;
+ mHasMoved = false;
}
private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
- float deltaX = x - mResizeStartPoint.x;
+ // |mResizeTaskBounds| is the bounds last reported if |mHasMoved| is true. If it's not true,
+ // we can compare it against |mTaskBoundsAtDragStart|.
+ final int oldLeft = mHasMoved ? mResizeTaskBounds.left : mTaskBoundsAtDragStart.left;
+ final int oldTop = mHasMoved ? mResizeTaskBounds.top : mTaskBoundsAtDragStart.top;
+ final int oldRight = mHasMoved ? mResizeTaskBounds.right : mTaskBoundsAtDragStart.right;
+ final int oldBottom = mHasMoved ? mResizeTaskBounds.bottom : mTaskBoundsAtDragStart.bottom;
+
+ final float deltaX = x - mResizeStartPoint.x;
+ final float deltaY = y - mResizeStartPoint.y;
mResizeTaskBounds.set(mTaskBoundsAtDragStart);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
mResizeTaskBounds.left += deltaX;
@@ -109,22 +117,22 @@
if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
mResizeTaskBounds.right += deltaX;
}
- float deltaY = y - mResizeStartPoint.y;
if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
mResizeTaskBounds.top += deltaY;
}
if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
mResizeTaskBounds.bottom += deltaY;
}
- if (mCtrlType == 0) {
+ if (mCtrlType == CTRL_TYPE_UNDEFINED) {
mResizeTaskBounds.offset((int) deltaX, (int) deltaY);
}
- if (!mResizeTaskBounds.isEmpty()) {
- wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
- return true;
+ if (oldLeft == mResizeTaskBounds.left && oldTop == mResizeTaskBounds.top
+ && oldRight == mResizeTaskBounds.right && oldBottom == mResizeTaskBounds.bottom) {
+ return false;
}
- return false;
+ wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
+ return true;
}
interface DragStartListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 7f85988..62b72f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -252,9 +252,7 @@
}
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
- final int captionWidth = params.mCaptionWidthId == Resources.ID_NULL
- ? taskBounds.width()
- : loadDimensionPixelSize(resources, params.mCaptionWidthId);
+ final int captionWidth = taskBounds.width();
startT.setPosition(
mCaptionContainerSurface,
@@ -428,7 +426,6 @@
RunningTaskInfo mRunningTaskInfo;
int mLayoutResId;
int mCaptionHeightId;
- int mCaptionWidthId;
int mShadowRadiusId;
int mOutsetTopId;
@@ -454,7 +451,6 @@
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
- mCaptionWidthId = Resources.ID_NULL;
mShadowRadiusId = Resources.ID_NULL;
mOutsetTopId = Resources.ID_NULL;
diff --git a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
index 8949a75..27d40b2 100644
--- a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
@@ -17,7 +17,7 @@
<resources>
<!-- Resources used in WindowDecorationTests -->
<dimen name="test_freeform_decor_caption_height">32dp</dimen>
- <dimen name="test_freeform_decor_caption_width">216dp</dimen>
+ <dimen name="test_freeform_decor_caption_menu_width">216dp</dimen>
<dimen name="test_window_decor_left_outset">10dp</dimen>
<dimen name="test_window_decor_top_outset">20dp</dimen>
<dimen name="test_window_decor_right_outset">30dp</dimen>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
index 1636c5f..0a31338 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
@@ -21,7 +21,6 @@
import android.util.SparseArray
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.bubbles.storage.BubbleXmlHelperTest.Companion.sparseArraysEqual
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertTrue
@@ -36,7 +35,8 @@
// user, package, shortcut, notification key, height, res-height, title, taskId, locusId
private val user0Bubbles = listOf(
- BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1, null),
+ BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1, null,
+ true),
BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2,
null),
BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null,
@@ -44,7 +44,8 @@
)
private val user1Bubbles = listOf(
- BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3, null),
+ BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3, null,
+ true),
BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4,
null),
BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null,
@@ -76,6 +77,6 @@
assertEquals(actual.size(), 0)
repository.persistsToDisk(bubbles)
- assertTrue(sparseArraysEqual(bubbles, repository.readFromDisk()))
+ assertTrue(bubbles.contentEquals(repository.readFromDisk()))
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
index 4ab9f87..c02e2d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
@@ -34,7 +34,8 @@
class BubbleXmlHelperTest : ShellTestCase() {
private val user0Bubbles = listOf(
- BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1),
+ BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1,
+ isClearable = true),
BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2,
null),
BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null,
@@ -42,7 +43,8 @@
)
private val user1Bubbles = listOf(
- BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3),
+ BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3,
+ isClearable = true),
BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4,
null),
BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null,
@@ -51,28 +53,6 @@
private val bubbles = SparseArray<List<BubbleEntity>>()
- // Checks that the contents of the two sparse arrays are the same.
- companion object {
- fun sparseArraysEqual(
- one: SparseArray<List<BubbleEntity>>?,
- two: SparseArray<List<BubbleEntity>>?
- ): Boolean {
- if (one == null && two == null) return true
- if ((one == null) != (two == null)) return false
- if (one!!.size() != two!!.size()) return false
- for (i in 0 until one.size()) {
- val k1 = one.keyAt(i)
- val v1 = one.valueAt(i)
- val k2 = two.keyAt(i)
- val v2 = two.valueAt(i)
- if (k1 != k2 && v1 != v2) {
- return false
- }
- }
- return true
- }
- }
-
@Before
fun setup() {
bubbles.put(0, user0Bubbles)
@@ -83,14 +63,14 @@
fun testWriteXml() {
val expectedEntries = """
<bs uid="0">
-<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" />
-<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" />
-<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" />
+<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" d="true" />
+<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" d="false" />
+<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" d="false" />
</bs>
<bs uid="1">
-<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" />
-<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" />
-<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" />
+<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" d="true" />
+<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" d="false" />
+<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" d="false" />
</bs>
""".trimIndent()
ByteArrayOutputStream().use {
@@ -107,19 +87,19 @@
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<bs v="2">
<bs uid="0">
-<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" />
-<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" />
-<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" />
+<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" d="true" />
+<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" d="false" />
+<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" d="false" />
</bs>
<bs uid="1">
-<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" />
-<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" />
-<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" />
+<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" d="true" />
+<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" d="false" />
+<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" d="false" />
</bs>
</bs>
""".trimIndent()
val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
- assertTrue("failed parsing bubbles from xml\n$src", sparseArraysEqual(bubbles, actual))
+ assertTrue("failed parsing bubbles from xml\n$src", bubbles.contentEquals(actual))
}
// V0 -> V1 happened prior to release / during dogfood so nothing is saved
@@ -161,8 +141,7 @@
</bs>
""".trimIndent()
val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
- assertTrue("failed parsing bubbles from xml\n$src",
- sparseArraysEqual(expectedBubbles, actual))
+ assertTrue("failed parsing bubbles from xml\n$src", expectedBubbles.contentEquals(actual))
}
/**
@@ -187,7 +166,7 @@
""".trimIndent()
val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
assertTrue("failed parsing bubbles from xml\n$src",
- sparseArraysEqual(expectedBubbles, actual))
+ expectedBubbles.contentEquals(actual))
}
@Test
@@ -210,6 +189,6 @@
)
val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
assertTrue("failed parsing bubbles from xml\n$src",
- sparseArraysEqual(expectedBubbles, actual))
+ expectedBubbles.contentEquals(actual))
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
new file mode 100644
index 0000000..8f84008
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.os.SystemClock
+import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
+import android.view.InputDevice
+import androidx.test.filters.SmallTest
+import org.junit.After
+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.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/**
+ * Tests for [DragDetector].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DragDetectorTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DragDetectorTest {
+ private val motionEvents = mutableListOf<MotionEvent>()
+
+ @Mock
+ private lateinit var eventHandler: DragDetector.MotionEventHandler
+
+ private lateinit var dragDetector: DragDetector
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(eventHandler.handleMotionEvent(any())).thenReturn(true)
+
+ dragDetector = DragDetector(eventHandler)
+ dragDetector.setTouchSlop(SLOP)
+ }
+
+ @After
+ fun tearDown() {
+ motionEvents.forEach {
+ it.recycle()
+ }
+ motionEvents.clear()
+ }
+
+ @Test
+ fun testNoMove_passesDownAndUp() {
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+ }
+
+ @Test
+ fun testMoveInSlop_touch_passesDownAndUp() {
+ `when`(eventHandler.handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN
+ })).thenReturn(false)
+
+ assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+
+ val newX = X + SLOP - 1
+ assertFalse(
+ dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
+ verify(eventHandler, never()).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_MOVE
+ })
+
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+ }
+
+ @Test
+ fun testMoveInSlop_mouse_passesDownMoveAndUp() {
+ `when`(eventHandler.handleMotionEvent(argThat {
+ it.action == MotionEvent.ACTION_DOWN
+ })).thenReturn(false)
+
+ assertFalse(dragDetector.onMotionEvent(
+ createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_MOUSE
+ })
+
+ val newX = X + SLOP - 1
+ assertTrue(dragDetector.onMotionEvent(
+ createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_MOUSE
+ })
+
+ assertTrue(dragDetector.onMotionEvent(
+ createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_MOUSE
+ })
+ }
+
+ @Test
+ fun testMoveBeyondSlop_passesDownMoveAndUp() {
+ `when`(eventHandler.handleMotionEvent(argThat {
+ it.action == MotionEvent.ACTION_DOWN
+ })).thenReturn(false)
+
+ assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+
+ val newX = X + SLOP + 1
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+ }
+
+ @Test
+ fun testPassesHoverEnter() {
+ `when`(eventHandler.handleMotionEvent(argThat {
+ it.action == MotionEvent.ACTION_HOVER_ENTER
+ })).thenReturn(false)
+
+ assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y
+ })
+ }
+
+ @Test
+ fun testPassesHoverMove() {
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y
+ })
+ }
+
+ @Test
+ fun testPassesHoverExit() {
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y
+ })
+ }
+
+ private fun createMotionEvent(action: Int, x: Float = X, y: Float = Y, isTouch: Boolean = true):
+ MotionEvent {
+ val time = SystemClock.uptimeMillis()
+ val ev = MotionEvent.obtain(time, time, action, x, y, 0)
+ ev.source = if (isTouch) InputDevice.SOURCE_TOUCHSCREEN else InputDevice.SOURCE_MOUSE
+ motionEvents.add(ev)
+ return ev
+ }
+
+ companion object {
+ private const val SLOP = 10
+ private const val X = 123f
+ private const val Y = 234f
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
index ac10ddb..804c416 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -1,6 +1,7 @@
package com.android.wm.shell.windowdecor
import android.app.ActivityManager
+import android.app.WindowConfiguration
import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
@@ -10,6 +11,7 @@
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
import org.junit.Before
import org.junit.Test
@@ -63,6 +65,90 @@
}
@Test
+ fun testDragResize_notMove_skipsTransactionOnEnd() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragResizeEnd(
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10
+ )
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_noEffectiveMove_skipsTransactionOnMoveAndEnd() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragResizeMove(
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragResizeEnd(
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10
+ )
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_hasEffectiveMove_issuesTransactionOnMoveAndEnd() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragResizeMove(
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat()
+ )
+ val rectAfterMove = Rect(STARTING_BOUNDS)
+ rectAfterMove.right += 10
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterMove
+ }
+ })
+
+ taskPositioner.onDragResizeEnd(
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10
+ )
+ val rectAfterEnd = Rect(rectAfterMove)
+ rectAfterEnd.top += 10
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterEnd
+ }
+ })
+ }
+
+ @Test
fun testDragResize_move_skipsDragResizingFlag() {
taskPositioner.onDragResizeStart(
CTRL_TYPE_UNDEFINED, // Move
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index ec4f17f..2f5263c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -107,6 +107,7 @@
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
+ private int mCaptionMenuWidthId;
@Before
public void setUp() {
@@ -116,8 +117,7 @@
mRelayoutParams.mLayoutResId = 0;
mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
- // Caption should have fixed width except in testLayoutResultCalculation_fullWidthCaption()
- mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
+ mCaptionMenuWidthId = R.dimen.test_freeform_decor_caption_menu_width;
mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
@@ -240,7 +240,7 @@
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
- verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64);
+ verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
@@ -248,7 +248,7 @@
verify(mMockSurfaceControlViewHost)
.setView(same(mMockView),
argThat(lp -> lp.height == 64
- && lp.width == 432
+ && lp.width == 300
&& (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
if (ViewRootImpl.CAPTION_ON_SHELL) {
verify(mMockView).setTaskFocusState(true);
@@ -484,7 +484,6 @@
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
- mRelayoutParams.mCaptionWidthId = Resources.ID_NULL;
windowDecor.relayout(taskInfo);
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
@@ -558,7 +557,7 @@
final Resources resources = mDecorWindowContext.getResources();
int x = mRelayoutParams.mCaptionX;
int y = mRelayoutParams.mCaptionY;
- int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId);
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 798688e..8d96bb7 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7687,7 +7687,8 @@
Objects.requireNonNull(device);
try {
if (device.getId() == 0) {
- throw new IllegalArgumentException("In valid device: " + device);
+ Log.w(TAG, "setCommunicationDevice: device not found: " + device);
+ return false;
}
return getService().setCommunicationDevice(mICallBack, device.getId());
} catch (RemoteException e) {
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 2f4dd8f..408883e 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -1004,9 +1004,10 @@
static jbyteArray android_media_MediaDrm_getSupportedCryptoSchemesNative(JNIEnv *env) {
sp<IDrm> drm = android::DrmUtils::MakeDrm();
+ if (drm == NULL) return env->NewByteArray(0);
+
std::vector<uint8_t> bv;
drm->getSupportedSchemes(bv);
-
jbyteArray jUuidBytes = env->NewByteArray(bv.size());
env->SetByteArrayRegion(jUuidBytes, 0, bv.size(), reinterpret_cast<const jbyte *>(bv.data()));
return jUuidBytes;
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
index 18e8a96..bf922bc 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
@@ -21,4 +21,5 @@
const val MESSAGE_ID_SLOT_SELECTED = 1337
const val KEY_SLOT_ID = "slot_id"
const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
+ const val KEY_HIGHLIGHT_QUICK_AFFORDANCES = "highlight_quick_affordances"
}
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index ccb35fa..79d5718 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -52,6 +52,10 @@
* Unselect an already-selected quick affordance from a slot
* Unselect all already-selected quick affordances from a slot
+## Testing
+* Add a unit test for your implementation of `KeyguardQuickAffordanceConfig`
+* Manually verify that your implementation works in multi-user environments from both the main user and a secondary user
+
## Debugging
To see the current state of the system, you can run `dumpsys`:
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 1f00812..1a67691e 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -197,13 +197,10 @@
-packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt
-packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
-packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -611,7 +608,6 @@
-packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
index 9ed3bac..70b5d73 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
@@ -105,6 +105,11 @@
default void onDozingChanged(boolean isDozing) {}
/**
+ * Callback to be notified when Dreaming changes. Dreaming is stored separately from state.
+ */
+ default void onDreamingChanged(boolean isDreaming) {}
+
+ /**
* Callback to be notified when the doze amount changes. Useful for animations.
* Note: this will be called for each animation frame. Please be careful to avoid
* performance regressions.
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index 2cac9c7..90851e2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -45,7 +45,7 @@
android:id="@+id/user_switcher_header"
android:textDirection="locale"
android:layout_width="@dimen/bouncer_user_switcher_width"
- android:layout_height="wrap_content" />
+ android:layout_height="match_parent" />
</com.android.keyguard.KeyguardUserSwitcherAnchor>
</LinearLayout>
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
index a1068c6..6c8db91 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
@@ -25,9 +25,6 @@
<!-- Margin around the various security views -->
<dimen name="keyguard_security_view_top_margin">12dp</dimen>
- <!-- Padding for the lock icon on the keyguard -->
- <dimen name="lock_icon_padding">16dp</dimen>
-
<!-- Overload default clock widget parameters -->
<dimen name="widget_big_font_size">100dp</dimen>
<dimen name="widget_label_font_size">18sp</dimen>
diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml
index acd2462..7d03b0d 100644
--- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml
+++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml
@@ -21,7 +21,7 @@
<item android:state_selected="true">
<shape android:shape="oval">
<stroke
- android:color="@color/control_primary_text"
+ android:color="?android:attr/textColorPrimary"
android:width="2dp"/>
<size
android:width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
new file mode 100644
index 0000000..3807b92
--- /dev/null
+++ b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+<ripple
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white"/>
+ <corners android:radius="28dp" />
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorSurface" />
+ <corners android:radius="28dp" />
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
deleted file mode 100644
index 1a1fc75..0000000
--- a/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
+++ /dev/null
@@ -1,160 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<com.android.systemui.screenshot.DraggableConstraintLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/clipboard_ui"
- android:theme="@style/FloatingOverlay"
- android:alpha="0"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:contentDescription="@string/clipboard_overlay_window_name">
- <ImageView
- android:id="@+id/actions_container_background"
- android:visibility="gone"
- android:layout_height="0dp"
- android:layout_width="0dp"
- android:elevation="4dp"
- android:background="@drawable/action_chip_container_background"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- app:layout_constraintBottom_toBottomOf="@+id/actions_container"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@+id/actions_container"
- app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
- <HorizontalScrollView
- android:id="@+id/actions_container"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
- android:paddingEnd="@dimen/overlay_action_container_padding_right"
- android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
- android:elevation="4dp"
- android:scrollbars="none"
- android:layout_marginBottom="4dp"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintWidth_percent="1.0"
- app:layout_constraintWidth_max="wrap"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@+id/preview_border"
- app:layout_constraintEnd_toEndOf="parent">
- <LinearLayout
- android:id="@+id/actions"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:animateLayoutChanges="true">
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/share_chip"/>
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/remote_copy_chip"/>
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/edit_chip"/>
- </LinearLayout>
- </HorizontalScrollView>
- <View
- android:id="@+id/preview_border"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_marginStart="@dimen/overlay_offset_x"
- android:layout_marginBottom="12dp"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- android:elevation="7dp"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
- android:background="@drawable/overlay_border"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_preview_end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierMargin="@dimen/overlay_border_width"
- app:barrierDirection="end"
- app:constraint_referenced_ids="clipboard_preview"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_preview_top"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierDirection="top"
- app:barrierMargin="@dimen/overlay_border_width_neg"
- app:constraint_referenced_ids="clipboard_preview"/>
- <FrameLayout
- android:id="@+id/clipboard_preview"
- android:elevation="7dp"
- android:background="@drawable/overlay_preview_background"
- android:clipChildren="true"
- android:clipToOutline="true"
- android:clipToPadding="true"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_margin="@dimen/overlay_border_width"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- app:layout_constraintBottom_toBottomOf="@id/preview_border"
- app:layout_constraintStart_toStartOf="@id/preview_border"
- app:layout_constraintEnd_toEndOf="@id/preview_border"
- app:layout_constraintTop_toTopOf="@id/preview_border">
- <TextView android:id="@+id/text_preview"
- android:textFontWeight="500"
- android:padding="8dp"
- android:gravity="center|start"
- android:ellipsize="end"
- android:autoSizeTextType="uniform"
- android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
- android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
- android:textColor="?attr/overlayButtonTextColor"
- android:textColorLink="?attr/overlayButtonTextColor"
- android:background="?androidprv:attr/colorAccentSecondary"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_height="@dimen/clipboard_preview_size"/>
- <ImageView
- android:id="@+id/image_preview"
- android:scaleType="fitCenter"
- android:adjustViewBounds="true"
- android:contentDescription="@string/clipboard_image_preview"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/hidden_preview"
- android:visibility="gone"
- android:textFontWeight="500"
- android:padding="8dp"
- android:gravity="center"
- android:textSize="14sp"
- android:textColor="?attr/overlayButtonTextColor"
- android:background="?androidprv:attr/colorAccentSecondary"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_height="@dimen/clipboard_preview_size"/>
- </FrameLayout>
- <FrameLayout
- android:id="@+id/dismiss_button"
- android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
- android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
- android:elevation="10dp"
- android:visibility="gone"
- android:alpha="0"
- app:layout_constraintStart_toEndOf="@id/clipboard_preview"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview"
- app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
- android:contentDescription="@string/clipboard_dismiss_description">
- <ImageView
- android:id="@+id/dismiss_image"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
- android:src="@drawable/overlay_cancel"/>
- </FrameLayout>
-</com.android.systemui.screenshot.DraggableConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
new file mode 100644
index 0000000..89d88fe
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="52dp"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:background="@drawable/keyguard_settings_popup_menu_background"
+ android:paddingStart="16dp"
+ android:paddingEnd="24dp"
+ android:paddingVertical="16dp">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:layout_marginEnd="16dp"
+ android:tint="?android:attr/textColorPrimary"
+ android:importantForAccessibility="no"
+ tools:ignore="UseAppTint" />
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="14sp"
+ android:maxLines="1"
+ android:ellipsize="end" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 159323a..3c860a9 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -26,6 +26,11 @@
android:layout_height="match_parent"
android:background="@android:color/transparent">
+ <com.android.systemui.common.ui.view.LongPressHandlingView
+ android:id="@+id/keyguard_long_press"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<ViewStub
android:id="@+id/keyguard_qs_user_switch_stub"
android:layout="@layout/keyguard_qs_user_switch"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e8a5e7e..1826c00 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -824,4 +824,8 @@
<item>bottom_end:wallet</item>
</string-array>
+ <!-- Package name for the app that implements the wallpaper picker. -->
+ <string name="config_wallpaperPickerPackage" translatable="false">
+ com.android.wallpaper
+ </string>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1ef0206..dfc0150 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1648,4 +1648,10 @@
<dimen name="rear_display_animation_height">200dp</dimen>
<dimen name="rear_display_title_top_padding">24dp</dimen>
<dimen name="rear_display_title_bottom_padding">16dp</dimen>
+
+ <!--
+ Vertical distance between the pointer and the popup menu that shows up on the lock screen when
+ it is long-pressed.
+ -->
+ <dimen name="keyguard_long_press_settings_popup_vertical_offset">96dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index c5ffc94..6354752 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -33,8 +33,6 @@
<!-- Whether to show chipbar UI whenever the device is unlocked by ActiveUnlock. -->
<bool name="flag_active_unlock_chipbar">true</bool>
- <bool name="flag_smartspace">false</bool>
-
<!-- Whether the user switcher chip shows in the status bar. When true, the multi user
avatar will no longer show on the lockscreen -->
<bool name="flag_user_switcher_chip">false</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8dcd3b0..e60835c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2808,4 +2808,13 @@
<!-- Label for the close button on switch to work profile dialog. Switch to work profile dialog guide users to make call from work
profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
<string name="call_from_work_profile_close">Close</string>
+
+ <!--
+ Label for a menu item in a menu that is shown when the user wishes to configure the lock screen.
+ Clicking on this menu item takes the user to a screen where they can modify the settings of the
+ lock screen.
+
+ [CHAR LIMIT=32]
+ -->
+ <string name="lock_screen_settings">Lock screen settings</string>
</resources>
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index d97031f..52a98984 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -41,9 +41,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/privacy_container"
app:layout_constraintBottom_toBottomOf="@id/carrier_group"
- app:layout_constraintEnd_toStartOf="@id/carrier_group"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
<Transform
android:scaleX="2.57"
@@ -62,18 +59,18 @@
/>
</Constraint>
+ <!-- LargeScreenShadeHeaderController helps with managing clock width to layout this view -->
<Constraint
android:id="@+id/carrier_group">
<Layout
- app:layout_constraintWidth_min="48dp"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="@dimen/large_screen_shade_header_min_height"
- app:layout_constraintStart_toEndOf="@id/clock"
+ app:layout_constraintWidth_min="48dp"
+ app:layout_constraintWidth_default="wrap"
+ app:layout_constraintStart_toStartOf="@id/clock"
app:layout_constraintTop_toBottomOf="@id/privacy_container"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="1"
app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
<PropertySet
android:alpha="1"
diff --git a/packages/SystemUI/scripts/token_alignment/.eslintrc.json b/packages/SystemUI/scripts/token_alignment/.eslintrc.json
new file mode 100644
index 0000000..69dc00e
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/.eslintrc.json
@@ -0,0 +1,31 @@
+{
+ "env": {
+ "es2021": true,
+ "node": true
+ },
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+ "plugins": ["prettier", "@typescript-eslint", "eslint-plugin-simple-import-sort", "import"],
+ "extends": ["prettier", "eslint:recommended", "plugin:@typescript-eslint/recommended"],
+ "rules": {
+ "prettier/prettier": ["error"],
+ "no-unused-vars": "off",
+ "@typescript-eslint/no-unused-vars": [
+ "warn",
+ {
+ "argsIgnorePattern": "^_",
+ "varsIgnorePattern": "^_",
+ "caughtErrorsIgnorePattern": "^_"
+ }
+ ],
+ "no-multiple-empty-lines": ["error", { "max": 2 }],
+ "no-multi-spaces": "error",
+ "simple-import-sort/imports": "error",
+ "simple-import-sort/exports": "error",
+ "import/first": "error",
+ "import/newline-after-import": "error",
+ "import/no-duplicates": "error"
+ }
+}
diff --git a/packages/SystemUI/scripts/token_alignment/.gitignore b/packages/SystemUI/scripts/token_alignment/.gitignore
new file mode 100644
index 0000000..96ce14f
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/.gitignore
@@ -0,0 +1,2 @@
+vscode
+node_modules
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/.prettierrc b/packages/SystemUI/scripts/token_alignment/.prettierrc
new file mode 100644
index 0000000..20f02f9
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/.prettierrc
@@ -0,0 +1,9 @@
+{
+ "tabWidth": 4,
+ "printWidth": 100,
+ "semi": true,
+ "singleQuote": true,
+ "bracketSameLine": true,
+ "bracketSpacing": true,
+ "arrowParens": "always"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts b/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts
new file mode 100644
index 0000000..80e075c
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts
@@ -0,0 +1,297 @@
+// Copyright 2022 Google LLC
+
+// 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.
+
+type IElementComment =
+ | { commentNode: undefined; textContent: undefined; hidden: undefined }
+ | { commentNode: Node; textContent: string; hidden: boolean };
+
+interface ITag {
+ attrs?: Record<string, string | number>;
+ tagName: string;
+}
+
+export interface INewTag extends ITag {
+ content?: string | number;
+ comment?: string;
+}
+
+export type IUpdateTag = Partial<Omit<INewTag, 'tagName'>>;
+
+export default class DOM {
+ static addEntry(containerElement: Element, tagOptions: INewTag) {
+ const doc = containerElement.ownerDocument;
+ const exists = this.alreadyHasEntry(containerElement, tagOptions);
+
+ if (exists) {
+ console.log('Ignored adding entry already available: ', exists.outerHTML);
+ return;
+ }
+
+ let insertPoint: Node | null = containerElement.lastElementChild; //.childNodes[containerElement.childNodes.length - 1];
+
+ if (!insertPoint) {
+ console.log('Ignored adding entry in empity parent: ', containerElement.outerHTML);
+ return;
+ }
+
+ const { attrs, comment, content, tagName } = tagOptions;
+
+ if (comment) {
+ const commentNode = doc.createComment(comment);
+ this.insertAfterIdented(commentNode, insertPoint);
+ insertPoint = commentNode;
+ }
+
+ const newEl = doc.createElement(tagName);
+ if (content) newEl.innerHTML = content.toString();
+ if (attrs)
+ Object.entries(attrs).forEach(([attr, value]) =>
+ newEl.setAttribute(attr, value.toString())
+ );
+ this.insertAfterIdented(newEl, insertPoint);
+
+ return true;
+ }
+
+ static insertBeforeIndented(newNode: Node, referenceNode: Node) {
+ const paddingNode = referenceNode.previousSibling;
+ const ownerDoc = referenceNode.ownerDocument;
+ const containerNode = referenceNode.parentNode;
+
+ if (!paddingNode || !ownerDoc || !containerNode) return;
+
+ const currentPadding = paddingNode.textContent || '';
+ const textNode = referenceNode.ownerDocument.createTextNode(currentPadding);
+
+ containerNode.insertBefore(newNode, referenceNode);
+ containerNode.insertBefore(textNode, newNode);
+ }
+
+ static insertAfterIdented(newNode: Node, referenceNode: Node) {
+ const paddingNode = referenceNode.previousSibling;
+ const ownerDoc = referenceNode.ownerDocument;
+ const containerNode = referenceNode.parentNode;
+
+ if (!paddingNode || !ownerDoc || !containerNode) return;
+
+ const currentPadding = paddingNode.textContent || '';
+ const textNode = ownerDoc.createTextNode(currentPadding);
+
+ containerNode.insertBefore(newNode, referenceNode.nextSibling);
+ containerNode.insertBefore(textNode, newNode);
+ }
+
+ static getElementComment(el: Element): IElementComment {
+ const commentNode = el.previousSibling?.previousSibling;
+
+ const out = { commentNode: undefined, textContent: undefined, hidden: undefined };
+
+ if (!commentNode) return out;
+
+ const textContent = commentNode.textContent || '';
+ const hidden = textContent.substring(textContent.length - 6) == '@hide ';
+
+ if (!(commentNode && commentNode.nodeName == '#comment')) return out;
+
+ return { commentNode, textContent, hidden: hidden };
+ }
+
+ static duplicateEntryWithChange(
+ templateElement: Element,
+ options: Omit<IUpdateTag, 'content'>
+ ) {
+ const exists = this.futureEntryAlreadyExist(templateElement, options);
+ if (exists) {
+ console.log('Ignored duplicating entry already available: ', exists.outerHTML);
+ return;
+ }
+
+ const { commentNode } = this.getElementComment(templateElement);
+ let insertPoint: Node = templateElement;
+
+ if (commentNode) {
+ const newComment = commentNode.cloneNode();
+ this.insertAfterIdented(newComment, insertPoint);
+ insertPoint = newComment;
+ }
+
+ const newEl = templateElement.cloneNode(true) as Element;
+ this.insertAfterIdented(newEl, insertPoint);
+
+ this.updateElement(newEl, options);
+ return true;
+ }
+
+ static replaceStringInAttributeValueOnQueried(
+ root: Element,
+ query: string,
+ attrArray: string[],
+ replaceMap: Map<string, string>
+ ): boolean {
+ let updated = false;
+ const queried = [...Array.from(root.querySelectorAll(query)), root];
+
+ queried.forEach((el) => {
+ attrArray.forEach((attr) => {
+ if (el.hasAttribute(attr)) {
+ const currentAttrValue = el.getAttribute(attr);
+
+ if (!currentAttrValue) return;
+
+ [...replaceMap.entries()].some(([oldStr, newStr]) => {
+ if (
+ currentAttrValue.length >= oldStr.length &&
+ currentAttrValue.indexOf(oldStr) ==
+ currentAttrValue.length - oldStr.length
+ ) {
+ el.setAttribute(attr, currentAttrValue.replace(oldStr, newStr));
+ updated = true;
+ return true;
+ }
+ return false;
+ });
+ }
+ });
+ });
+
+ return updated;
+ }
+
+ static updateElement(el: Element, updateOptions: IUpdateTag) {
+ const exists = this.futureEntryAlreadyExist(el, updateOptions);
+ if (exists) {
+ console.log('Ignored updating entry already available: ', exists.outerHTML);
+ return;
+ }
+
+ const { comment, attrs, content } = updateOptions;
+
+ if (comment) {
+ const { commentNode } = this.getElementComment(el);
+ if (commentNode) {
+ commentNode.textContent = comment;
+ }
+ }
+
+ if (attrs) {
+ for (const attr in attrs) {
+ const value = attrs[attr];
+
+ if (value != undefined) {
+ el.setAttribute(attr, `${value}`);
+ } else {
+ el.removeAttribute(attr);
+ }
+ }
+ }
+
+ if (content != undefined) {
+ el.innerHTML = `${content}`;
+ }
+
+ return true;
+ }
+
+ static elementToOptions(el: Element): ITag {
+ return {
+ attrs: this.getAllElementAttributes(el),
+ tagName: el.tagName,
+ };
+ }
+
+ static getAllElementAttributes(el: Element): Record<string, string> {
+ return el
+ .getAttributeNames()
+ .reduce(
+ (acc, attr) => ({ ...acc, [attr]: el.getAttribute(attr) || '' }),
+ {} as Record<string, string>
+ );
+ }
+
+ static futureEntryAlreadyExist(el: Element, updateOptions: IUpdateTag) {
+ const currentElOptions = this.elementToOptions(el);
+
+ if (!el.parentElement) {
+ console.log('Checked el has no parent');
+ process.exit();
+ }
+
+ return this.alreadyHasEntry(el.parentElement, {
+ ...currentElOptions,
+ ...updateOptions,
+ attrs: { ...currentElOptions.attrs, ...updateOptions.attrs },
+ });
+ }
+
+ static alreadyHasEntry(
+ containerElement: Element,
+ { attrs, tagName }: Pick<INewTag, 'attrs' | 'tagName'>
+ ) {
+ const qAttrs = attrs
+ ? Object.entries(attrs)
+ .map(([a, v]) => `[${a}="${v}"]`)
+ .join('')
+ : '';
+
+ return containerElement.querySelector(tagName + qAttrs);
+ }
+
+ static replaceContentTextOnQueried(
+ root: Element,
+ query: string,
+ replacePairs: Array<[string, string]>
+ ) {
+ let updated = false;
+ let queried = Array.from(root.querySelectorAll(query));
+
+ if (queried.length == 0) queried = [...Array.from(root.querySelectorAll(query)), root];
+
+ queried.forEach((el) => {
+ replacePairs.forEach(([oldStr, newStr]) => {
+ if (el.innerHTML == oldStr) {
+ el.innerHTML = newStr;
+ updated = true;
+ }
+ });
+ });
+
+ return updated;
+ }
+
+ static XMLDocToString(doc: XMLDocument) {
+ let str = '';
+
+ doc.childNodes.forEach((node) => {
+ switch (node.nodeType) {
+ case 8: // comment
+ str += `<!--${node.nodeValue}-->\n`;
+ break;
+
+ case 3: // text
+ str += node.textContent;
+ break;
+
+ case 1: // element
+ str += (node as Element).outerHTML;
+ break;
+
+ default:
+ console.log('Unhandled node type: ' + node.nodeType);
+ break;
+ }
+ });
+
+ return str;
+ }
+}
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/FileIO.ts b/packages/SystemUI/scripts/token_alignment/helpers/FileIO.ts
new file mode 100644
index 0000000..359e3ab
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/FileIO.ts
@@ -0,0 +1,112 @@
+// Copyright 2022 Google LLC
+
+// 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 { exec } from 'child_process';
+
+import { exec } from 'child_process';
+import { parse } from 'csv-parse';
+import { promises as fs } from 'fs';
+import jsdom from 'jsdom';
+
+const DOMParser = new jsdom.JSDOM('').window.DOMParser as typeof window.DOMParser;
+
+type TFileList = string[];
+
+export type TCSVRecord = Array<string | boolean | number>;
+
+class _FileIO {
+ public parser = new DOMParser();
+ public saved: string[] = [];
+
+ public loadXML = async (path: string): Promise<XMLDocument> => {
+ try {
+ const src = await this.loadFileAsText(path);
+ return this.parser.parseFromString(src, 'text/xml') as XMLDocument;
+ } catch (error) {
+ console.log(`Failed to parse XML file '${path}'.`, error);
+ process.exit();
+ }
+ };
+
+ public loadFileAsText = async (path: string): Promise<string> => {
+ try {
+ return await fs.readFile(path, { encoding: 'utf8' });
+ } catch (error) {
+ console.log(`Failed to read file '${path}'.`, error);
+ process.exit();
+ }
+ };
+
+ public saveFile = async (data: string, path: string) => {
+ try {
+ await fs.writeFile(path, data, { encoding: 'utf8' });
+ this.saved.push(path);
+ } catch (error) {
+ console.log(error);
+ console.log(`Failed to write file '${path}'.`);
+ process.exit();
+ }
+ };
+
+ public loadFileList = async (path: string): Promise<TFileList> => {
+ const src = await this.loadFileAsText(path);
+
+ try {
+ return JSON.parse(src) as TFileList;
+ } catch (error) {
+ console.log(error);
+ console.log(`Failed to parse JSON file '${path}'.`);
+ process.exit();
+ }
+ };
+
+ public loadCSV = (path: string): Promise<Array<TCSVRecord>> => {
+ return new Promise((resolve, reject) => {
+ this.loadFileAsText(path).then((src) => {
+ parse(
+ src,
+ {
+ delimiter: ' ',
+ },
+ (err, records) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+
+ resolve(records);
+ }
+ );
+ });
+ });
+ };
+
+ formatSaved = () => {
+ const cmd = `idea format ${this.saved.join(' ')}`;
+
+ exec(cmd, (error, out, stderr) => {
+ if (error) {
+ console.log(error.message);
+ return;
+ }
+
+ if (stderr) {
+ console.log(stderr);
+ return;
+ }
+
+ console.log(out);
+ });
+ };
+}
+
+export const FileIO = new _FileIO();
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/migrationList.ts b/packages/SystemUI/scripts/token_alignment/helpers/migrationList.ts
new file mode 100644
index 0000000..8d50644
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/migrationList.ts
@@ -0,0 +1,70 @@
+// Copyright 2022 Google LLC
+
+// 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 { exec } from 'child_process';
+
+import { FileIO, TCSVRecord } from './FileIO';
+import ProcessArgs from './processArgs';
+
+interface IInputMigItem {
+ migrationToken: string;
+ materialToken: string;
+ newDefaultValue?: string;
+ newComment?: string;
+}
+
+interface IAditionalKeys {
+ step: ('update' | 'duplicate' | 'add' | 'ignore')[];
+ isHidden: boolean;
+ replaceToken: string;
+}
+
+export type IMigItem = Omit<IInputMigItem, 'materialToken' | 'migrationToken'> & IAditionalKeys;
+
+export type IMigrationMap = Map<string, IMigItem>;
+
+function isMigrationRecord(record: TCSVRecord): record is string[] {
+ return !record.some((value) => typeof value != 'string') || record.length != 5;
+}
+
+export const loadMIgrationList = async function (): Promise<IMigrationMap> {
+ const out: IMigrationMap = new Map();
+ const csv = await FileIO.loadCSV('resources/migrationList.csv');
+
+ csv.forEach((record, i) => {
+ if (i == 0) return; // header
+
+ if (typeof record[0] != 'string') return;
+
+ if (!isMigrationRecord(record)) {
+ console.log(`Failed to validade CSV record as string[5].`, record);
+ process.exit();
+ }
+
+ const [originalToken, materialToken, newDefaultValue, newComment, migrationToken] = record;
+
+ if (out.has(originalToken)) {
+ console.log('Duplicated entry on Migration CSV file: ', originalToken);
+ return;
+ }
+
+ out.set(originalToken, {
+ replaceToken: ProcessArgs.isDebug ? migrationToken : materialToken,
+ ...(!!newDefaultValue && { newDefaultValue }),
+ ...(!!newComment && { newComment }),
+ step: [],
+ isHidden: false,
+ });
+ });
+
+ return new Map([...out].sort((a, b) => b[0].length - a[0].length));
+};
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/processArgs.ts b/packages/SystemUI/scripts/token_alignment/helpers/processArgs.ts
new file mode 100644
index 0000000..be0e232
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/processArgs.ts
@@ -0,0 +1,21 @@
+// Copyright 2022 Google LLC
+
+// 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 { exec } from 'child_process';
+
+const myArgs = process.argv.slice(2);
+
+const ProcessArgs = {
+ isDebug: myArgs.includes('debug'),
+};
+
+export default ProcessArgs;
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/processXML.ts b/packages/SystemUI/scripts/token_alignment/helpers/processXML.ts
new file mode 100644
index 0000000..368d4cb
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/processXML.ts
@@ -0,0 +1,102 @@
+// Copyright 2022 Google LLC
+
+// 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 { exec } from 'child_process';
+
+import DOM, { INewTag, IUpdateTag } from './DOMFuncs';
+import { FileIO } from './FileIO';
+import { IMigItem, IMigrationMap } from './migrationList';
+
+export type TResultExistingEval = ['update' | 'duplicate', IUpdateTag] | void;
+export type TResultMissingEval = INewTag | void;
+
+interface IProcessXML {
+ attr?: string;
+ containerQuery?: string;
+ evalExistingEntry?: TEvalExistingEntry;
+ evalMissingEntry?: TEvalMissingEntry;
+ hidable?: boolean;
+ path: string;
+ step: number;
+ tagName: string;
+}
+
+export type TEvalExistingEntry = (
+ attrname: string,
+ migItem: IMigItem,
+ qItem: Element
+) => TResultExistingEval;
+
+export type TEvalMissingEntry = (originalToken: string, migItem: IMigItem) => TResultMissingEval;
+
+export async function processQueriedEntries(
+ migrationMap: IMigrationMap,
+ {
+ attr = 'name',
+ containerQuery = '*',
+ evalExistingEntry,
+ path,
+ step,
+ tagName,
+ evalMissingEntry,
+ }: IProcessXML
+) {
+ const doc = await FileIO.loadXML(path);
+
+ const containerElement =
+ (containerQuery && doc.querySelector(containerQuery)) || doc.documentElement;
+
+ migrationMap.forEach((migItem, originalToken) => {
+ migItem.step[step] = 'ignore';
+
+ const queryTiems = containerElement.querySelectorAll(
+ `${tagName}[${attr}="${originalToken}"]`
+ );
+
+ if (evalMissingEntry) {
+ const addinOptions = evalMissingEntry(originalToken, migItem);
+
+ if (queryTiems.length == 0 && containerElement && addinOptions) {
+ DOM.addEntry(containerElement, addinOptions);
+ migItem.step[step] = 'add';
+ return;
+ }
+ }
+
+ if (evalExistingEntry)
+ queryTiems.forEach((qEl) => {
+ const attrName = qEl.getAttribute(attr);
+ const migItem = migrationMap.get(attrName || '');
+
+ if (!attrName || !migItem) return;
+
+ const updateOptions = evalExistingEntry(attrName, migItem, qEl);
+
+ if (!updateOptions) return;
+
+ const [processType, processOptions] = updateOptions;
+
+ switch (processType) {
+ case 'update':
+ if (DOM.updateElement(qEl, processOptions)) migItem.step[step] = 'update';
+ break;
+
+ case 'duplicate':
+ if (DOM.duplicateEntryWithChange(qEl, processOptions))
+ migItem.step[step] = 'duplicate';
+ break;
+ }
+ });
+ });
+
+ await FileIO.saveFile(doc.documentElement.outerHTML, path);
+}
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/rootPath.ts b/packages/SystemUI/scripts/token_alignment/helpers/rootPath.ts
new file mode 100644
index 0000000..2c6f632
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/rootPath.ts
@@ -0,0 +1,21 @@
+// Copyright 2022 Google LLC
+
+// 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 { exec } from 'child_process';
+
+if (!process?.env?.ANDROID_BUILD_TOP) {
+ console.log(
+ "Error: Couldn't find 'ANDROID_BUILD_TOP' environment variable. Make sure to run 'lunch' in this terminal"
+ );
+}
+
+export const repoPath = process?.env?.ANDROID_BUILD_TOP;
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/textFuncs.ts b/packages/SystemUI/scripts/token_alignment/helpers/textFuncs.ts
new file mode 100644
index 0000000..6679c5a
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/textFuncs.ts
@@ -0,0 +1,27 @@
+// Copyright 2022 Google LLC
+
+// 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 { exec } from 'child_process';
+
+export function groupReplace(src: string, replaceMap: Map<string, string>, pattern: string) {
+ const fullPattern = pattern.replace('#group#', [...replaceMap.keys()].join('|'));
+
+ const regEx = new RegExp(fullPattern, 'g');
+
+ ''.replace;
+
+ return src.replace(regEx, (...args) => {
+ //match, ...matches, offset, string, groups
+ const [match, key] = args as string[];
+ return match.replace(key, replaceMap.get(key) || '');
+ });
+}
diff --git a/packages/SystemUI/scripts/token_alignment/index.ts b/packages/SystemUI/scripts/token_alignment/index.ts
new file mode 100644
index 0000000..1b15e48
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/index.ts
@@ -0,0 +1,240 @@
+// Copyright 2022 Google LLC
+
+// 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 { exec } from 'child_process';
+
+import DOM from './helpers/DOMFuncs';
+import { FileIO } from './helpers/FileIO';
+import { loadMIgrationList } from './helpers/migrationList';
+import { processQueriedEntries, TEvalExistingEntry } from './helpers/processXML';
+import { repoPath } from './helpers/rootPath';
+import { groupReplace } from './helpers/textFuncs';
+
+async function init() {
+ const migrationMap = await loadMIgrationList();
+ const basePath = `${repoPath}/../tm-qpr-dev/frameworks/base/core/res/res/values/`;
+
+ await processQueriedEntries(migrationMap, {
+ containerQuery: 'declare-styleable[name="Theme"]',
+ hidable: true,
+ path: `${basePath}attrs.xml`,
+ step: 0,
+ tagName: 'attr',
+ evalExistingEntry: (_attrValue, migItem, qItem) => {
+ const { hidden, textContent: currentComment } = DOM.getElementComment(qItem);
+
+ if (hidden) migItem.isHidden = hidden;
+
+ const { newComment } = migItem;
+ return [
+ hidden ? 'update' : 'duplicate',
+ {
+ attrs: { name: migItem.replaceToken },
+ ...(newComment
+ ? { comment: `${newComment} @hide ` }
+ : currentComment
+ ? { comment: hidden ? currentComment : `${currentComment} @hide ` }
+ : {}),
+ },
+ ];
+ },
+ evalMissingEntry: (_originalToken, { replaceToken, newComment }) => {
+ return {
+ tagName: 'attr',
+ attrs: {
+ name: replaceToken,
+ format: 'color',
+ },
+ comment: `${newComment} @hide `,
+ };
+ },
+ });
+
+ // only update all existing entries
+ await processQueriedEntries(migrationMap, {
+ tagName: 'item',
+ path: `${basePath}themes_device_defaults.xml`,
+ containerQuery: 'resources',
+ step: 2,
+ evalExistingEntry: (_attrValue, { isHidden, replaceToken, step }, _qItem) => {
+ if (step[0] != 'ignore')
+ return [
+ isHidden ? 'update' : 'duplicate',
+ {
+ attrs: { name: replaceToken },
+ },
+ ];
+ },
+ });
+
+ // add missing entries on specific container
+ await processQueriedEntries(migrationMap, {
+ tagName: 'item',
+ path: `${basePath}themes_device_defaults.xml`,
+ containerQuery: 'resources style[parent="Theme.Material"]',
+ step: 3,
+ evalMissingEntry: (originalToken, { newDefaultValue, replaceToken }) => {
+ return {
+ tagName: 'item',
+ content: newDefaultValue,
+ attrs: {
+ name: replaceToken,
+ },
+ };
+ },
+ });
+
+ const evalExistingEntry: TEvalExistingEntry = (_attrValue, { replaceToken, step }, _qItem) => {
+ if (step[0] == 'update')
+ return [
+ 'update',
+ {
+ attrs: { name: replaceToken },
+ },
+ ];
+ };
+
+ await processQueriedEntries(migrationMap, {
+ tagName: 'item',
+ containerQuery: 'resources',
+ path: `${basePath}../values-night/themes_device_defaults.xml`,
+ step: 4,
+ evalExistingEntry,
+ });
+
+ await processQueriedEntries(migrationMap, {
+ tagName: 'java-symbol',
+ path: `${basePath}symbols.xml`,
+ containerQuery: 'resources',
+ step: 5,
+ evalExistingEntry,
+ });
+
+ // update attributes on tracked XML files
+ {
+ const searchAttrs = [
+ 'android:color',
+ 'android:indeterminateTint',
+ 'app:tint',
+ 'app:backgroundTint',
+ 'android:background',
+ 'android:tint',
+ 'android:drawableTint',
+ 'android:textColor',
+ 'android:fillColor',
+ 'android:startColor',
+ 'android:endColor',
+ 'name',
+ 'ns1:color',
+ ];
+
+ const filtered = new Map(
+ [...migrationMap]
+ .filter(([_originalToken, { step }]) => step[0] == 'update')
+ .map(([originalToken, { replaceToken }]) => [originalToken, replaceToken])
+ );
+
+ const query =
+ searchAttrs.map((str) => `*[${str}]`).join(',') +
+ [...filtered.keys()].map((originalToken) => `item[name*="${originalToken}"]`).join(',');
+
+ const trackedFiles = await FileIO.loadFileList(
+ `${__dirname}/resources/whitelist/xmls1.json`
+ );
+
+ const promises = trackedFiles.map(async (locaFilePath) => {
+ const filePath = `${repoPath}/${locaFilePath}`;
+
+ const doc = await FileIO.loadXML(filePath);
+ const docUpdated = DOM.replaceStringInAttributeValueOnQueried(
+ doc.documentElement,
+ query,
+ searchAttrs,
+ filtered
+ );
+ if (docUpdated) {
+ await FileIO.saveFile(DOM.XMLDocToString(doc), filePath);
+ } else {
+ console.warn(`Failed to update tracked file: '${locaFilePath}'`);
+ }
+ });
+ await Promise.all(promises);
+ }
+
+ // updates tag content on tracked files
+ {
+ const searchPrefixes = ['?android:attr/', '?androidprv:attr/'];
+ const filtered = searchPrefixes
+ .reduce<Array<[string, string]>>((acc, prefix) => {
+ return [
+ ...acc,
+ ...[...migrationMap.entries()]
+ .filter(([_originalToken, { step }]) => step[0] == 'update')
+ .map(
+ ([originalToken, { replaceToken }]) =>
+ [`${prefix}${originalToken}`, `${prefix}${replaceToken}`] as [
+ string,
+ string
+ ]
+ ),
+ ];
+ }, [])
+ .sort((a, b) => b[0].length - a[0].length);
+
+ const trackedFiles = await FileIO.loadFileList(
+ `${__dirname}/resources/whitelist/xmls2.json`
+ );
+
+ const promises = trackedFiles.map(async (locaFilePath) => {
+ const filePath = `${repoPath}/${locaFilePath}`;
+ const doc = await FileIO.loadXML(filePath);
+ const docUpdated = DOM.replaceContentTextOnQueried(
+ doc.documentElement,
+ 'item, color',
+ filtered
+ );
+ if (docUpdated) {
+ await FileIO.saveFile(DOM.XMLDocToString(doc), filePath);
+ } else {
+ console.warn(`Failed to update tracked file: '${locaFilePath}'`);
+ }
+ });
+ await Promise.all(promises);
+ }
+
+ // replace imports on Java / Kotlin
+ {
+ const replaceMap = new Map(
+ [...migrationMap.entries()]
+ .filter(([_originalToken, { step }]) => step[0] == 'update')
+ .map(
+ ([originalToken, { replaceToken }]) =>
+ [originalToken, replaceToken] as [string, string]
+ )
+ .sort((a, b) => b[0].length - a[0].length)
+ );
+
+ const trackedFiles = await FileIO.loadFileList(
+ `${__dirname}/resources/whitelist/java.json`
+ );
+
+ const promises = trackedFiles.map(async (locaFilePath) => {
+ const filePath = `${repoPath}/${locaFilePath}`;
+ const fileContent = await FileIO.loadFileAsText(filePath);
+ const str = groupReplace(fileContent, replaceMap, 'R.attr.(#group#)(?![a-zA-Z])');
+ await FileIO.saveFile(str, filePath);
+ });
+ await Promise.all(promises);
+ }
+}
+
+init();
diff --git a/packages/SystemUI/scripts/token_alignment/package-lock.json b/packages/SystemUI/scripts/token_alignment/package-lock.json
new file mode 100644
index 0000000..da9edb3
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/package-lock.json
@@ -0,0 +1,3356 @@
+{
+ "name": "token_alignment",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "csv-parse": "^5.3.3",
+ "high5": "^1.0.0",
+ "jsdom": "^20.0.3"
+ },
+ "devDependencies": {
+ "@types/jsdom": "^20.0.1",
+ "@types/node": "^18.11.18",
+ "@typescript-eslint/eslint-plugin": "^5.48.0",
+ "eslint-config-prettier": "^8.6.0",
+ "eslint-plugin-import": "^2.26.0",
+ "eslint-plugin-prettier": "^4.2.1",
+ "eslint-plugin-simple-import-sort": "^8.0.0",
+ "ts-node": "^10.9.1",
+ "typescript": "^4.9.4"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
+ "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.4.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.8",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
+ "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+ "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
+ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
+ "dev": true
+ },
+ "node_modules/@types/jsdom": {
+ "version": "20.0.1",
+ "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
+ "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "@types/tough-cookie": "*",
+ "parse5": "^7.0.0"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.11",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
+ "dev": true
+ },
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "18.11.18",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
+ "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
+ "dev": true
+ },
+ "node_modules/@types/semver": {
+ "version": "7.3.13",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
+ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==",
+ "dev": true
+ },
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
+ "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==",
+ "dev": true
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "5.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz",
+ "integrity": "sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "5.48.0",
+ "@typescript-eslint/type-utils": "5.48.0",
+ "@typescript-eslint/utils": "5.48.0",
+ "debug": "^4.3.4",
+ "ignore": "^5.2.0",
+ "natural-compare-lite": "^1.4.0",
+ "regexpp": "^3.2.0",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^5.0.0",
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "5.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz",
+ "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "5.48.0",
+ "@typescript-eslint/types": "5.48.0",
+ "@typescript-eslint/typescript-estree": "5.48.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "5.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz",
+ "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.48.0",
+ "@typescript-eslint/visitor-keys": "5.48.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "5.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz",
+ "integrity": "sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "5.48.0",
+ "@typescript-eslint/utils": "5.48.0",
+ "debug": "^4.3.4",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "5.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz",
+ "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "5.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz",
+ "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.48.0",
+ "@typescript-eslint/visitor-keys": "5.48.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "5.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.0.tgz",
+ "integrity": "sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.9",
+ "@types/semver": "^7.3.12",
+ "@typescript-eslint/scope-manager": "5.48.0",
+ "@typescript-eslint/types": "5.48.0",
+ "@typescript-eslint/typescript-estree": "5.48.0",
+ "eslint-scope": "^5.1.1",
+ "eslint-utils": "^3.0.0",
+ "semver": "^7.3.7"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "5.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz",
+ "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.48.0",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/abab": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
+ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="
+ },
+ "node_modules/acorn": {
+ "version": "8.8.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
+ "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-globals": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
+ "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+ "dependencies": {
+ "acorn": "^8.1.0",
+ "acorn-walk": "^8.0.2"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peer": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
+ "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz",
+ "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssom": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
+ "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="
+ },
+ "node_modules/cssstyle": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
+ "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+ "dependencies": {
+ "cssom": "~0.3.6"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cssstyle/node_modules/cssom": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
+ },
+ "node_modules/csv-parse": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.3.3.tgz",
+ "integrity": "sha512-kEWkAPleNEdhFNkHQpFHu9RYPogsFj3dx6bCxL847fsiLgidzWg0z/O0B1kVWMJUc5ky64zGp18LX2T3DQrOfw=="
+ },
+ "node_modules/data-urls": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
+ "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+ "dependencies": {
+ "abab": "^2.0.6",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
+ },
+ "node_modules/define-properties": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
+ "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
+ "dev": true,
+ "dependencies": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/domexception": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
+ "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
+ "dependencies": {
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
+ "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.0.tgz",
+ "integrity": "sha512-GUGtW7eXQay0c+PRq0sGIKSdaBorfVqsCMhGHo4elP7YVqZu9nCZS4UkK4gv71gOWNMra/PaSKD3ao1oWExO0g==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.0",
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.1.3",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.4",
+ "is-array-buffer": "^3.0.0",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.2",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
+ "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3",
+ "has": "^1.0.3",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
+ "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/escodegen": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz",
+ "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2",
+ "optionator": "^0.8.1"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.31.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz",
+ "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@eslint/eslintrc": "^1.4.1",
+ "@humanwhocodes/config-array": "^0.11.8",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.1",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.4.0",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-sdsl": "^4.1.4",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "regexpp": "^3.2.0",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz",
+ "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==",
+ "dev": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz",
+ "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7",
+ "resolve": "^1.20.0"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz",
+ "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.26.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz",
+ "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.4",
+ "array.prototype.flat": "^1.2.5",
+ "debug": "^2.6.9",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.6",
+ "eslint-module-utils": "^2.7.3",
+ "has": "^1.0.3",
+ "is-core-module": "^2.8.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.values": "^1.1.5",
+ "resolve": "^1.22.0",
+ "tsconfig-paths": "^3.14.1"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
+ "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==",
+ "dev": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.28.0",
+ "prettier": ">=2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-simple-import-sort": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-8.0.0.tgz",
+ "integrity": "sha512-bXgJQ+lqhtQBCuWY/FUWdB27j4+lqcvXv5rUARkzbeWLwea+S5eBZEQrhnO+WgX3ZoJHVj0cn943iyXwByHHQw==",
+ "dev": true,
+ "peerDependencies": {
+ "eslint": ">=5.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+ "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "engines": {
+ "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=5"
+ }
+ },
+ "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/eslint/node_modules/optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/eslint/node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/eslint/node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
+ "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "acorn": "^8.8.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/fast-diff": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.2.12",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
+ "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
+ },
+ "node_modules/fastq": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.3"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+ "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.0",
+ "functions-have-names": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
+ "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
+ "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.19.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz",
+ "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
+ "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+ "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
+ "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/high5": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/high5/-/high5-1.0.0.tgz",
+ "integrity": "sha512-xucW/5M1hd+p6bj530wtRSKwqUQrgiIgOWepi4Di9abkonZaxhTDf0zrqqraxfZSXBcFSuc1/WVGBIlqSe1Hdw==",
+ "dependencies": {
+ "entities": "1.0"
+ }
+ },
+ "node_modules/high5/node_modules/entities": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
+ "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ=="
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
+ "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+ "dependencies": {
+ "whatwg-encoding": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/internal-slot": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz",
+ "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz",
+ "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "is-typed-array": "^1.1.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+ "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+ "dev": true,
+ "dependencies": {
+ "has-bigints": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+ "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+ "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
+ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="
+ },
+ "node_modules/is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
+ "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/js-sdsl": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
+ "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==",
+ "dev": true,
+ "peer": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsdom": {
+ "version": "20.0.3",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
+ "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
+ "dependencies": {
+ "abab": "^2.0.6",
+ "acorn": "^8.8.1",
+ "acorn-globals": "^7.0.0",
+ "cssom": "^0.5.0",
+ "cssstyle": "^2.3.0",
+ "data-urls": "^3.0.2",
+ "decimal.js": "^10.4.2",
+ "domexception": "^4.0.0",
+ "escodegen": "^2.0.0",
+ "form-data": "^4.0.0",
+ "html-encoding-sniffer": "^3.0.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.1",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.2",
+ "parse5": "^7.1.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^4.1.2",
+ "w3c-xmlserializer": "^4.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^2.0.0",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0",
+ "ws": "^8.11.0",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "canvas": "^2.5.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+ "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
+ "dependencies": {
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
+ "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/natural-compare-lite": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
+ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
+ "dev": true
+ },
+ "node_modules/nwsapi": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
+ "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw=="
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
+ "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
+ "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+ "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+ "dependencies": {
+ "deep-is": "~0.1.3",
+ "fast-levenshtein": "~2.0.6",
+ "levn": "~0.3.0",
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2",
+ "word-wrap": "~1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
+ "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+ "dependencies": {
+ "entities": "^4.4.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+ "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz",
+ "integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/psl": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
+ },
+ "node_modules/punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
+ "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "functions-have-names": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
+ },
+ "node_modules/resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
+ "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "is-regex": "^1.1.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.3.8",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+ "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
+ "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "dependencies": {
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.1",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+ "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+ "dev": true,
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
+ "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.1",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "node_modules/tsutils": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^1.8.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+ "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
+ "dependencies": {
+ "prelude-ls": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
+ "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "is-typed-array": "^1.1.9"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "4.9.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
+ "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
+ "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+ "dependencies": {
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+ "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "dependencies": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+ "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "dev": true,
+ "dependencies": {
+ "is-bigint": "^1.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "is-symbol": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
+ "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/ws": {
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
+ "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/scripts/token_alignment/package.json b/packages/SystemUI/scripts/token_alignment/package.json
new file mode 100644
index 0000000..2e63668
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/package.json
@@ -0,0 +1,22 @@
+{
+ "dependencies": {
+ "csv-parse": "^5.3.3",
+ "high5": "^1.0.0",
+ "jsdom": "^20.0.3"
+ },
+ "devDependencies": {
+ "@types/jsdom": "^20.0.1",
+ "@types/node": "^18.11.18",
+ "@typescript-eslint/eslint-plugin": "^5.48.0",
+ "eslint-config-prettier": "^8.6.0",
+ "eslint-plugin-import": "^2.26.0",
+ "eslint-plugin-prettier": "^4.2.1",
+ "eslint-plugin-simple-import-sort": "^8.0.0",
+ "ts-node": "^10.9.1",
+ "typescript": "^4.9.4"
+ },
+ "scripts": {
+ "main": "ts-node ./index.ts",
+ "resetRepo": "repo forall -j100 -c 'git restore .'"
+ }
+}
diff --git a/packages/SystemUI/scripts/token_alignment/resources/migrationList.csv b/packages/SystemUI/scripts/token_alignment/resources/migrationList.csv
new file mode 100644
index 0000000..4111bb3
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/resources/migrationList.csv
@@ -0,0 +1,16 @@
+android material newDefaultValue newComment migrationToken
+colorAccentPrimaryVariant colorPrimaryContainer MigTok02
+colorAccentSecondaryVariant colorSecondaryContainer MigTok04
+colorAccentTertiary colorTertiary MigTok05
+colorAccentTertiaryVariant colorTertiaryContainer MigTok06
+colorBackground colorSurfaceContainer MigTok07
+colorSurface colorSurfaceContainer MigTok08
+colorSurfaceHeader colorSurfaceContainerHighest MigTok09
+colorSurfaceHighlight colorSurfaceBright MigTok10
+colorSurfaceVariant colorSurfaceContainerHigh MigTok11
+textColorOnAccent colorOnPrimary MigTok12
+textColorPrimary colorOnSurface MigTok13
+textColorPrimaryInverse colorOnShadeInactive MigTok14
+textColorSecondary colorOnSurfaceVariant MigTok15
+textColorSecondaryInverse colorOnShadeInactiveVariant MigTok16
+textColorTertiary colorOutline MigTok17
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/resources/whitelist/java.json b/packages/SystemUI/scripts/token_alignment/resources/whitelist/java.json
new file mode 100644
index 0000000..7f55b2d
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/resources/whitelist/java.json
@@ -0,0 +1,30 @@
+[
+ "frameworks/base/core/java/android/app/Notification.java",
+ "packages/apps/Settings/src/com/android/settings/dashboard/profileselector/UserAdapter.java",
+ "packages/apps/Settings/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java",
+ "frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt",
+ "frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt",
+ "packages/apps/WallpaperPicker2/src/com/android/wallpaper/picker/PreviewFragment.java",
+ "packages/apps/WallpaperPicker2/src/com/android/wallpaper/picker/CategorySelectorFragment.java",
+ "packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt",
+ "frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java",
+ "vendor/unbundled_google/packages/NexusLauncher/src/com/google/android/apps/nexuslauncher/customize/WallpaperCarouselView.java",
+ "vendor/unbundled_google/packages/NexusLauncher/src/com/google/android/apps/nexuslauncher/quickstep/TaskOverlayFactoryImpl.java",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserViewBinder.kt",
+ "frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java",
+ "frameworks/base/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java",
+ "frameworks/base/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java"
+]
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls1.json b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls1.json
new file mode 100644
index 0000000..1e59773
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls1.json
@@ -0,0 +1,184 @@
+[
+ "frameworks/base/core/res/res/color/resolver_profile_tab_selected_bg.xml",
+ "frameworks/base/core/res/res/color-night/resolver_profile_tab_selected_bg.xml",
+ "frameworks/base/core/res/res/drawable/autofill_bottomsheet_background.xml",
+ "frameworks/base/core/res/res/drawable/btn_outlined.xml",
+ "frameworks/base/core/res/res/drawable/btn_tonal.xml",
+ "frameworks/base/core/res/res/drawable/chooser_action_button_bg.xml",
+ "frameworks/base/core/res/res/drawable/chooser_row_layer_list.xml",
+ "frameworks/base/core/res/res/drawable/resolver_outlined_button_bg.xml",
+ "frameworks/base/core/res/res/drawable/resolver_profile_tab_bg.xml",
+ "frameworks/base/core/res/res/drawable/toast_frame.xml",
+ "frameworks/base/core/res/res/drawable/work_widget_mask_view_background.xml",
+ "frameworks/base/core/res/res/layout/app_language_picker_current_locale_item.xml",
+ "frameworks/base/core/res/res/layout/app_language_picker_system_current.xml",
+ "frameworks/base/core/res/res/layout/autofill_save.xml",
+ "frameworks/base/core/res/res/layout/chooser_grid.xml",
+ "frameworks/base/core/res/res/layout/user_switching_dialog.xml",
+ "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml",
+ "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_right_bk.xml",
+ "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_bk.xml",
+ "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml",
+ "packages/modules/IntentResolver/java/res/drawable/chooser_action_button_bg.xml",
+ "packages/modules/IntentResolver/java/res/drawable/chooser_row_layer_list.xml",
+ "packages/modules/IntentResolver/java/res/drawable/resolver_outlined_button_bg.xml",
+ "packages/modules/IntentResolver/java/res/drawable/resolver_profile_tab_bg.xml",
+ "packages/modules/IntentResolver/java/res/layout/chooser_grid.xml",
+ "frameworks/base/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml",
+ "frameworks/base/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_bg.xml",
+ "frameworks/base/libs/WindowManager/Shell/res/drawable/bubble_stack_user_education_bg.xml",
+ "frameworks/base/libs/WindowManager/Shell/res/drawable/bubble_stack_user_education_bg_rtl.xml",
+ "vendor/unbundled_google/packages/SystemUIGoogle/bcsmartspace/res/drawable/bg_smartspace_combination_sub_card.xml",
+ "vendor/unbundled_google/packages/SystemUIGoogle/bcsmartspace/res/layout/smartspace_combination_sub_card.xml",
+ "packages/apps/Launcher3/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml",
+ "packages/apps/Launcher3/res/drawable/rounded_action_button.xml",
+ "packages/apps/Launcher3/res/drawable/work_card.xml",
+ "packages/apps/Nfc/res/color/nfc_icon.xml",
+ "packages/apps/Nfc/res/color-night/nfc_icon.xml",
+ "packages/apps/Launcher3/quickstep/res/drawable/bg_overview_clear_all_button.xml",
+ "packages/apps/Launcher3/quickstep/res/drawable/bg_sandbox_feedback.xml",
+ "packages/apps/Launcher3/quickstep/res/drawable/bg_wellbeing_toast.xml",
+ "packages/apps/Launcher3/quickstep/res/drawable/button_taskbar_edu_bordered.xml",
+ "packages/apps/Launcher3/quickstep/res/drawable/button_taskbar_edu_colored.xml",
+ "packages/apps/Launcher3/quickstep/res/drawable/split_instructions_background.xml",
+ "packages/apps/Launcher3/quickstep/res/drawable/task_menu_item_bg.xml",
+ "packages/apps/Launcher3/quickstep/res/layout/digital_wellbeing_toast.xml",
+ "packages/apps/Launcher3/quickstep/res/layout/split_instructions_view.xml",
+ "packages/apps/Launcher3/quickstep/res/layout/taskbar_edu.xml",
+ "frameworks/base/packages/SettingsLib/res/drawable/broadcast_dialog_btn_bg.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/color/share_target_text.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/all_apps_tab_background_selected.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/all_apps_tabs_background.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/arrow_tip_view_bg.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/button_bg.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/shortcut_halo.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/surface.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/color-night-v31/all_apps_tab_background_selected.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/color-night-v31/surface.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/drawable/bg_pin_keyboard_snackbar_accept_button.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/drawable/bg_search_edu_preferences_button.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/drawable/circle_accentprimary_32dp.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/drawable/ic_search.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/drawable/ic_suggest_icon_background.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/drawable/share_target_background.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/drawable/sticky_snackbar_accept_btn_background.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/drawable/sticky_snackbar_background.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/drawable/sticky_snackbar_dismiss_btn_background.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/drawable/tall_card_btn_background.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/layout/section_header.xml",
+ "packages/apps/Settings/res/color/dream_card_color_state_list.xml",
+ "packages/apps/Settings/res/color/dream_card_icon_color_state_list.xml",
+ "packages/apps/Settings/res/color/dream_card_text_color_state_list.xml",
+ "packages/apps/Settings/res/drawable/accessibility_text_reading_preview.xml",
+ "packages/apps/Settings/res/drawable/broadcast_button_outline.xml",
+ "packages/apps/Settings/res/drawable/button_border_selected.xml",
+ "packages/apps/Settings/res/drawable/dream_preview_rounded_bg.xml",
+ "packages/apps/Settings/res/drawable/rounded_bg.xml",
+ "packages/apps/Settings/res/drawable/sim_confirm_dialog_btn_outline.xml",
+ "packages/apps/Settings/res/drawable/user_select_background.xml",
+ "packages/apps/Settings/res/drawable/volume_dialog_button_background_outline.xml",
+ "packages/apps/Settings/res/drawable/volume_dialog_button_background_solid.xml",
+ "packages/apps/Settings/res/layout/dream_preview_button.xml",
+ "packages/apps/Settings/res/layout/qrcode_scanner_fragment.xml",
+ "frameworks/base/packages/SystemUI/res/values-television/styles.xml",
+ "frameworks/base/packages/SystemUI/res/color/media_player_album_bg.xml",
+ "frameworks/base/packages/SystemUI/res/color/media_player_outline_button_bg.xml",
+ "frameworks/base/packages/SystemUI/res/color/media_player_solid_button_bg.xml",
+ "frameworks/base/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml",
+ "frameworks/base/packages/SystemUI/res/color/settingslib_state_on.xml",
+ "frameworks/base/packages/SystemUI/res/color/settingslib_track_off.xml",
+ "frameworks/base/packages/SystemUI/res/color/settingslib_track_on.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/accessibility_floating_tooltip_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/action_chip_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/action_chip_container_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/availability_dot_10dp.xml",
+ "frameworks/base/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml",
+ "frameworks/base/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_item_selected_bg.xml",
+ "frameworks/base/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_popup_bg.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/broadcast_dialog_btn_bg.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/fgs_dot.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/fingerprint_bg.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/ic_avatar_with_badge.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml",
+ "frameworks/base/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml",
+ "frameworks/base/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/logout_button_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/media_ttt_chip_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/media_ttt_chip_background_receiver.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/media_ttt_undo_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/notif_footer_btn_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/notification_guts_bg.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/notification_material_bg.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/overlay_badge_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/overlay_border.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/overlay_button_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/overlay_cancel.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/people_space_messages_count_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/people_tile_status_scrim.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/people_tile_suppressed_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/qs_media_outline_button.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/qs_media_solid_button.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/rounded_bg_full.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/screenrecord_button_background_solid.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/screenrecord_spinner_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/screenshot_edit_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/volume_background_bottom.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/volume_background_top.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/volume_background_top_rounded.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/volume_row_rounded_background.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/volume_row_seekbar.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/wallet_action_button_bg.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/wallet_app_button_bg.xml",
+ "frameworks/base/packages/SystemUI/res/drawable/wallet_empty_state_bg.xml",
+ "frameworks/base/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml",
+ "frameworks/base/packages/SystemUI/res/layout/chipbar.xml",
+ "frameworks/base/packages/SystemUI/res/layout/chipbar.xml",
+ "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay.xml",
+ "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay.xml",
+ "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml",
+ "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml",
+ "frameworks/base/packages/SystemUI/res/layout/internet_connectivity_dialog.xml",
+ "frameworks/base/packages/SystemUI/res/layout/notification_snooze.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_space_tile_view.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_large_with_content.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_medium_with_content.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml",
+ "frameworks/base/packages/SystemUI/res/layout/chipbar.xml",
+ "frameworks/base/packages/SystemUI/res/layout/notification_snooze.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_small.xml",
+ "frameworks/base/packages/SystemUI/res/layout/people_tile_small_horizontal.xml",
+ "frameworks/base/packages/SystemUI/res/layout/screen_share_dialog.xml",
+ "frameworks/base/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml",
+ "frameworks/base/packages/SystemUI/res/layout/user_switcher_fullscreen.xml",
+ "frameworks/base/packages/SystemUI/res/layout/user_switcher_fullscreen.xml",
+ "frameworks/base/packages/SystemUI/res/layout/wallet_empty_state.xml",
+ "frameworks/base/packages/SystemUI/res/layout/wallet_fullscreen.xml",
+ "frameworks/base/packages/SystemUI/res/layout/wallet_fullscreen.xml",
+ "frameworks/base/packages/SystemUI/res/layout/chipbar.xml",
+ "frameworks/base/packages/SystemUI/res/layout/notification_snooze.xml",
+ "vendor/unbundled_google/packages/SystemUIGoogle/res/drawable/columbus_chip_background_raw.xml",
+ "vendor/unbundled_google/packages/SystemUIGoogle/res/drawable/columbus_chip_background_raw.xml",
+ "vendor/unbundled_google/packages/SystemUIGoogle/res/drawable/columbus_dialog_background.xml",
+ "vendor/unbundled_google/packages/SystemUIGoogle/res/layout/columbus_target_request_dialog.xml",
+ "vendor/unbundled_google/packages/SettingsGoogle/res/color/dream_card_suw_color_state_list.xml",
+ "vendor/unbundled_google/packages/SettingsGoogle/res/drawable/dream_item_suw_rounded_bg.xml"
+]
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls2.json b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls2.json
new file mode 100644
index 0000000..20a7c76
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls2.json
@@ -0,0 +1,13 @@
+[
+ "vendor/google/nexus_overlay/PixelDocumentsUIGoogleOverlay/res/values-v31/themes.xml",
+ "vendor/google/nexus_overlay/PixelDocumentsUIGoogleOverlay/res/values-night-v31/themes.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/values/colors.xml",
+ "vendor/unbundled_google/packages/NexusLauncher/res/values/styles.xml",
+ "packages/apps/Settings/res/values-night/colors.xml",
+ "packages/apps/Settings/res/values/colors.xml",
+ "packages/apps/Settings/res/values/styles.xml",
+ "frameworks/base/packages/SystemUI/res-keyguard/values/styles.xml",
+ "frameworks/base/packages/SystemUI/res/values/styles.xml",
+ "vendor/unbundled_google/packages/SettingsGoogle/res/values/styles.xml",
+ "vendor/unbundled_google/packages/SettingsGoogle/res/values/styles.xml"
+]
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/tsconfig.json b/packages/SystemUI/scripts/token_alignment/tsconfig.json
new file mode 100644
index 0000000..20c7321
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/tsconfig.json
@@ -0,0 +1,103 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig to read more about this file */
+
+ /* Projects */
+ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+
+ /* Language and Environment */
+ "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+ // "jsxFragmentFactory": "", /* Speciffy the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
+
+ /* Modules */
+ "module": "commonjs", /* Specify what module code is generated. */
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ "typeRoots": ["../node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
+ "resolveJsonModule": true, /* Enable importing .json files. */
+ // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
+
+ /* JavaScript Support */
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+ /* Emit */
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+ // "outDir": "./", /* Specify an output folder for all emitted files. */
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+
+ /* Type Checking */
+ "strict": true, /* Enable all strict type-checking options. */
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
+ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ }
+}
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 766266d..037a71e 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
@@ -112,7 +112,8 @@
public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25;
// Freeform windows are showing in desktop mode
public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
-
+ // Device dreaming state
+ public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -141,7 +142,8 @@
SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
SYSUI_STATE_IMMERSIVE_MODE,
SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING,
- SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE
+ SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE,
+ SYSUI_STATE_DEVICE_DREAMING
})
public @interface SystemUiStateFlags {}
@@ -179,6 +181,7 @@
str.add((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0 ? "vis_win_showing" : "");
str.add((flags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0
? "freeform_active_in_desktop_mode" : "");
+ str.add((flags & SYSUI_STATE_DEVICE_DREAMING) != 0 ? "device_dreaming" : "");
return str.toString();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index e4f85db..9f07a20 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -92,6 +92,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.settingslib.Utils;
+import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -235,14 +236,6 @@
}
updateChildren(0 /* translationY */, 1f /* alpha */);
}
-
- private void updateChildren(int translationY, float alpha) {
- for (int i = 0; i < KeyguardSecurityContainer.this.getChildCount(); ++i) {
- View child = KeyguardSecurityContainer.this.getChildAt(i);
- child.setTranslationY(translationY);
- child.setAlpha(alpha);
- }
- }
};
private final OnBackAnimationCallback mBackCallback = new OnBackAnimationCallback() {
@@ -594,6 +587,7 @@
* This will run when the bouncer shows in all cases except when the user drags the bouncer up.
*/
public void startAppearAnimation(SecurityMode securityMode) {
+ updateChildren(0 /* translationY */, 1f /* alpha */);
mViewMode.startAppearAnimation(securityMode);
}
@@ -777,6 +771,14 @@
setScaleY(scale);
}
+ private void updateChildren(int translationY, float alpha) {
+ for (int i = 0; i < getChildCount(); ++i) {
+ View child = getChildAt(i);
+ child.setTranslationY(translationY);
+ child.setAlpha(alpha);
+ }
+ }
+
/**
* Enscapsulates the differences between bouncer modes for the container.
*/
@@ -998,8 +1000,10 @@
private Drawable findUserIcon(int userId) {
Bitmap userIcon = UserManager.get(mView.getContext()).getUserIcon(userId);
if (userIcon != null) {
- return new BitmapDrawable(userIcon);
+ return CircleFramedDrawable.getInstance(mView.getContext(),
+ userIcon);
}
+
return UserIcons.getDefaultUserIcon(mResources, userId, false);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index a9695dd..54886c3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -26,7 +26,6 @@
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
import static android.hardware.biometrics.BiometricConstants.LockoutMode;
-import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricSourceType.FACE;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
@@ -352,7 +351,6 @@
private final Executor mBackgroundExecutor;
private final SensorPrivacyManager mSensorPrivacyManager;
private final ActiveUnlockConfig mActiveUnlockConfig;
- private final PowerManager mPowerManager;
private final IDreamManager mDreamManager;
private final TelephonyManager mTelephonyManager;
@Nullable
@@ -360,7 +358,6 @@
@Nullable
private final FaceManager mFaceManager;
private final LockPatternUtils mLockPatternUtils;
- private final boolean mWakeOnFingerprintAcquiredStart;
@VisibleForTesting
@DevicePostureController.DevicePostureInt
protected int mConfigFaceAuthSupportedPosture;
@@ -884,11 +881,6 @@
private void handleFingerprintAcquired(
@BiometricFingerprintConstants.FingerprintAcquired int acquireInfo) {
Assert.isMainThread();
- if (mWakeOnFingerprintAcquiredStart && acquireInfo == FINGERPRINT_ACQUIRED_START) {
- mPowerManager.wakeUp(
- SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC,
- "com.android.systemui.keyguard:FINGERPRINT_ACQUIRED_START");
- }
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1537,6 +1529,7 @@
@VisibleForTesting
void setAssistantVisible(boolean assistantVisible) {
mAssistantVisible = assistantVisible;
+ mLogger.logAssistantVisible(mAssistantVisible);
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED);
if (mAssistantVisible) {
@@ -1944,6 +1937,11 @@
}
}
mGoingToSleep = true;
+ // Resetting assistant visibility state as the device is going to sleep now.
+ // TaskStackChangeListener gets triggered a little late when we transition to AoD,
+ // which results in face auth running once on AoD.
+ mAssistantVisible = false;
+ mLogger.d("Started going to sleep, mGoingToSleep=true, mAssistantVisible=false");
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_GOING_TO_SLEEP);
}
@@ -2049,7 +2047,6 @@
UiEventLogger uiEventLogger,
// This has to be a provider because SessionTracker depends on KeyguardUpdateMonitor :(
Provider<SessionTracker> sessionTrackerProvider,
- PowerManager powerManager,
TrustManager trustManager,
SubscriptionManager subscriptionManager,
UserManager userManager,
@@ -2086,7 +2083,6 @@
mLogger = logger;
mUiEventLogger = uiEventLogger;
mSessionTrackerProvider = sessionTrackerProvider;
- mPowerManager = powerManager;
mTrustManager = trustManager;
mUserManager = userManager;
mDreamManager = dreamManager;
@@ -2097,8 +2093,6 @@
mFpm = fingerprintManager;
mFaceManager = faceManager;
mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
- mWakeOnFingerprintAcquiredStart = context.getResources()
- .getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start);
mFaceAcquiredInfoIgnoreList = Arrays.stream(
mContext.getResources().getIntArray(
R.array.config_face_acquire_device_entry_ignorelist))
@@ -2678,7 +2672,10 @@
private boolean shouldListenForFaceAssistant() {
BiometricAuthenticated face = mUserFaceAuthenticated.get(getCurrentUser());
- return mAssistantVisible && mKeyguardOccluded
+ return mAssistantVisible
+ // There can be intermediate states where mKeyguardShowing is false but
+ // mKeyguardOccluded is true, we don't want to run face auth in such a scenario.
+ && (mKeyguardShowing && mKeyguardOccluded)
&& !(face != null && face.mAuthenticated)
&& !mUserHasTrust.get(getCurrentUser(), false);
}
@@ -3338,7 +3335,8 @@
/**
* Handle {@link #MSG_KEYGUARD_RESET}
*/
- private void handleKeyguardReset() {
+ @VisibleForTesting
+ protected void handleKeyguardReset() {
mLogger.d("handleKeyguardReset");
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_KEYGUARD_RESET);
@@ -3679,6 +3677,7 @@
if (info == null) {
return;
}
+ mLogger.logTaskStackChangedForAssistant(info.visible);
mHandler.sendMessage(mHandler.obtainMessage(MSG_ASSISTANT_STACK_CHANGED,
info.visible));
} catch (RemoteException e) {
@@ -3877,7 +3876,6 @@
pw.println(" getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
pw.println(" getUserUnlockedWithBiometric()="
+ getUserUnlockedWithBiometric(getCurrentUser()));
- pw.println(" mWakeOnFingerprintAcquiredStart=" + mWakeOnFingerprintAcquiredStart);
pw.println(" SIM States:");
for (SimData data : mSimDatas.values()) {
pw.println(" " + data.toString());
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 1322f16..8071a5d 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -429,6 +429,7 @@
pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
+ pw.println(" mDefaultPaddingPx: " + mDefaultPaddingPx);
if (mView != null) {
mView.dump(pw, args);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
index 0cbf8bc..5ad21df 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
@@ -20,13 +20,13 @@
import com.android.keyguard.KeyguardHostViewController;
import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import dagger.BindsInstance;
import dagger.Subcomponent;
/**
- * Dagger Subcomponent for the {@link KeyguardBouncer}.
+ * Dagger Subcomponent for the {@link PrimaryBouncerInteractor}.
*/
@Subcomponent(modules = {KeyguardBouncerModule.class})
@KeyguardBouncerScope
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
index ef067b8..cb7a0a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
@@ -29,7 +29,7 @@
import com.android.systemui.R;
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import java.util.Optional;
@@ -39,7 +39,7 @@
import dagger.Provides;
/**
- * Module to create and access view related to the {@link KeyguardBouncer}.
+ * Module to create and access view related to the {@link PrimaryBouncerInteractor}.
*/
@Module
public interface KeyguardBouncerModule {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 5b42455..201a1d9 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -431,4 +431,20 @@
str1 = PowerManager.wakeReasonToString(pmWakeReason)
}, { "Skip updating face listening state on wakeup from $str1"})
}
+
+ fun logTaskStackChangedForAssistant(assistantVisible: Boolean) {
+ logBuffer.log(TAG, VERBOSE, {
+ bool1 = assistantVisible
+ }, {
+ "TaskStackChanged for ACTIVITY_TYPE_ASSISTANT, assistant visible: $bool1"
+ })
+ }
+
+ fun logAssistantVisible(assistantVisible: Boolean) {
+ logBuffer.log(TAG, VERBOSE, {
+ bool1 = assistantVisible
+ }, {
+ "Updating mAssistantVisible to new value: $bool1"
+ })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 4c92598..696437d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -207,7 +207,8 @@
// Saving in instance variable since to prevent GC since
// NotificationShadeWindowController.registerCallback() only keeps weak references.
mNotificationShadeCallback =
- (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing, panelExpanded) ->
+ (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing, panelExpanded,
+ isDreaming) ->
registerOrUnregisterDismissNotificationShadeAction();
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 4b57d45..04a2689 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -55,11 +55,11 @@
private val fadeDuration = 83L
private val retractDuration = 400L
private var alphaInDuration: Long = 0
- private var unlockedRippleInProgress: Boolean = false
private val dwellShader = DwellRippleShader()
private val dwellPaint = Paint()
private val rippleShader = RippleShader()
private val ripplePaint = Paint()
+ private var unlockedRippleAnimator: AnimatorSet? = null
private var fadeDwellAnimator: Animator? = null
private var retractDwellAnimator: Animator? = null
private var dwellPulseOutAnimator: Animator? = null
@@ -205,7 +205,7 @@
* Plays a ripple animation that grows to the dwellRadius with distortion.
*/
fun startDwellRipple(isDozing: Boolean) {
- if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) {
+ if (unlockedRippleAnimator?.isRunning == true || dwellPulseOutAnimator?.isRunning == true) {
return
}
@@ -262,9 +262,7 @@
* Ripple that bursts outwards from the position of the sensor to the edges of the screen
*/
fun startUnlockedRipple(onAnimationEnd: Runnable?) {
- if (unlockedRippleInProgress) {
- return // Ignore if ripple effect is already playing
- }
+ unlockedRippleAnimator?.cancel()
val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
@@ -289,14 +287,13 @@
}
}
- val animatorSet = AnimatorSet().apply {
+ unlockedRippleAnimator = AnimatorSet().apply {
playTogether(
rippleAnimator,
alphaInAnimator
)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
- unlockedRippleInProgress = true
rippleShader.rippleFill = false
drawRipple = true
visibility = VISIBLE
@@ -304,13 +301,13 @@
override fun onAnimationEnd(animation: Animator?) {
onAnimationEnd?.run()
- unlockedRippleInProgress = false
drawRipple = false
visibility = GONE
+ unlockedRippleAnimator = null
}
})
}
- animatorSet.start()
+ unlockedRippleAnimator?.start()
}
fun resetRippleAlpha() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 6f594d5..c799e91 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -54,12 +54,18 @@
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.PrintWriter
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
private const val TAG = "SideFpsController"
@@ -79,6 +85,9 @@
displayManager: DisplayManager,
@Main private val mainExecutor: DelayableExecutor,
@Main private val handler: Handler,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ @Application private val scope: CoroutineScope,
+ private val featureFlags: FeatureFlags,
dumpManager: DumpManager
) : Dumpable {
val requests: HashSet<SideFpsUiRequestSource> = HashSet()
@@ -168,9 +177,26 @@
}
)
overviewProxyService.addCallback(overviewProxyListener)
+ listenForAlternateBouncerVisibility()
+
dumpManager.registerDumpable(this)
}
+ private fun listenForAlternateBouncerVisibility() {
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
+ if (featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)) {
+ scope.launch {
+ alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
+ if (isVisible) {
+ show(SideFpsUiRequestSource.ALTERNATE_BOUNCER)
+ } else {
+ hide(SideFpsUiRequestSource.ALTERNATE_BOUNCER)
+ }
+ }
+ }
+ }
+ }
+
/** Shows the side fps overlay if not already shown. */
fun show(request: SideFpsUiRequestSource) {
requests.add(request)
@@ -423,4 +449,5 @@
AUTO_SHOW,
/** Pin, pattern or password bouncer */
PRIMARY_BOUNCER,
+ ALTERNATE_BOUNCER
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 63a1b76..d072ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -32,7 +32,6 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -149,21 +148,6 @@
}
}
- private val mPrimaryBouncerExpansionCallback: PrimaryBouncerExpansionCallback =
- object : PrimaryBouncerExpansionCallback {
- override fun onExpansionChanged(expansion: Float) {
- inputBouncerHiddenAmount = expansion
- updateAlpha()
- updatePauseAuth()
- }
-
- override fun onVisibilityChanged(isVisible: Boolean) {
- updateBouncerHiddenAmount()
- updateAlpha()
- updatePauseAuth()
- }
- }
-
private val configurationListener: ConfigurationController.ConfigurationListener =
object : ConfigurationController.ConfigurationListener {
override fun onUiModeChanged() {
@@ -315,14 +299,6 @@
statusBarState = statusBarStateController.state
qsExpansion = keyguardViewManager.qsExpansion
keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
- if (!isModernBouncerEnabled) {
- val bouncer = keyguardViewManager.primaryBouncer
- bouncer?.expansion?.let {
- mPrimaryBouncerExpansionCallback.onExpansionChanged(it)
- bouncer.addBouncerExpansionCallback(mPrimaryBouncerExpansionCallback)
- }
- updateBouncerHiddenAmount()
- }
configurationController.addCallback(configurationListener)
shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
updateScaleFactor()
@@ -352,11 +328,6 @@
}
activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
- if (!isModernBouncerEnabled) {
- keyguardViewManager.primaryBouncer?.removeBouncerExpansionCallback(
- mPrimaryBouncerExpansionCallback
- )
- }
}
override fun dump(pw: PrintWriter, args: Array<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
index 3a01cd5..39ea936 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -54,9 +54,12 @@
return when (event.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN,
- MotionEvent.ACTION_MOVE -> processActionMove(preprocess())
+ MotionEvent.ACTION_MOVE,
+ MotionEvent.ACTION_HOVER_ENTER,
+ MotionEvent.ACTION_HOVER_MOVE -> processActionMove(preprocess())
MotionEvent.ACTION_UP,
- MotionEvent.ACTION_POINTER_UP ->
+ MotionEvent.ACTION_POINTER_UP,
+ MotionEvent.ACTION_HOVER_EXIT ->
processActionUp(preprocess(), event.getPointerId(event.actionIndex))
MotionEvent.ACTION_CANCEL -> processActionCancel(NormalizedTouchData())
else ->
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 805a20a..1c26841 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -18,7 +18,6 @@
import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
@@ -29,7 +28,6 @@
import android.content.ClipboardManager;
import android.content.Context;
import android.os.SystemProperties;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.Log;
@@ -37,9 +35,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.util.DeviceConfigProxy;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -59,42 +54,28 @@
"com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY";
private final Context mContext;
- private final DeviceConfigProxy mDeviceConfig;
private final Provider<ClipboardOverlayController> mOverlayProvider;
- private final ClipboardOverlayControllerLegacyFactory mOverlayFactory;
private final ClipboardToast mClipboardToast;
private final ClipboardManager mClipboardManager;
private final UiEventLogger mUiEventLogger;
- private final FeatureFlags mFeatureFlags;
- private boolean mUsingNewOverlay;
private ClipboardOverlay mClipboardOverlay;
@Inject
- public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy,
+ public ClipboardListener(Context context,
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
- ClipboardOverlayControllerLegacyFactory overlayFactory,
ClipboardToast clipboardToast,
ClipboardManager clipboardManager,
- UiEventLogger uiEventLogger,
- FeatureFlags featureFlags) {
+ UiEventLogger uiEventLogger) {
mContext = context;
- mDeviceConfig = deviceConfigProxy;
mOverlayProvider = clipboardOverlayControllerProvider;
- mOverlayFactory = overlayFactory;
mClipboardToast = clipboardToast;
mClipboardManager = clipboardManager;
mUiEventLogger = uiEventLogger;
- mFeatureFlags = featureFlags;
-
- mUsingNewOverlay = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
}
@Override
public void start() {
- if (mDeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, true)) {
- mClipboardManager.addPrimaryClipChangedListener(this);
- }
+ mClipboardManager.addPrimaryClipChangedListener(this);
}
@Override
@@ -120,14 +101,8 @@
return;
}
- boolean enabled = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
- if (mClipboardOverlay == null || enabled != mUsingNewOverlay) {
- mUsingNewOverlay = enabled;
- if (enabled) {
- mClipboardOverlay = mOverlayProvider.get();
- } else {
- mClipboardOverlay = mOverlayFactory.create(mContext);
- }
+ if (mClipboardOverlay == null) {
+ mClipboardOverlay = mOverlayProvider.get();
mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource);
} else {
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
deleted file mode 100644
index 3a040829..0000000
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
+++ /dev/null
@@ -1,963 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.clipboardoverlay;
-
-import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EDIT_TAPPED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
-
-import static java.util.Objects.requireNonNull;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.annotation.MainThread;
-import android.app.ICompatCameraControlCallback;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.Icon;
-import android.hardware.display.DisplayManager;
-import android.hardware.input.InputManager;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Looper;
-import android.provider.DeviceConfig;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.MathUtils;
-import android.util.Size;
-import android.util.TypedValue;
-import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.Gravity;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.InputMonitor;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.PathInterpolator;
-import android.view.textclassifier.TextClassification;
-import android.view.textclassifier.TextClassificationManager;
-import android.view.textclassifier.TextClassifier;
-import android.view.textclassifier.TextLinks;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.PhoneWindow;
-import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.screenshot.DraggableConstraintLayout;
-import com.android.systemui.screenshot.FloatingWindowUtil;
-import com.android.systemui.screenshot.OverlayActionChip;
-import com.android.systemui.screenshot.TimeoutHandler;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * Controls state and UI for the overlay that appears when something is added to the clipboard
- */
-public class ClipboardOverlayControllerLegacy implements ClipboardListener.ClipboardOverlay {
- private static final String TAG = "ClipboardOverlayCtrlr";
- private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
-
- /** Constants for screenshot/copy deconflicting */
- public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT";
- public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF";
- public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
-
- private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
-
- private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
- private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
- private static final int FONT_SEARCH_STEP_PX = 4;
-
- private final Context mContext;
- private final ClipboardLogger mClipboardLogger;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final DisplayManager mDisplayManager;
- private final DisplayMetrics mDisplayMetrics;
- private final WindowManager mWindowManager;
- private final WindowManager.LayoutParams mWindowLayoutParams;
- private final PhoneWindow mWindow;
- private final TimeoutHandler mTimeoutHandler;
- private final AccessibilityManager mAccessibilityManager;
- private final TextClassifier mTextClassifier;
-
- private final DraggableConstraintLayout mView;
- private final View mClipboardPreview;
- private final ImageView mImagePreview;
- private final TextView mTextPreview;
- private final TextView mHiddenPreview;
- private final View mPreviewBorder;
- private final OverlayActionChip mEditChip;
- private final OverlayActionChip mShareChip;
- private final OverlayActionChip mRemoteCopyChip;
- private final View mActionContainerBackground;
- private final View mDismissButton;
- private final LinearLayout mActionContainer;
- private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
-
- private Runnable mOnSessionCompleteListener;
-
- private InputMonitor mInputMonitor;
- private InputEventReceiver mInputEventReceiver;
-
- private BroadcastReceiver mCloseDialogsReceiver;
- private BroadcastReceiver mScreenshotReceiver;
-
- private boolean mBlockAttach = false;
- private Animator mExitAnimator;
- private Animator mEnterAnimator;
- private final int mOrientation;
- private boolean mKeyboardVisible;
-
-
- public ClipboardOverlayControllerLegacy(Context context,
- BroadcastDispatcher broadcastDispatcher,
- BroadcastSender broadcastSender,
- TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
- mBroadcastDispatcher = broadcastDispatcher;
- mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
- final Context displayContext = context.createDisplayContext(getDefaultDisplay());
- mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
-
- mClipboardLogger = new ClipboardLogger(uiEventLogger);
-
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
- mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
- .getTextClassifier();
-
- mWindowManager = mContext.getSystemService(WindowManager.class);
-
- mDisplayMetrics = new DisplayMetrics();
- mContext.getDisplay().getRealMetrics(mDisplayMetrics);
-
- mTimeoutHandler = timeoutHandler;
- mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
-
- // Setup the window that we are going to use
- mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
- mWindowLayoutParams.setTitle("ClipboardOverlay");
-
- mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
- mWindow.setWindowManager(mWindowManager, null, null);
-
- setWindowFocusable(false);
-
- mView = (DraggableConstraintLayout)
- LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay_legacy, null);
- mActionContainerBackground =
- requireNonNull(mView.findViewById(R.id.actions_container_background));
- mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
- mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
- mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
- mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
- mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview));
- mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
- mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
- mShareChip = requireNonNull(mView.findViewById(R.id.share_chip));
- mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
- mEditChip.setAlpha(1);
- mShareChip.setAlpha(1);
- mRemoteCopyChip.setAlpha(1);
- mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
-
- mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
- mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
- @Override
- public void onInteraction() {
- mTimeoutHandler.resetTimeout();
- }
-
- @Override
- public void onSwipeDismissInitiated(Animator animator) {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
- mExitAnimator = animator;
- }
-
- @Override
- public void onDismissComplete() {
- hideImmediate();
- }
- });
-
- mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
- int availableHeight = mTextPreview.getHeight()
- - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
- mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
- return true;
- });
-
- mDismissButton.setOnClickListener(view -> {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
- animateOut();
- });
-
- mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
- mRemoteCopyChip.setIcon(
- Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
- mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
- mOrientation = mContext.getResources().getConfiguration().orientation;
-
- attachWindow();
- withWindowAttached(() -> {
- mWindow.setContentView(mView);
- WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
- mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
- updateInsets(insets);
- mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- WindowInsets insets =
- mWindowManager.getCurrentWindowMetrics().getWindowInsets();
- boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
- if (keyboardVisible != mKeyboardVisible) {
- mKeyboardVisible = keyboardVisible;
- updateInsets(insets);
- }
- }
- });
- mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
- new ViewRootImpl.ActivityConfigCallback() {
- @Override
- public void onConfigurationChanged(Configuration overrideConfig,
- int newDisplayId) {
- if (mContext.getResources().getConfiguration().orientation
- != mOrientation) {
- mClipboardLogger.logSessionComplete(
- CLIPBOARD_OVERLAY_DISMISSED_OTHER);
- hideImmediate();
- }
- }
-
- @Override
- public void requestCompatCameraControl(
- boolean showControl, boolean transformationApplied,
- ICompatCameraControlCallback callback) {
- Log.w(TAG, "unexpected requestCompatCameraControl call");
- }
- });
- });
-
- mTimeoutHandler.setOnTimeoutRunnable(() -> {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
- animateOut();
- });
-
- mCloseDialogsReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
- animateOut();
- }
- }
- };
-
- mBroadcastDispatcher.registerReceiver(mCloseDialogsReceiver,
- new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
- mScreenshotReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (SCREENSHOT_ACTION.equals(intent.getAction())) {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
- animateOut();
- }
- }
- };
-
- mBroadcastDispatcher.registerReceiver(mScreenshotReceiver,
- new IntentFilter(SCREENSHOT_ACTION), null, null, Context.RECEIVER_EXPORTED,
- SELF_PERMISSION);
- monitorOutsideTouches();
-
- Intent copyIntent = new Intent(COPY_OVERLAY_ACTION);
- // Set package name so the system knows it's safe
- copyIntent.setPackage(mContext.getPackageName());
- broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
- }
-
- @Override // ClipboardListener.ClipboardOverlay
- public void setClipData(ClipData clipData, String clipSource) {
- if (mExitAnimator != null && mExitAnimator.isRunning()) {
- mExitAnimator.cancel();
- }
- reset();
- String accessibilityAnnouncement;
-
- boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
- && clipData.getDescription().getExtras()
- .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
- if (clipData == null || clipData.getItemCount() == 0) {
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
- } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
- ClipData.Item item = clipData.getItemAt(0);
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
- if (item.getTextLinks() != null) {
- AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
- }
- }
- if (isSensitive) {
- showEditableText(
- mContext.getResources().getString(R.string.clipboard_asterisks), true);
- } else {
- showEditableText(item.getText(), false);
- }
- showShareChip(clipData);
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
- } else if (clipData.getItemAt(0).getUri() != null) {
- if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
- showShareChip(clipData);
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
- } else {
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
- }
- } else {
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
- }
- Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
- // Only show remote copy if it's available.
- PackageManager packageManager = mContext.getPackageManager();
- if (packageManager.resolveActivity(
- remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
- mRemoteCopyChip.setContentDescription(
- mContext.getString(R.string.clipboard_send_nearby_description));
- mRemoteCopyChip.setVisibility(View.VISIBLE);
- mRemoteCopyChip.setOnClickListener((v) -> {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
- mContext.startActivity(remoteCopyIntent);
- animateOut();
- });
- mActionContainerBackground.setVisibility(View.VISIBLE);
- } else {
- mRemoteCopyChip.setVisibility(View.GONE);
- }
- withWindowAttached(() -> {
- if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
- mView.post(this::animateIn);
- }
- mView.announceForAccessibility(accessibilityAnnouncement);
- });
- mTimeoutHandler.resetTimeout();
- }
-
- @Override // ClipboardListener.ClipboardOverlay
- public void setOnSessionCompleteListener(Runnable runnable) {
- mOnSessionCompleteListener = runnable;
- }
-
- private void classifyText(ClipData.Item item, String source) {
- ArrayList<RemoteAction> actions = new ArrayList<>();
- for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
- TextClassification classification = mTextClassifier.classifyText(
- item.getText(), link.getStart(), link.getEnd(), null);
- actions.addAll(classification.getActions());
- }
- mView.post(() -> {
- resetActionChips();
- if (actions.size() > 0) {
- mActionContainerBackground.setVisibility(View.VISIBLE);
- for (RemoteAction action : actions) {
- Intent targetIntent = action.getActionIntent().getIntent();
- ComponentName component = targetIntent.getComponent();
- if (component != null && !TextUtils.equals(source,
- component.getPackageName())) {
- OverlayActionChip chip = constructActionChip(action);
- mActionContainer.addView(chip);
- mActionChips.add(chip);
- break; // only show at most one action chip
- }
- }
- }
- });
- }
-
- private void showShareChip(ClipData clip) {
- mShareChip.setVisibility(View.VISIBLE);
- mActionContainerBackground.setVisibility(View.VISIBLE);
- mShareChip.setOnClickListener((v) -> shareContent(clip));
- }
-
- private OverlayActionChip constructActionChip(RemoteAction action) {
- OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
- R.layout.overlay_action_chip, mActionContainer, false);
- chip.setText(action.getTitle());
- chip.setContentDescription(action.getTitle());
- chip.setIcon(action.getIcon(), false);
- chip.setPendingIntent(action.getActionIntent(), () -> {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
- animateOut();
- });
- chip.setAlpha(1);
- return chip;
- }
-
- private void monitorOutsideTouches() {
- InputManager inputManager = mContext.getSystemService(InputManager.class);
- mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
- mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
- Looper.getMainLooper()) {
- @Override
- public void onInputEvent(InputEvent event) {
- if (event instanceof MotionEvent) {
- MotionEvent motionEvent = (MotionEvent) event;
- if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
- Region touchRegion = new Region();
-
- final Rect tmpRect = new Rect();
- mPreviewBorder.getBoundsOnScreen(tmpRect);
- tmpRect.inset(
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
- -SWIPE_PADDING_DP));
- touchRegion.op(tmpRect, Region.Op.UNION);
- mActionContainerBackground.getBoundsOnScreen(tmpRect);
- tmpRect.inset(
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
- -SWIPE_PADDING_DP));
- touchRegion.op(tmpRect, Region.Op.UNION);
- mDismissButton.getBoundsOnScreen(tmpRect);
- touchRegion.op(tmpRect, Region.Op.UNION);
- if (!touchRegion.contains(
- (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
- animateOut();
- }
- }
- }
- finishInputEvent(event, true /* handled */);
- }
- };
- }
-
- private void editImage(Uri uri) {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
- mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext));
- animateOut();
- }
-
- private void editText() {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
- mContext.startActivity(IntentCreator.getTextEditorIntent(mContext));
- animateOut();
- }
-
- private void shareContent(ClipData clip) {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED);
- mContext.startActivity(IntentCreator.getShareIntent(clip, mContext));
- animateOut();
- }
-
- private void showSinglePreview(View v) {
- mTextPreview.setVisibility(View.GONE);
- mImagePreview.setVisibility(View.GONE);
- mHiddenPreview.setVisibility(View.GONE);
- v.setVisibility(View.VISIBLE);
- }
-
- private void showTextPreview(CharSequence text, TextView textView) {
- showSinglePreview(textView);
- final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length()));
- textView.setText(truncatedText);
- updateTextSize(truncatedText, textView);
-
- textView.addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- if (right - left != oldRight - oldLeft) {
- updateTextSize(truncatedText, textView);
- }
- });
- mEditChip.setVisibility(View.GONE);
- }
-
- private void updateTextSize(CharSequence text, TextView textView) {
- Paint paint = new Paint(textView.getPaint());
- Resources res = textView.getResources();
- float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
- float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
- if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
- // If the text is a single word and would fit within the TextView at the min font size,
- // find the biggest font size that will fit.
- float fontSizePx = minFontSize;
- while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
- && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
- fontSizePx += FONT_SEARCH_STEP_PX;
- }
- // Need to turn off autosizing, otherwise setTextSize is a no-op.
- textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
- // It's possible to hit the max font size and not fill the width, so centering
- // horizontally looks better in this case.
- textView.setGravity(Gravity.CENTER);
- textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
- } else {
- // Otherwise just stick with autosize.
- textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
- (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
- textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
- }
- }
-
- private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
- float fontSizePx) {
- paint.setTextSize(fontSizePx);
- float size = paint.measureText(text.toString());
- float availableWidth = textView.getWidth() - textView.getPaddingLeft()
- - textView.getPaddingRight();
- return size < availableWidth;
- }
-
- private static boolean isOneWord(CharSequence text) {
- return text.toString().split("\\s+", 2).length == 1;
- }
-
- private void showEditableText(CharSequence text, boolean hidden) {
- TextView textView = hidden ? mHiddenPreview : mTextPreview;
- showTextPreview(text, textView);
- View.OnClickListener listener = v -> editText();
- setAccessibilityActionToEdit(textView);
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
- mEditChip.setVisibility(View.VISIBLE);
- mActionContainerBackground.setVisibility(View.VISIBLE);
- mEditChip.setContentDescription(
- mContext.getString(R.string.clipboard_edit_text_description));
- mEditChip.setOnClickListener(listener);
- }
- textView.setOnClickListener(listener);
- }
-
- private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
- View.OnClickListener listener = v -> editImage(uri);
- ContentResolver resolver = mContext.getContentResolver();
- String mimeType = resolver.getType(uri);
- boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
- if (isSensitive) {
- mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
- showSinglePreview(mHiddenPreview);
- if (isEditableImage) {
- mHiddenPreview.setOnClickListener(listener);
- setAccessibilityActionToEdit(mHiddenPreview);
- }
- } else if (isEditableImage) { // if the MIMEtype is image, try to load
- try {
- int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
- // The width of the view is capped, height maintains aspect ratio, so allow it to be
- // taller if needed.
- Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
- showSinglePreview(mImagePreview);
- mImagePreview.setImageBitmap(thumbnail);
- mImagePreview.setOnClickListener(listener);
- setAccessibilityActionToEdit(mImagePreview);
- } catch (IOException e) {
- Log.e(TAG, "Thumbnail loading failed", e);
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
- isEditableImage = false;
- }
- } else {
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
- }
- if (isEditableImage && DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
- mEditChip.setVisibility(View.VISIBLE);
- mActionContainerBackground.setVisibility(View.VISIBLE);
- mEditChip.setOnClickListener(listener);
- mEditChip.setContentDescription(
- mContext.getString(R.string.clipboard_edit_image_description));
- }
- return isEditableImage;
- }
-
- private void setAccessibilityActionToEdit(View view) {
- ViewCompat.replaceAccessibilityAction(view,
- AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
- mContext.getString(R.string.clipboard_edit), null);
- }
-
- private void animateIn() {
- if (mAccessibilityManager.isEnabled()) {
- mDismissButton.setVisibility(View.VISIBLE);
- }
- mEnterAnimator = getEnterAnimation();
- mEnterAnimator.start();
- }
-
- private void animateOut() {
- if (mExitAnimator != null && mExitAnimator.isRunning()) {
- return;
- }
- Animator anim = getExitAnimation();
- anim.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- super.onAnimationCancel(animation);
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- if (!mCancelled) {
- hideImmediate();
- }
- }
- });
- mExitAnimator = anim;
- anim.start();
- }
-
- private Animator getEnterAnimation() {
- TimeInterpolator linearInterpolator = new LinearInterpolator();
- TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
- AnimatorSet enterAnim = new AnimatorSet();
-
- ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
- rootAnim.setInterpolator(linearInterpolator);
- rootAnim.setDuration(66);
- rootAnim.addUpdateListener(animation -> {
- mView.setAlpha(animation.getAnimatedFraction());
- });
-
- ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
- scaleAnim.setInterpolator(scaleInterpolator);
- scaleAnim.setDuration(333);
- scaleAnim.addUpdateListener(animation -> {
- float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
- mClipboardPreview.setScaleX(previewScale);
- mClipboardPreview.setScaleY(previewScale);
- mPreviewBorder.setScaleX(previewScale);
- mPreviewBorder.setScaleY(previewScale);
-
- float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
- mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
- mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
- float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
- float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
- mActionContainer.setScaleX(actionsScaleX);
- mActionContainer.setScaleY(actionsScaleY);
- mActionContainerBackground.setScaleX(actionsScaleX);
- mActionContainerBackground.setScaleY(actionsScaleY);
- });
-
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.setInterpolator(linearInterpolator);
- alphaAnim.setDuration(283);
- alphaAnim.addUpdateListener(animation -> {
- float alpha = animation.getAnimatedFraction();
- mClipboardPreview.setAlpha(alpha);
- mPreviewBorder.setAlpha(alpha);
- mDismissButton.setAlpha(alpha);
- mActionContainer.setAlpha(alpha);
- });
-
- mActionContainer.setAlpha(0);
- mPreviewBorder.setAlpha(0);
- mClipboardPreview.setAlpha(0);
- enterAnim.play(rootAnim).with(scaleAnim);
- enterAnim.play(alphaAnim).after(50).after(rootAnim);
-
- enterAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mView.setAlpha(1);
- mTimeoutHandler.resetTimeout();
- }
- });
- return enterAnim;
- }
-
- private Animator getExitAnimation() {
- TimeInterpolator linearInterpolator = new LinearInterpolator();
- TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
- AnimatorSet exitAnim = new AnimatorSet();
-
- ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
- rootAnim.setInterpolator(linearInterpolator);
- rootAnim.setDuration(100);
- rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
-
- ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
- scaleAnim.setInterpolator(scaleInterpolator);
- scaleAnim.setDuration(250);
- scaleAnim.addUpdateListener(animation -> {
- float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
- mClipboardPreview.setScaleX(previewScale);
- mClipboardPreview.setScaleY(previewScale);
- mPreviewBorder.setScaleX(previewScale);
- mPreviewBorder.setScaleY(previewScale);
-
- float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
- mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
- mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
- float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
- float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
- mActionContainer.setScaleX(actionScaleX);
- mActionContainer.setScaleY(actionScaleY);
- mActionContainerBackground.setScaleX(actionScaleX);
- mActionContainerBackground.setScaleY(actionScaleY);
- });
-
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.setInterpolator(linearInterpolator);
- alphaAnim.setDuration(166);
- alphaAnim.addUpdateListener(animation -> {
- float alpha = 1 - animation.getAnimatedFraction();
- mClipboardPreview.setAlpha(alpha);
- mPreviewBorder.setAlpha(alpha);
- mDismissButton.setAlpha(alpha);
- mActionContainer.setAlpha(alpha);
- });
-
- exitAnim.play(alphaAnim).with(scaleAnim);
- exitAnim.play(rootAnim).after(150).after(alphaAnim);
- return exitAnim;
- }
-
- private void hideImmediate() {
- // Note this may be called multiple times if multiple dismissal events happen at the same
- // time.
- mTimeoutHandler.cancelTimeout();
- final View decorView = mWindow.peekDecorView();
- if (decorView != null && decorView.isAttachedToWindow()) {
- mWindowManager.removeViewImmediate(decorView);
- }
- if (mCloseDialogsReceiver != null) {
- mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
- mCloseDialogsReceiver = null;
- }
- if (mScreenshotReceiver != null) {
- mBroadcastDispatcher.unregisterReceiver(mScreenshotReceiver);
- mScreenshotReceiver = null;
- }
- if (mInputEventReceiver != null) {
- mInputEventReceiver.dispose();
- mInputEventReceiver = null;
- }
- if (mInputMonitor != null) {
- mInputMonitor.dispose();
- mInputMonitor = null;
- }
- if (mOnSessionCompleteListener != null) {
- mOnSessionCompleteListener.run();
- }
- }
-
- private void resetActionChips() {
- for (OverlayActionChip chip : mActionChips) {
- mActionContainer.removeView(chip);
- }
- mActionChips.clear();
- }
-
- private void reset() {
- mView.setTranslationX(0);
- mView.setAlpha(0);
- mActionContainerBackground.setVisibility(View.GONE);
- mShareChip.setVisibility(View.GONE);
- mEditChip.setVisibility(View.GONE);
- mRemoteCopyChip.setVisibility(View.GONE);
- resetActionChips();
- mTimeoutHandler.cancelTimeout();
- mClipboardLogger.reset();
- }
-
- @MainThread
- private void attachWindow() {
- View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow() || mBlockAttach) {
- return;
- }
- mBlockAttach = true;
- mWindowManager.addView(decorView, mWindowLayoutParams);
- decorView.requestApplyInsets();
- mView.requestApplyInsets();
- decorView.getViewTreeObserver().addOnWindowAttachListener(
- new ViewTreeObserver.OnWindowAttachListener() {
- @Override
- public void onWindowAttached() {
- mBlockAttach = false;
- }
-
- @Override
- public void onWindowDetached() {
- }
- }
- );
- }
-
- private void withWindowAttached(Runnable action) {
- View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow()) {
- action.run();
- } else {
- decorView.getViewTreeObserver().addOnWindowAttachListener(
- new ViewTreeObserver.OnWindowAttachListener() {
- @Override
- public void onWindowAttached() {
- mBlockAttach = false;
- decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
- action.run();
- }
-
- @Override
- public void onWindowDetached() {
- }
- });
- }
- }
-
- private void updateInsets(WindowInsets insets) {
- int orientation = mContext.getResources().getConfiguration().orientation;
- FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
- if (p == null) {
- return;
- }
- DisplayCutout cutout = insets.getDisplayCutout();
- Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
- Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
- if (cutout == null) {
- p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
- } else {
- Insets waterfall = cutout.getWaterfallInsets();
- if (orientation == ORIENTATION_PORTRAIT) {
- p.setMargins(
- waterfall.left,
- Math.max(cutout.getSafeInsetTop(), waterfall.top),
- waterfall.right,
- Math.max(imeInsets.bottom,
- Math.max(cutout.getSafeInsetBottom(),
- Math.max(navBarInsets.bottom, waterfall.bottom))));
- } else {
- p.setMargins(
- waterfall.left,
- waterfall.top,
- waterfall.right,
- Math.max(imeInsets.bottom,
- Math.max(navBarInsets.bottom, waterfall.bottom)));
- }
- }
- mView.setLayoutParams(p);
- mView.requestLayout();
- }
-
- private Display getDefaultDisplay() {
- return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
- }
-
- /**
- * Updates the window focusability. If the window is already showing, then it updates the
- * window immediately, otherwise the layout params will be applied when the window is next
- * shown.
- */
- private void setWindowFocusable(boolean focusable) {
- int flags = mWindowLayoutParams.flags;
- if (focusable) {
- mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- } else {
- mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- }
- if (mWindowLayoutParams.flags == flags) {
- return;
- }
- final View decorView = mWindow.peekDecorView();
- if (decorView != null && decorView.isAttachedToWindow()) {
- mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
- }
- }
-
- static class ClipboardLogger {
- private final UiEventLogger mUiEventLogger;
- private boolean mGuarded = false;
-
- ClipboardLogger(UiEventLogger uiEventLogger) {
- mUiEventLogger = uiEventLogger;
- }
-
- void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
- if (!mGuarded) {
- mGuarded = true;
- mUiEventLogger.log(event);
- }
- }
-
- void reset() {
- mGuarded = false;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
deleted file mode 100644
index 0d989a7..0000000
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.clipboardoverlay;
-
-import android.content.Context;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.screenshot.TimeoutHandler;
-
-import javax.inject.Inject;
-
-/**
- * A factory that churns out ClipboardOverlayControllerLegacys on demand.
- */
-@SysUISingleton
-public class ClipboardOverlayControllerLegacyFactory {
-
- private final UiEventLogger mUiEventLogger;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final BroadcastSender mBroadcastSender;
-
- @Inject
- public ClipboardOverlayControllerLegacyFactory(BroadcastDispatcher broadcastDispatcher,
- BroadcastSender broadcastSender, UiEventLogger uiEventLogger) {
- this.mBroadcastDispatcher = broadcastDispatcher;
- this.mBroadcastSender = broadcastSender;
- this.mUiEventLogger = uiEventLogger;
- }
-
- /**
- * One new ClipboardOverlayControllerLegacy, coming right up!
- */
- public ClipboardOverlayControllerLegacy create(Context context) {
- return new ClipboardOverlayControllerLegacy(context, mBroadcastDispatcher, mBroadcastSender,
- new TimeoutHandler(context), mUiEventLogger);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
new file mode 100644
index 0000000..2dd98dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.common.ui.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import kotlin.math.pow
+import kotlin.math.sqrt
+import kotlinx.coroutines.DisposableHandle
+
+/**
+ * View designed to handle long-presses.
+ *
+ * The view will not handle any long pressed by default. To set it up, set up a listener and, when
+ * ready to start consuming long-presses, set [setLongPressHandlingEnabled] to `true`.
+ */
+class LongPressHandlingView(
+ context: Context,
+ attrs: AttributeSet?,
+) :
+ View(
+ context,
+ attrs,
+ ) {
+ interface Listener {
+ /** Notifies that a long-press has been detected by the given view. */
+ fun onLongPressDetected(
+ view: View,
+ x: Int,
+ y: Int,
+ )
+
+ /** Notifies that the gesture was too short for a long press, it is actually a click. */
+ fun onSingleTapDetected(view: View) = Unit
+ }
+
+ var listener: Listener? = null
+
+ private val interactionHandler: LongPressHandlingViewInteractionHandler by lazy {
+ LongPressHandlingViewInteractionHandler(
+ postDelayed = { block, timeoutMs ->
+ val dispatchToken = Any()
+
+ handler.postDelayed(
+ block,
+ dispatchToken,
+ timeoutMs,
+ )
+
+ DisposableHandle { handler.removeCallbacksAndMessages(dispatchToken) }
+ },
+ isAttachedToWindow = ::isAttachedToWindow,
+ onLongPressDetected = { x, y ->
+ listener?.onLongPressDetected(
+ view = this,
+ x = x,
+ y = y,
+ )
+ },
+ onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) },
+ )
+ }
+
+ fun setLongPressHandlingEnabled(isEnabled: Boolean) {
+ interactionHandler.isLongPressHandlingEnabled = isEnabled
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
+ return interactionHandler.onTouchEvent(event?.toModel())
+ }
+}
+
+private fun MotionEvent.toModel(): LongPressHandlingViewInteractionHandler.MotionEventModel {
+ return when (actionMasked) {
+ MotionEvent.ACTION_DOWN ->
+ LongPressHandlingViewInteractionHandler.MotionEventModel.Down(
+ x = x.toInt(),
+ y = y.toInt(),
+ )
+ MotionEvent.ACTION_MOVE ->
+ LongPressHandlingViewInteractionHandler.MotionEventModel.Move(
+ distanceMoved = distanceMoved(),
+ )
+ MotionEvent.ACTION_UP ->
+ LongPressHandlingViewInteractionHandler.MotionEventModel.Up(
+ distanceMoved = distanceMoved(),
+ gestureDuration = gestureDuration(),
+ )
+ MotionEvent.ACTION_CANCEL -> LongPressHandlingViewInteractionHandler.MotionEventModel.Cancel
+ else -> LongPressHandlingViewInteractionHandler.MotionEventModel.Other
+ }
+}
+
+private fun MotionEvent.distanceMoved(): Float {
+ return if (historySize > 0) {
+ sqrt((x - getHistoricalX(0)).pow(2) + (y - getHistoricalY(0)).pow(2))
+ } else {
+ 0f
+ }
+}
+
+private fun MotionEvent.gestureDuration(): Long {
+ return eventTime - downTime
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt
new file mode 100644
index 0000000..c2d4d12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.common.ui.view
+
+import android.view.ViewConfiguration
+import kotlinx.coroutines.DisposableHandle
+
+/** Encapsulates logic to handle complex touch interactions with a [LongPressHandlingView]. */
+class LongPressHandlingViewInteractionHandler(
+ /**
+ * Callback to run the given [Runnable] with the given delay, returning a [DisposableHandle]
+ * allowing the delayed runnable to be canceled before it is run.
+ */
+ private val postDelayed: (block: Runnable, delayMs: Long) -> DisposableHandle,
+ /** Callback to be queried to check if the view is attached to its window. */
+ private val isAttachedToWindow: () -> Boolean,
+ /** Callback reporting the a long-press gesture was detected at the given coordinates. */
+ private val onLongPressDetected: (x: Int, y: Int) -> Unit,
+ /** Callback reporting the a single tap gesture was detected at the given coordinates. */
+ private val onSingleTapDetected: () -> Unit,
+) {
+ sealed class MotionEventModel {
+ object Other : MotionEventModel()
+
+ data class Down(
+ val x: Int,
+ val y: Int,
+ ) : MotionEventModel()
+
+ data class Move(
+ val distanceMoved: Float,
+ ) : MotionEventModel()
+
+ data class Up(
+ val distanceMoved: Float,
+ val gestureDuration: Long,
+ ) : MotionEventModel()
+
+ object Cancel : MotionEventModel()
+ }
+
+ var isLongPressHandlingEnabled: Boolean = false
+ var scheduledLongPressHandle: DisposableHandle? = null
+
+ fun onTouchEvent(event: MotionEventModel?): Boolean {
+ if (!isLongPressHandlingEnabled) {
+ return false
+ }
+
+ return when (event) {
+ is MotionEventModel.Down -> {
+ scheduleLongPress(event.x, event.y)
+ true
+ }
+ is MotionEventModel.Move -> {
+ if (event.distanceMoved > ViewConfiguration.getTouchSlop()) {
+ cancelScheduledLongPress()
+ }
+ false
+ }
+ is MotionEventModel.Up -> {
+ cancelScheduledLongPress()
+ if (
+ event.distanceMoved <= ViewConfiguration.getTouchSlop() &&
+ event.gestureDuration < ViewConfiguration.getLongPressTimeout()
+ ) {
+ dispatchSingleTap()
+ }
+ false
+ }
+ is MotionEventModel.Cancel -> {
+ cancelScheduledLongPress()
+ false
+ }
+ else -> false
+ }
+ }
+
+ private fun scheduleLongPress(
+ x: Int,
+ y: Int,
+ ) {
+ scheduledLongPressHandle =
+ postDelayed(
+ {
+ dispatchLongPress(
+ x = x,
+ y = y,
+ )
+ },
+ ViewConfiguration.getLongPressTimeout().toLong(),
+ )
+ }
+
+ private fun dispatchLongPress(
+ x: Int,
+ y: Int,
+ ) {
+ if (!isAttachedToWindow()) {
+ return
+ }
+
+ onLongPressDetected(x, y)
+ }
+
+ private fun cancelScheduledLongPress() {
+ scheduledLongPressHandle?.dispose()
+ }
+
+ private fun dispatchSingleTap() {
+ if (!isAttachedToWindow()) {
+ return
+ }
+
+ onSingleTapDetected()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 54587b2..3808e73 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -189,9 +189,9 @@
authorizedPanelsRepository.addAuthorizedPanels(
setOf(serviceInfo.componentName.packageName)
)
- animateExitAndFinish()
val selected = SelectedItem.PanelItem(appName, componentName)
controlsController.setPreferredSelection(selected)
+ animateExitAndFinish()
openControlsOrigin()
}
dialog = null
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 966dbf1..9e71bef 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -25,6 +25,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
@@ -38,6 +39,7 @@
import android.view.animation.DecelerateInterpolator
import android.widget.AdapterView
import android.widget.ArrayAdapter
+import android.widget.BaseAdapter
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
@@ -90,6 +92,7 @@
class ControlsUiControllerImpl @Inject constructor (
val controlsController: Lazy<ControlsController>,
val context: Context,
+ private val packageManager: PackageManager,
@Main val uiExecutor: DelayableExecutor,
@Background val bgExecutor: DelayableExecutor,
val controlsListingController: Lazy<ControlsListingController>,
@@ -113,6 +116,11 @@
private const val PREF_IS_PANEL = "controls_is_panel"
private const val FADE_IN_MILLIS = 200L
+
+ private const val OPEN_APP_ID = 0L
+ private const val ADD_CONTROLS_ID = 1L
+ private const val ADD_APP_ID = 2L
+ private const val EDIT_CONTROLS_ID = 3L
}
private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
@@ -140,6 +148,9 @@
it.getTitle()
}
+ private var openAppIntent: Intent? = null
+ private var overflowMenuAdapter: BaseAdapter? = null
+
private val onSeedingComplete = Consumer<Boolean> {
accepted ->
if (accepted) {
@@ -216,6 +227,8 @@
this.parent = parent
this.onDismiss = onDismiss
this.activityContext = activityContext
+ this.openAppIntent = null
+ this.overflowMenuAdapter = null
hidden = false
retainCache = false
@@ -306,6 +319,12 @@
startTargetedActivity(si, ControlsEditingActivity::class.java)
}
+ private fun startDefaultActivity() {
+ openAppIntent?.let {
+ startActivity(it, animateExtra = false)
+ }
+ }
+
private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) {
val i = Intent(activityContext, klazz)
putIntentExtras(i, si)
@@ -329,9 +348,11 @@
startActivity(i)
}
- private fun startActivity(intent: Intent) {
+ private fun startActivity(intent: Intent, animateExtra: Boolean = true) {
// Force animations when transitioning from a dialog to an activity
- intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+ if (animateExtra) {
+ intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+ }
if (keyguardStateController.isShowing()) {
activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
@@ -383,8 +404,31 @@
Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem")
}
+ bgExecutor.execute {
+ val intent = Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setPackage(selectionItem.componentName.packageName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+ val intents = packageManager
+ .queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(0L))
+ intents.firstOrNull { it.activityInfo.exported }?.let { resolved ->
+ intent.setPackage(null)
+ intent.setComponent(resolved.activityInfo.componentName)
+ openAppIntent = intent
+ parent.post {
+ // This will call show on the PopupWindow in the same thread, so make sure this
+ // happens in the view thread.
+ overflowMenuAdapter?.notifyDataSetChanged()
+ }
+ }
+ }
createDropDown(panelsAndStructures, selectionItem)
- createMenu()
+
+ val currentApps = panelsAndStructures.map { it.componentName }.toSet()
+ val allApps = controlsListingController.get()
+ .getCurrentServices().map { it.componentName }.toSet()
+ createMenu(extraApps = (allApps - currentApps).isNotEmpty())
}
private fun createPanelView(componentName: ComponentName) {
@@ -423,28 +467,41 @@
}
}
- private fun createMenu() {
+ private fun createMenu(extraApps: Boolean) {
val isPanel = selectedItem is SelectedItem.PanelItem
val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
?: EMPTY_STRUCTURE
val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
- val addControlsId = if (newFlows || isPanel) {
- R.string.controls_menu_add_another_app
- } else {
- R.string.controls_menu_add
+
+ val items = buildList {
+ add(OverflowMenuAdapter.MenuItem(
+ context.getText(R.string.controls_open_app),
+ OPEN_APP_ID
+ ))
+ if (newFlows || isPanel) {
+ if (extraApps) {
+ add(OverflowMenuAdapter.MenuItem(
+ context.getText(R.string.controls_menu_add_another_app),
+ ADD_APP_ID
+ ))
+ }
+ } else {
+ add(OverflowMenuAdapter.MenuItem(
+ context.getText(R.string.controls_menu_add),
+ ADD_CONTROLS_ID
+ ))
+ }
+ if (!isPanel) {
+ add(OverflowMenuAdapter.MenuItem(
+ context.getText(R.string.controls_menu_edit),
+ EDIT_CONTROLS_ID
+ ))
+ }
}
- val items = if (isPanel) {
- arrayOf(
- context.resources.getString(addControlsId),
- )
- } else {
- arrayOf(
- context.resources.getString(addControlsId),
- context.resources.getString(R.string.controls_menu_edit)
- )
+ val adapter = OverflowMenuAdapter(context, R.layout.controls_more_item, items) { position ->
+ getItemId(position) != OPEN_APP_ID || openAppIntent != null
}
- var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
anchor.setOnClickListener(object : View.OnClickListener {
@@ -462,25 +519,21 @@
pos: Int,
id: Long
) {
- when (pos) {
- // 0: Add Control
- 0 -> {
- if (isPanel || newFlows) {
- startProviderSelectorActivity()
- } else {
- startFavoritingActivity(selectedStructure)
- }
- }
- // 1: Edit controls
- 1 -> startEditingActivity(selectedStructure)
+ when (id) {
+ OPEN_APP_ID -> startDefaultActivity()
+ ADD_APP_ID -> startProviderSelectorActivity()
+ ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
+ EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
}
dismiss()
}
})
show()
+ listView?.post { listView?.requestAccessibilityFocus() }
}
}
})
+ overflowMenuAdapter = adapter
}
private fun createDropDown(items: List<SelectionItem>, selected: SelectionItem) {
@@ -542,6 +595,7 @@
}
})
show()
+ listView?.post { listView?.requestAccessibilityFocus() }
}
}
})
@@ -631,7 +685,7 @@
.putString(PREF_COMPONENT, selectedItem.componentName.flattenToString())
.putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString())
.putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem)
- .commit()
+ .apply()
}
private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/OverflowMenuAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/OverflowMenuAdapter.kt
new file mode 100644
index 0000000..6b84e36
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/OverflowMenuAdapter.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.controls.ui
+
+import android.content.Context
+import android.widget.ArrayAdapter
+import androidx.annotation.LayoutRes
+
+open class OverflowMenuAdapter(
+ context: Context,
+ @LayoutRes layoutId: Int,
+ itemsWithIds: List<MenuItem>,
+ private val isEnabledInternal: OverflowMenuAdapter.(Int) -> Boolean
+) : ArrayAdapter<CharSequence>(context, layoutId, itemsWithIds.map(MenuItem::text)) {
+
+ private val ids = itemsWithIds.map(MenuItem::id)
+
+ override fun getItemId(position: Int): Long {
+ return ids[position]
+ }
+
+ override fun isEnabled(position: Int): Boolean {
+ return isEnabledInternal(position)
+ }
+
+ data class MenuItem(val text: CharSequence, val id: Long)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 8e9992f..ef07e99 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -27,6 +27,7 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.dagger.qualifiers.PerUser
+import com.android.systemui.dreams.DreamMonitor
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyguard.KeyguardViewMediator
@@ -286,4 +287,10 @@
@IntoMap
@ClassKey(StylusUsiPowerStartable::class)
abstract fun bindStylusUsiPowerStartable(sysui: StylusUsiPowerStartable): CoreStartable
+
+ /**Inject into DreamMonitor */
+ @Binds
+ @IntoMap
+ @ClassKey(DreamMonitor::class)
+ abstract fun bindDreamMonitor(sysui: DreamMonitor): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
new file mode 100644
index 0000000..102f208
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
@@ -0,0 +1,58 @@
+/*
+ * 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.dreams;
+
+import android.util.Log;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.callbacks.DreamStatusBarStateCallback;
+import com.android.systemui.dreams.conditions.DreamCondition;
+import com.android.systemui.shared.condition.Monitor;
+
+import javax.inject.Inject;
+
+/**
+ * A {@link CoreStartable} to retain a monitor for tracking dreaming.
+ */
+public class DreamMonitor implements CoreStartable {
+ private static final String TAG = "DreamMonitor";
+
+ // We retain a reference to the monitor so it is not garbage-collected.
+ private final Monitor mConditionMonitor;
+ private final DreamCondition mDreamCondition;
+ private final DreamStatusBarStateCallback mCallback;
+
+
+ @Inject
+ public DreamMonitor(Monitor monitor, DreamCondition dreamCondition,
+ DreamStatusBarStateCallback callback) {
+ mConditionMonitor = monitor;
+ mDreamCondition = dreamCondition;
+ mCallback = callback;
+
+ }
+ @Override
+ public void start() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "started");
+ }
+
+ mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
+ .addCondition(mDreamCondition)
+ .build());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index c882f8a..c3bd5d9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -182,6 +182,18 @@
}
}
+ /**
+ * Ends the dream content and dream overlay animations, if they're currently running.
+ * @see [AnimatorSet.end]
+ */
+ fun endAnimations() {
+ mAnimator =
+ mAnimator?.let {
+ it.end()
+ null
+ }
+ }
+
private fun blurAnimator(
view: View,
fromBlurRadius: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 33c8379..4de96e3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -40,8 +40,6 @@
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
import com.android.systemui.statusbar.BlurUtils;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -56,7 +54,6 @@
@DreamOverlayComponent.DreamOverlayScope
public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> {
private final DreamOverlayStatusBarViewController mStatusBarViewController;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final BlurUtils mBlurUtils;
private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
private final DreamOverlayStateController mStateController;
@@ -127,13 +124,29 @@
}
};
+ /**
+ * If true, overlay entry animations should be skipped once.
+ *
+ * This is turned on when exiting low light and should be turned off once the entry animations
+ * are skipped once.
+ */
+ private boolean mSkipEntryAnimations;
+
+ private final DreamOverlayStateController.Callback
+ mDreamOverlayStateCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onExitLowLight() {
+ mSkipEntryAnimations = true;
+ }
+ };
+
@Inject
public DreamOverlayContainerViewController(
DreamOverlayContainerView containerView,
ComplicationHostViewController complicationHostViewController,
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
DreamOverlayStatusBarViewController statusBarViewController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager,
BlurUtils blurUtils,
@Main Handler handler,
@Main Resources resources,
@@ -147,7 +160,6 @@
super(containerView);
mDreamOverlayContentView = contentView;
mStatusBarViewController = statusBarViewController;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mBlurUtils = blurUtils;
mDreamOverlayAnimationsController = animationsController;
mStateController = stateController;
@@ -170,6 +182,7 @@
@Override
protected void onInit() {
+ mStateController.addCallback(mDreamOverlayStateCallback);
mStatusBarViewController.init();
mComplicationHostViewController.init();
mDreamOverlayAnimationsController.init(mView);
@@ -179,25 +192,24 @@
protected void onViewAttached() {
mJitterStartTimeMillis = System.currentTimeMillis();
mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
- final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer();
- if (bouncer != null) {
- bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
- }
mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
// Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) {
mDreamOverlayAnimationsController.startEntryAnimations();
+
+ if (mSkipEntryAnimations) {
+ // If we're transitioning from the low light dream back to the user dream, skip the
+ // overlay animations and show immediately.
+ mDreamOverlayAnimationsController.endAnimations();
+ mSkipEntryAnimations = false;
+ }
}
}
@Override
protected void onViewDetached() {
mHandler.removeCallbacks(this::updateBurnInOffsets);
- final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer();
- if (bouncer != null) {
- bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
- }
mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
mDreamOverlayAnimationsController.cancelAnimations();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index ccfdd096..2c7ecb1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -83,6 +83,12 @@
*/
default void onAvailableComplicationTypesChanged() {
}
+
+ /**
+ * Called when the low light dream is exiting and transitioning back to the user dream.
+ */
+ default void onExitLowLight() {
+ }
}
private final Executor mExecutor;
@@ -278,6 +284,10 @@
* @param active {@code true} if low light mode is active, {@code false} otherwise.
*/
public void setLowLightActive(boolean active) {
+ if (isLowLightActive() && !active) {
+ // Notify that we're exiting low light only on the transition from active to not active.
+ mCallbacks.forEach(Callback::onExitLowLight);
+ }
modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_LOW_LIGHT_ACTIVE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/callbacks/DreamStatusBarStateCallback.java b/packages/SystemUI/src/com/android/systemui/dreams/callbacks/DreamStatusBarStateCallback.java
new file mode 100644
index 0000000..c8c9470
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/callbacks/DreamStatusBarStateCallback.java
@@ -0,0 +1,46 @@
+/*
+ * 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.dreams.callbacks;
+
+import android.util.Log;
+
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+
+import javax.inject.Inject;
+
+/**
+ * A callback that informs {@link SysuiStatusBarStateController} when the dream state has changed.
+ */
+public class DreamStatusBarStateCallback implements Monitor.Callback {
+ private static final String TAG = "DreamStatusBarCallback";
+
+ private final SysuiStatusBarStateController mStateController;
+
+ @Inject
+ public DreamStatusBarStateCallback(SysuiStatusBarStateController statusBarStateController) {
+ mStateController = statusBarStateController;
+ }
+
+ @Override
+ public void onConditionsChanged(boolean allConditionsMet) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConditionChanged:" + allConditionsMet);
+ }
+
+ mStateController.setIsDreaming(allConditionsMet);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
new file mode 100644
index 0000000..2befce7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
@@ -0,0 +1,73 @@
+/*
+ * 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.dreams.conditions;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.text.TextUtils;
+
+import com.android.systemui.shared.condition.Condition;
+
+import javax.inject.Inject;
+
+/**
+ * {@link DreamCondition} provides a signal when a dream begins and ends.
+ */
+public class DreamCondition extends Condition {
+ private final Context mContext;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ processIntent(intent);
+ }
+ };
+
+ @Inject
+ public DreamCondition(Context context) {
+ mContext = context;
+ }
+
+ private void processIntent(Intent intent) {
+ // In the case of a non-existent sticky broadcast, ignore when there is no intent.
+ if (intent == null) {
+ return;
+ }
+ if (TextUtils.equals(intent.getAction(), Intent.ACTION_DREAMING_STARTED)) {
+ updateCondition(true);
+ } else if (TextUtils.equals(intent.getAction(), Intent.ACTION_DREAMING_STOPPED)) {
+ updateCondition(false);
+ } else {
+ throw new IllegalStateException("unexpected intent:" + intent);
+ }
+ }
+
+ @Override
+ protected void start() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_DREAMING_STARTED);
+ filter.addAction(Intent.ACTION_DREAMING_STOPPED);
+ final Intent stickyIntent = mContext.registerReceiver(mReceiver, filter);
+ processIntent(stickyIntent);
+ }
+
+ @Override
+ protected void stop() {
+ mContext.unregisterReceiver(mReceiver);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index 695b59a..b8b459e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -226,6 +226,15 @@
return;
}
+ // When we stop monitoring touches, we must ensure that all active touch sessions and
+ // descendants informed of the removal so any cleanup for active tracking can proceed.
+ mExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> {
+ while (touchSession != null) {
+ touchSession.onRemoved();
+ touchSession = touchSession.getPredecessor();
+ }
+ }));
+
mCurrentInputSession.dispose();
mCurrentInputSession = null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index b94d781..dc7fc28 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -21,6 +21,7 @@
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.InitializationChecker
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -34,7 +35,8 @@
private val commandRegistry: CommandRegistry,
private val flagCommand: FlagCommand,
private val featureFlags: FeatureFlagsDebug,
- private val broadcastSender: BroadcastSender
+ private val broadcastSender: BroadcastSender,
+ private val initializationChecker: InitializationChecker
) : CoreStartable {
init {
@@ -46,8 +48,11 @@
override fun start() {
featureFlags.init()
commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
- val intent = Intent(FlagManager.ACTION_SYSUI_STARTED)
- broadcastSender.sendBroadcast(intent)
+ if (initializationChecker.initializeComponents()) {
+ // protected broadcast should only be sent for the main process
+ val intent = Intent(FlagManager.ACTION_SYSUI_STARTED)
+ broadcastSender.sendBroadcast(intent)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ddc69f9..47c41fe 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -198,8 +198,7 @@
/** A different path for unocclusion transitions back to keyguard */
// TODO(b/262859270): Tracking Bug
- @JvmField
- val UNOCCLUSION_TRANSITION = unreleasedFlag(223, "unocclusion_transition", teamfood = true)
+ @JvmField val UNOCCLUSION_TRANSITION = releasedFlag(223, "unocclusion_transition")
// flag for controlling auto pin confirmation and material u shapes in bouncer
@JvmField
@@ -220,6 +219,15 @@
val WALLPAPER_FULLSCREEN_PREVIEW =
unreleasedFlag(227, "wallpaper_fullscreen_preview", teamfood = true)
+ /** Whether the long-press gesture to open wallpaper picker is enabled. */
+ // TODO(b/266242192): Tracking Bug
+ @JvmField
+ val LOCK_SCREEN_LONG_PRESS_ENABLED =
+ unreleasedFlag(
+ 228,
+ "lock_screen_long_press_enabled",
+ )
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -229,7 +237,6 @@
// TODO(b/254513100): Tracking Bug
val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
releasedFlag(401, "smartspace_shared_element_transition_enabled")
- val SMARTSPACE = resourceBooleanFlag(402, R.bool.flag_smartspace, "smartspace")
// TODO(b/258517050): Clean up after the feature is launched.
@JvmField
@@ -267,10 +274,11 @@
// 600- status bar
// TODO(b/256614753): Tracking Bug
- val NEW_STATUS_BAR_MOBILE_ICONS = unreleasedFlag(606, "new_status_bar_mobile_icons")
+ val NEW_STATUS_BAR_MOBILE_ICONS =
+ unreleasedFlag(606, "new_status_bar_mobile_icons", teamfood = true)
// TODO(b/256614210): Tracking Bug
- val NEW_STATUS_BAR_WIFI_ICON = unreleasedFlag(607, "new_status_bar_wifi_icon")
+ val NEW_STATUS_BAR_WIFI_ICON = unreleasedFlag(607, "new_status_bar_wifi_icon", teamfood = true)
// TODO(b/256614751): Tracking Bug
val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
@@ -438,6 +446,18 @@
teamfood = false
)
+ // TODO(b/198643358): Tracking bug
+ @Keep
+ @JvmField
+ val ENABLE_PIP_SIZE_LARGE_SCREEN =
+ sysPropBooleanFlag(1114, "persist.wm.debug.enable_pip_size_large_screen", default = false)
+
+ // TODO(b/265998256): Tracking bug
+ @Keep
+ @JvmField
+ val ENABLE_PIP_APP_ICON_OVERLAY =
+ sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = false)
+
// 1200 - predictive back
@Keep
@JvmField
@@ -493,6 +513,9 @@
val SCREENSHOT_WORK_PROFILE_POLICY =
unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
+ // TODO(b/264916608): Tracking Bug
+ @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata")
+
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
@@ -511,7 +534,6 @@
unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
// 1700 - clipboard
- @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
// 1800 - shade container
@@ -524,7 +546,7 @@
@JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag")
// 2000 - device controls
- @Keep @JvmField val USE_APP_PANELS = unreleasedFlag(2000, "use_app_panels", teamfood = true)
+ @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels", teamfood = true)
@JvmField
val APP_PANELS_ALL_APPS_ALLOWED =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index 482138e..680c504 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -31,10 +31,12 @@
import android.util.Log
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.runBlocking
class CustomizationProvider :
@@ -42,6 +44,7 @@
@Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
@Inject lateinit var previewManager: KeyguardRemotePreviewManager
+ @Inject @Main lateinit var mainDispatcher: CoroutineDispatcher
private lateinit var contextAvailableCallback: ContextAvailableCallback
@@ -138,12 +141,14 @@
selectionArgs: Array<out String>?,
sortOrder: String?,
): Cursor? {
- return when (uriMatcher.match(uri)) {
- MATCH_CODE_ALL_AFFORDANCES -> runBlocking { queryAffordances() }
- MATCH_CODE_ALL_SLOTS -> querySlots()
- MATCH_CODE_ALL_SELECTIONS -> runBlocking { querySelections() }
- MATCH_CODE_ALL_FLAGS -> queryFlags()
- else -> null
+ return runBlocking(mainDispatcher) {
+ when (uriMatcher.match(uri)) {
+ MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
+ MATCH_CODE_ALL_SLOTS -> querySlots()
+ MATCH_CODE_ALL_SELECTIONS -> querySelections()
+ MATCH_CODE_ALL_FLAGS -> queryFlags()
+ else -> null
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index f6e6d6b..5a9f775 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -19,6 +19,7 @@
import android.app.StatusBarManager
import android.content.Context
+import android.content.pm.PackageManager
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.camera.CameraGestureHelper
@@ -26,7 +27,6 @@
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.statusbar.StatusBarState
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -37,6 +37,7 @@
@Inject
constructor(
@Application private val context: Context,
+ private val packageManager: PackageManager,
private val cameraGestureHelper: Lazy<CameraGestureHelper>,
) : KeyguardQuickAffordanceConfig {
@@ -79,6 +80,6 @@
}
private fun isLaunchable(): Boolean {
- return cameraGestureHelper.get().canCameraGestureBeLaunched(StatusBarState.KEYGUARD)
+ return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 08edbc6..b3a9cf5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -22,14 +22,18 @@
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
+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.stateIn
/** Encapsulates state about device entry fingerprint auth mechanism. */
interface DeviceEntryFingerprintAuthRepository {
/** Whether the device entry fingerprint auth is locked out. */
- val isLockedOut: Flow<Boolean>
+ val isLockedOut: StateFlow<Boolean>
}
/**
@@ -44,29 +48,34 @@
@Inject
constructor(
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ @Application scope: CoroutineScope,
) : DeviceEntryFingerprintAuthRepository {
- override val isLockedOut: Flow<Boolean> = conflatedCallbackFlow {
- val sendLockoutUpdate =
- fun() {
- trySendWithFailureLogging(
- keyguardUpdateMonitor.isFingerprintLockedOut,
- TAG,
- "onLockedOutStateChanged"
- )
- }
- val callback =
- object : KeyguardUpdateMonitorCallback() {
- override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) {
- if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
- sendLockoutUpdate()
+ override val isLockedOut: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val sendLockoutUpdate =
+ fun() {
+ trySendWithFailureLogging(
+ keyguardUpdateMonitor.isFingerprintLockedOut,
+ TAG,
+ "onLockedOutStateChanged"
+ )
}
- }
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onLockedOutStateChanged(
+ biometricSourceType: BiometricSourceType?
+ ) {
+ if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+ sendLockoutUpdate()
+ }
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ sendLockoutUpdate()
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
- keyguardUpdateMonitor.registerCallback(callback)
- sendLockoutUpdate()
- awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
- }
+ .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false)
companion object {
const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 41574d1..3e17136 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -26,7 +26,6 @@
import com.android.systemui.log.dagger.BouncerLog
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.phone.KeyguardBouncer
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index d99af90..db95562 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -148,6 +148,9 @@
/** Source of the most recent biometric unlock, such as fingerprint or face. */
val biometricUnlockSource: Flow<BiometricUnlockSource?>
+ /** Whether quick settings or quick-quick settings is visible. */
+ val isQuickSettingsVisible: Flow<Boolean>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -172,6 +175,9 @@
* Returns whether the keyguard bottom area should be constrained to the top of the lock icon
*/
fun isUdfpsSupported(): Boolean
+
+ /** Sets whether quick settings or quick-quick settings is visible. */
+ fun setQuickSettingsVisible(isVisible: Boolean)
}
/** Encapsulates application state for the keyguard. */
@@ -581,6 +587,9 @@
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
+ private val _isQuickSettingsVisible = MutableStateFlow(false)
+ override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate
}
@@ -595,6 +604,10 @@
override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
+ override fun setQuickSettingsVisible(isVisible: Boolean) {
+ _isQuickSettingsVisible.value = isVisible
+ }
+
private fun statusBarStateIntToObject(value: Int): StatusBarState {
return when (value) {
0 -> StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 4639597..cc99eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -32,4 +32,9 @@
fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
@Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository
+
+ @Binds
+ fun deviceEntryFingerprintAuthRepository(
+ impl: DeviceEntryFingerprintAuthRepositoryImpl
+ ): DeviceEntryFingerprintAuthRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index 28c0b28..6020ef8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.BiometricRepository
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
import com.android.systemui.util.time.SystemClock
@@ -34,6 +35,7 @@
constructor(
private val bouncerRepository: KeyguardBouncerRepository,
private val biometricRepository: BiometricRepository,
+ private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val systemClock: SystemClock,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
featureFlags: FeatureFlags,
@@ -99,7 +101,8 @@
bouncerRepository.isAlternateBouncerUIAvailable.value &&
biometricRepository.isFingerprintEnrolled.value &&
biometricRepository.isStrongBiometricAllowed.value &&
- biometricRepository.isFingerprintEnabledByDevicePolicy.value
+ biometricRepository.isFingerprintEnabledByDevicePolicy.value &&
+ !deviceEntryFingerprintAuthRepository.isLockedOut.value
} else {
legacyAlternateBouncer != null &&
keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 14f918d..b5bcd45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -45,6 +45,27 @@
override fun start() {
listenForGoneToAodOrDozing()
listenForGoneToDreaming()
+ listenForGoneToLockscreen()
+ }
+
+ // Primarily for when the user chooses to lock down the device
+ private fun listenForGoneToLockscreen() {
+ scope.launch {
+ keyguardInteractor.isKeyguardShowing
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (isKeyguardShowing, lastStartedStep) ->
+ if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.GONE,
+ KeyguardState.LOCKSCREEN,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
}
private fun listenForGoneToDreaming() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 4cf56fe..3d39da6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -155,6 +155,11 @@
}
}
+ /** Sets whether quick settings or quick-quick settings is visible. */
+ fun setQuickSettingsVisible(isVisible: Boolean) {
+ repository.setQuickSettingsVisible(isVisible)
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
new file mode 100644
index 0000000..6525a13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
@@ -0,0 +1,187 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.shared.model.Position
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.domain.model.KeyguardSettingsPopupMenuModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.plugins.ActivityStarter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/** Business logic for use-cases related to the keyguard long-press feature. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardLongPressInteractor
+@Inject
+constructor(
+ @Application unsafeContext: Context,
+ @Application scope: CoroutineScope,
+ transitionInteractor: KeyguardTransitionInteractor,
+ repository: KeyguardRepository,
+ private val activityStarter: ActivityStarter,
+ private val logger: UiEventLogger,
+ private val featureFlags: FeatureFlags,
+ broadcastDispatcher: BroadcastDispatcher,
+) {
+ private val appContext = unsafeContext.applicationContext
+
+ private val _isLongPressHandlingEnabled: StateFlow<Boolean> =
+ if (isFeatureEnabled()) {
+ combine(
+ transitionInteractor.finishedKeyguardState.map {
+ it == KeyguardState.LOCKSCREEN
+ },
+ repository.isQuickSettingsVisible,
+ ) { isFullyTransitionedToLockScreen, isQuickSettingsVisible ->
+ isFullyTransitionedToLockScreen && !isQuickSettingsVisible
+ }
+ } else {
+ flowOf(false)
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /** Whether the long-press handling feature should be enabled. */
+ val isLongPressHandlingEnabled: Flow<Boolean> = _isLongPressHandlingEnabled
+
+ private val _menu = MutableStateFlow<KeyguardSettingsPopupMenuModel?>(null)
+ /** Model for a menu that should be shown; `null` when no menu should be shown. */
+ val menu: Flow<KeyguardSettingsPopupMenuModel?> =
+ isLongPressHandlingEnabled.flatMapLatest { isEnabled ->
+ if (isEnabled) {
+ _menu
+ } else {
+ flowOf(null)
+ }
+ }
+
+ init {
+ if (isFeatureEnabled()) {
+ broadcastDispatcher
+ .broadcastFlow(
+ IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+ )
+ .onEach { hideMenu() }
+ .launchIn(scope)
+ }
+ }
+
+ /** Notifies that the user has long-pressed on the lock screen. */
+ fun onLongPress(x: Int, y: Int) {
+ if (!_isLongPressHandlingEnabled.value) {
+ return
+ }
+
+ showMenu(
+ x = x,
+ y = y,
+ )
+ }
+
+ private fun isFeatureEnabled(): Boolean {
+ return featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED) &&
+ featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI)
+ }
+
+ /** Updates application state to ask to show the menu at the given coordinates. */
+ private fun showMenu(
+ x: Int,
+ y: Int,
+ ) {
+ _menu.value =
+ KeyguardSettingsPopupMenuModel(
+ position =
+ Position(
+ x = x,
+ y = y,
+ ),
+ onClicked = {
+ hideMenu()
+ navigateToLockScreenSettings()
+ },
+ onDismissed = { hideMenu() },
+ )
+ logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN)
+ }
+
+ /** Updates application state to ask to hide the menu. */
+ private fun hideMenu() {
+ _menu.value = null
+ }
+
+ /** Opens the wallpaper picker screen after the device is unlocked by the user. */
+ private fun navigateToLockScreenSettings() {
+ logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
+ activityStarter.dismissKeyguardThenExecute(
+ /* action= */ {
+ appContext.startActivity(
+ Intent(Intent.ACTION_SET_WALLPAPER).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ appContext
+ .getString(R.string.config_wallpaperPickerPackage)
+ .takeIf { it.isNotEmpty() }
+ ?.let { packageName -> setPackage(packageName) }
+ }
+ )
+ true
+ },
+ /* cancel= */ {},
+ /* afterKeyguardGone= */ true,
+ )
+ }
+
+ enum class LogEvents(
+ private val _id: Int,
+ ) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The lock screen was long-pressed and we showed the settings popup menu.")
+ LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN(1292),
+ @UiEvent(doc = "The lock screen long-press popup menu was clicked.")
+ LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED(1293),
+ ;
+
+ override fun getId() = _id
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt
new file mode 100644
index 0000000..7c61e71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.keyguard.domain.model
+
+import com.android.systemui.common.shared.model.Position
+
+/** Models a settings popup menu for the lock screen. */
+data class KeyguardSettingsPopupMenuModel(
+ /** Where the menu should be anchored, roughly in screen space. */
+ val position: Position,
+ /** Callback to invoke when the menu gets clicked by the user. */
+ val onClicked: () -> Unit,
+ /** Callback to invoke when the menu gets dismissed by the user. */
+ val onDismissed: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt
index bb5ac84..8222dd5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt
@@ -25,4 +25,5 @@
*/
const val EXPANSION_HIDDEN = 1f
const val EXPANSION_VISIBLE = 0f
+ const val ALPHA_EXPANSION_THRESHOLD = 0.95f
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index d020529..e9d7a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -66,6 +66,8 @@
object KeyguardBottomAreaViewBinder {
private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
+ private const val SCALE_SELECTED_BUTTON = 1.23f
+ private const val DIM_ALPHA = 0.3f
/**
* Defines interface for an object that acts as the binding between the view and its view-model.
@@ -315,6 +317,12 @@
} else {
null
}
+ view
+ .animate()
+ .scaleX(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
+ .scaleY(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
+ .alpha(if (viewModel.isDimmed) DIM_ALPHA else 1f)
+ .start()
view.isClickable = viewModel.isClickable
if (viewModel.isClickable) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt
new file mode 100644
index 0000000..d85682b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.keyguard.ui.binder
+
+import android.annotation.SuppressLint
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.PopupWindow
+import com.android.systemui.R
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsPopupMenuViewModel
+
+object KeyguardLongPressPopupViewBinder {
+ @SuppressLint("InflateParams") // We don't care that the parent is null.
+ fun createAndShow(
+ container: View,
+ viewModel: KeyguardSettingsPopupMenuViewModel,
+ onDismissed: () -> Unit,
+ ): () -> Unit {
+ val contentView: View =
+ LayoutInflater.from(container.context)
+ .inflate(
+ R.layout.keyguard_settings_popup_menu,
+ null,
+ )
+
+ contentView.setOnClickListener { viewModel.onClicked() }
+ IconViewBinder.bind(
+ icon = viewModel.icon,
+ view = contentView.requireViewById(R.id.icon),
+ )
+ TextViewBinder.bind(
+ view = contentView.requireViewById(R.id.text),
+ viewModel = viewModel.text,
+ )
+
+ val popupWindow =
+ PopupWindow(container.context).apply {
+ windowLayoutType = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
+ setBackgroundDrawable(null)
+ animationStyle = com.android.internal.R.style.Animation_Dialog
+ isOutsideTouchable = true
+ isFocusable = true
+ setContentView(contentView)
+ setOnDismissListener { onDismissed() }
+ contentView.measure(
+ View.MeasureSpec.makeMeasureSpec(
+ 0,
+ View.MeasureSpec.UNSPECIFIED,
+ ),
+ View.MeasureSpec.makeMeasureSpec(
+ 0,
+ View.MeasureSpec.UNSPECIFIED,
+ ),
+ )
+ showAtLocation(
+ container,
+ Gravity.NO_GRAVITY,
+ viewModel.position.x - contentView.measuredWidth / 2,
+ viewModel.position.y -
+ contentView.measuredHeight -
+ container.context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_long_press_settings_popup_vertical_offset
+ ),
+ )
+ }
+
+ return { popupWindow.dismiss() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
new file mode 100644
index 0000000..ef3f242
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.keyguard.ui.binder
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.common.ui.view.LongPressHandlingView
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import kotlinx.coroutines.launch
+
+object KeyguardLongPressViewBinder {
+ /**
+ * Drives UI for the lock screen long-press feature.
+ *
+ * @param view The view that listens for long-presses.
+ * @param viewModel The view-model that models the UI state.
+ * @param onSingleTap A callback to invoke when the system decides that there was a single tap.
+ * @param falsingManager [FalsingManager] for making sure the long-press didn't just happen in
+ * the user's pocket.
+ */
+ @JvmStatic
+ fun bind(
+ view: LongPressHandlingView,
+ viewModel: KeyguardLongPressViewModel,
+ onSingleTap: () -> Unit,
+ falsingManager: FalsingManager,
+ ) {
+ view.listener =
+ object : LongPressHandlingView.Listener {
+ override fun onLongPressDetected(view: View, x: Int, y: Int) {
+ if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+ return
+ }
+
+ viewModel.onLongPress(
+ x = x,
+ y = y,
+ )
+ }
+
+ override fun onSingleTapDetected(view: View) {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return
+ }
+
+ onSingleTap()
+ }
+ }
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.isLongPressHandlingEnabled.collect { isEnabled ->
+ view.setLongPressHandlingEnabled(isEnabled)
+ }
+ }
+
+ launch {
+ var dismissMenu: (() -> Unit)? = null
+
+ viewModel.menu.collect { menuOrNull ->
+ if (menuOrNull != null) {
+ dismissMenu =
+ KeyguardLongPressPopupViewBinder.createAndShow(
+ container = view,
+ viewModel = menuOrNull,
+ onDismissed = menuOrNull.onDismissed,
+ )
+ } else {
+ dismissMenu?.invoke()
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index a5ae8ba5..8808574 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -24,7 +24,6 @@
import android.hardware.display.DisplayManager
import android.os.Bundle
import android.os.IBinder
-import android.view.Gravity
import android.view.LayoutInflater
import android.view.SurfaceControlViewHost
import android.view.View
@@ -65,6 +64,11 @@
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT)
+ private val shouldHighlightSelectedAffordance: Boolean =
+ bundle.getBoolean(
+ KeyguardQuickAffordancePreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
+ false,
+ )
private var host: SurfaceControlViewHost
@@ -82,6 +86,7 @@
bundle.getString(
KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
),
+ shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
runBlocking(mainDispatcher) {
host =
@@ -154,8 +159,7 @@
bottomAreaView,
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.WRAP_CONTENT,
- Gravity.BOTTOM,
+ FrameLayout.LayoutParams.MATCH_PARENT,
),
)
}
@@ -195,7 +199,13 @@
?.events
?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
clockView?.let { parentView.removeView(it) }
- clockView = clockController.clock?.largeClock?.view?.apply { parentView.addView(this) }
+ clockView =
+ clockController.clock?.largeClock?.view?.apply {
+ if (shouldHighlightSelectedAffordance) {
+ alpha = DIM_ALPHA
+ }
+ parentView.addView(this)
+ }
}
companion object {
@@ -203,5 +213,7 @@
private const val KEY_VIEW_WIDTH = "width"
private const val KEY_VIEW_HEIGHT = "height"
private const val KEY_DISPLAY_ID = "display_id"
+
+ private const val DIM_ALPHA = 0.3f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 5d85680..1e3b60c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -45,12 +45,17 @@
private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
private val burnInHelperWrapper: BurnInHelperWrapper,
) {
+ data class PreviewMode(
+ val isInPreviewMode: Boolean = false,
+ val shouldHighlightSelectedAffordance: Boolean = false,
+ )
+
/**
* Whether this view-model instance is powering the preview experience that renders exclusively
* in the wallpaper picker application. This should _always_ be `false` for the real lock screen
* experience.
*/
- private val isInPreviewMode = MutableStateFlow(false)
+ private val previewMode = MutableStateFlow(PreviewMode())
/**
* ID of the slot that's currently selected in the preview that renders exclusively in the
@@ -87,8 +92,8 @@
keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
/** An observable for the alpha level for the entire bottom area. */
val alpha: Flow<Float> =
- isInPreviewMode.flatMapLatest { isInPreviewMode ->
- if (isInPreviewMode) {
+ previewMode.flatMapLatest {
+ if (it.isInPreviewMode) {
flowOf(1f)
} else {
bottomAreaInteractor.alpha.distinctUntilChanged()
@@ -129,9 +134,18 @@
* lock screen.
*
* @param initiallySelectedSlotId The ID of the initial slot to render as the selected one.
+ * @param shouldHighlightSelectedAffordance Whether the selected quick affordance should be
+ * highlighted (while all others are dimmed to make the selected one stand out).
*/
- fun enablePreviewMode(initiallySelectedSlotId: String?) {
- isInPreviewMode.value = true
+ fun enablePreviewMode(
+ initiallySelectedSlotId: String?,
+ shouldHighlightSelectedAffordance: Boolean,
+ ) {
+ previewMode.value =
+ PreviewMode(
+ isInPreviewMode = true,
+ shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
+ )
onPreviewSlotSelected(
initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
)
@@ -150,9 +164,9 @@
private fun button(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> {
- return isInPreviewMode.flatMapLatest { isInPreviewMode ->
+ return previewMode.flatMapLatest { previewMode ->
combine(
- if (isInPreviewMode) {
+ if (previewMode.isInPreviewMode) {
quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
} else {
quickAffordanceInteractor.quickAffordance(position = position)
@@ -161,11 +175,18 @@
areQuickAffordancesFullyOpaque,
selectedPreviewSlotId,
) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId ->
+ val isSelected = selectedPreviewSlotId == position.toSlotId()
model.toViewModel(
- animateReveal = !isInPreviewMode && animateReveal,
- isClickable = isFullyOpaque && !isInPreviewMode,
+ animateReveal = !previewMode.isInPreviewMode && animateReveal,
+ isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
isSelected =
- (isInPreviewMode && selectedPreviewSlotId == position.toSlotId()),
+ previewMode.isInPreviewMode &&
+ previewMode.shouldHighlightSelectedAffordance &&
+ isSelected,
+ isDimmed =
+ previewMode.isInPreviewMode &&
+ previewMode.shouldHighlightSelectedAffordance &&
+ !isSelected,
)
}
.distinctUntilChanged()
@@ -176,6 +197,7 @@
animateReveal: Boolean,
isClickable: Boolean,
isSelected: Boolean,
+ isDimmed: Boolean,
): KeyguardQuickAffordanceViewModel {
return when (this) {
is KeyguardQuickAffordanceModel.Visible ->
@@ -194,6 +216,7 @@
isActivated = activationState is ActivationState.Active,
isSelected = isSelected,
useLongPress = quickAffordanceInteractor.useLongPress,
+ isDimmed = isDimmed,
)
is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
new file mode 100644
index 0000000..d896390
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Models UI state to support the lock screen long-press feature. */
+class KeyguardLongPressViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardLongPressInteractor,
+) {
+
+ /** Whether the long-press handling feature should be enabled. */
+ val isLongPressHandlingEnabled: Flow<Boolean> = interactor.isLongPressHandlingEnabled
+
+ /** View-model for a menu that should be shown; `null` when no menu should be shown. */
+ val menu: Flow<KeyguardSettingsPopupMenuViewModel?> =
+ interactor.menu.map { model ->
+ model?.let {
+ KeyguardSettingsPopupMenuViewModel(
+ icon =
+ Icon.Resource(
+ res = R.drawable.ic_settings,
+ contentDescription = null,
+ ),
+ text =
+ Text.Resource(
+ res = R.string.lock_screen_settings,
+ ),
+ position = model.position,
+ onClicked = model.onClicked,
+ onDismissed = model.onDismissed,
+ )
+ }
+ }
+
+ /** Notifies that the user has long-pressed on the lock screen. */
+ fun onLongPress(
+ x: Int,
+ y: Int,
+ ) {
+ interactor.onLongPress(
+ x = x,
+ y = y,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index cf3a6da..cb68a82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -31,6 +31,7 @@
val isActivated: Boolean = false,
val isSelected: Boolean = false,
val useLongPress: Boolean = false,
+ val isDimmed: Boolean = false,
) {
data class OnClickedParameters(
val configKey: String,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt
new file mode 100644
index 0000000..0571b05
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.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.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Position
+import com.android.systemui.common.shared.model.Text
+
+/** Models the UI state of a keyguard settings popup menu. */
+data class KeyguardSettingsPopupMenuViewModel(
+ val icon: Icon,
+ val text: Text,
+ /** Where the menu should be anchored, roughly in screen space. */
+ val position: Position,
+ /** Callback to invoke when the menu gets clicked by the user. */
+ val onClicked: () -> Unit,
+ /** Callback to invoke when the menu gets dismissed by the user. */
+ val onDismissed: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 15306f9..6c6f7e9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -200,28 +200,6 @@
}
/**
- * Provides a logging buffer for logs related to the media tap-to-transfer chip on the sender
- * device. See {@link com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger}.
- */
- @Provides
- @SysUISingleton
- @MediaTttSenderLogBuffer
- public static LogBuffer provideMediaTttSenderLogBuffer(LogBufferFactory factory) {
- return factory.create("MediaTttSender", 20);
- }
-
- /**
- * Provides a logging buffer for logs related to the media tap-to-transfer chip on the receiver
- * device. See {@link com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger}.
- */
- @Provides
- @SysUISingleton
- @MediaTttReceiverLogBuffer
- public static LogBuffer provideMediaTttReceiverLogBuffer(LogBufferFactory factory) {
- return factory.create("MediaTttReceiver", 20);
- }
-
- /**
* Provides a logging buffer for logs related to the media mute-await connections. See
* {@link com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager}.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index bb833df..9ae4577 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -17,8 +17,7 @@
package com.android.systemui.media.dagger;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.log.dagger.MediaTttReceiverLogBuffer;
-import com.android.systemui.log.dagger.MediaTttSenderLogBuffer;
+import com.android.systemui.log.LogBufferFactory;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.media.controls.ui.MediaHost;
@@ -29,12 +28,9 @@
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
-import com.android.systemui.media.taptotransfer.receiver.ChipReceiverInfo;
-import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
+import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogBuffer;
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogBuffer;
import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo;
import java.util.Optional;
@@ -94,22 +90,22 @@
return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
}
+ /** Provides a logging buffer related to the media tap-to-transfer chip on the sender device. */
@Provides
@SysUISingleton
- @MediaTttSenderLogger
- static MediaTttLogger<ChipbarInfo> providesMediaTttSenderLogger(
- @MediaTttSenderLogBuffer LogBuffer buffer
- ) {
- return new MediaTttLogger<>("Sender", buffer);
+ @MediaTttSenderLogBuffer
+ static LogBuffer provideMediaTttSenderLogBuffer(LogBufferFactory factory) {
+ return factory.create("MediaTttSender", 30);
}
+ /**
+ * Provides a logging buffer related to the media tap-to-transfer chip on the receiver device.
+ */
@Provides
@SysUISingleton
- @MediaTttReceiverLogger
- static MediaTttLogger<ChipReceiverInfo> providesMediaTttReceiverLogger(
- @MediaTttReceiverLogBuffer LogBuffer buffer
- ) {
- return new MediaTttLogger<>("Receiver", buffer);
+ @MediaTttReceiverLogBuffer
+ static LogBuffer provideMediaTttReceiverLogBuffer(LogBufferFactory factory) {
+ return factory.create("MediaTttReceiver", 20);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
deleted file mode 100644
index 8aef938..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.taptotransfer.common
-
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
-import com.android.systemui.temporarydisplay.TemporaryViewLogger
-
-/**
- * A logger for media tap-to-transfer events.
- *
- * @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver".
- *
- * TODO(b/245610654): We should de-couple the sender and receiver loggers, since they're vastly
- * different experiences.
- */
-class MediaTttLogger<T : TemporaryViewInfo>(
- deviceTypeTag: String,
- buffer: LogBuffer
-) : TemporaryViewLogger<T>(buffer, BASE_TAG + deviceTypeTag) {
- /** Logs a change in the chip state for the given [mediaRouteId]. */
- fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) {
- buffer.log(
- tag,
- LogLevel.DEBUG,
- {
- str1 = stateName
- str2 = mediaRouteId
- str3 = packageName
- },
- { "State changed to $str1 for ID=$str2 package=$str3" }
- )
- }
-
- /**
- * Logs an error in trying to update to [displayState].
- *
- * [displayState] is either a [android.app.StatusBarManager.MediaTransferSenderState] or
- * a [android.app.StatusBarManager.MediaTransferReceiverState].
- */
- fun logStateChangeError(displayState: Int) {
- buffer.log(
- tag,
- LogLevel.ERROR,
- { int1 = displayState },
- { "Cannot display state=$int1; aborting" }
- )
- }
-
- /**
- * Logs an invalid sender state transition error in trying to update to [desiredState].
- *
- * @param currentState the previous state of the chip.
- * @param desiredState the new state of the chip.
- */
- fun logInvalidStateTransitionError(
- currentState: String,
- desiredState: String
- ) {
- buffer.log(
- tag,
- LogLevel.ERROR,
- {
- str1 = currentState
- str2 = desiredState
- },
- { "Cannot display state=$str2 after state=$str1; invalid transition" }
- )
- }
-
- /** Logs that we couldn't find information for [packageName]. */
- fun logPackageNotFound(packageName: String) {
- buffer.log(
- tag,
- LogLevel.DEBUG,
- { str1 = packageName },
- { "Package $str1 could not be found" }
- )
- }
-
- /**
- * Logs that a removal request has been bypassed (ignored).
- *
- * @param removalReason the reason that the chip removal was requested.
- * @param bypassReason the reason that the request was bypassed.
- */
- fun logRemovalBypass(removalReason: String, bypassReason: String) {
- buffer.log(
- tag,
- LogLevel.DEBUG,
- {
- str1 = removalReason
- str2 = bypassReason
- },
- { "Chip removal requested due to $str1; however, removal was ignored because $str2" })
- }
-}
-
-private const val BASE_TAG = "MediaTtt"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
new file mode 100644
index 0000000..0e839c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.common
+
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+
+/** A helper for logging media tap-to-transfer events. */
+object MediaTttLoggerUtils {
+ fun logStateChange(
+ buffer: LogBuffer,
+ tag: String,
+ stateName: String,
+ mediaRouteId: String,
+ packageName: String?,
+ ) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = stateName
+ str2 = mediaRouteId
+ str3 = packageName
+ },
+ { "State changed to $str1 for ID=$str2 package=$str3" }
+ )
+ }
+
+ fun logStateChangeError(buffer: LogBuffer, tag: String, displayState: Int) {
+ buffer.log(
+ tag,
+ LogLevel.ERROR,
+ { int1 = displayState },
+ { "Cannot display state=$int1; aborting" }
+ )
+ }
+
+ fun logPackageNotFound(buffer: LogBuffer, tag: String, packageName: String) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ { str1 = packageName },
+ { "Package $str1 could not be found" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 066c185..a3ae943 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -25,7 +25,6 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.TintedIcon
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
@@ -43,12 +42,13 @@
* default name and icon if we can't find the app name/icon.
*
* @param appPackageName the package name of the app playing the media.
- * @param logger the logger to use for any errors.
+ * @param onPackageNotFoundException a function run if a
+ * [PackageManager.NameNotFoundException] occurs.
*/
fun getIconInfoFromPackageName(
context: Context,
appPackageName: String?,
- logger: MediaTttLogger<out TemporaryViewInfo>
+ onPackageNotFoundException: () -> Unit,
): IconInfo {
if (appPackageName != null) {
val packageManager = context.packageManager
@@ -70,7 +70,7 @@
isAppIcon = true
)
} catch (e: PackageManager.NameNotFoundException) {
- logger.logPackageNotFound(appPackageName)
+ onPackageNotFoundException.invoke()
}
}
return IconInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 60dd5da..34bf74fa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -40,7 +40,6 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttIcon
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -65,7 +64,7 @@
open class MediaTttChipControllerReceiver @Inject constructor(
private val commandQueue: CommandQueue,
context: Context,
- @MediaTttReceiverLogger logger: MediaTttLogger<ChipReceiverInfo>,
+ logger: MediaTttReceiverLogger,
windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
@@ -79,7 +78,7 @@
wakeLockBuilder: WakeLock.Builder,
systemClock: SystemClock,
private val rippleController: MediaTttReceiverRippleController,
-) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger<ChipReceiverInfo>>(
+) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttReceiverLogger>(
context,
logger,
windowManager,
@@ -173,9 +172,10 @@
}
override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
- var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
- context, newInfo.routeInfo.clientPackageName, logger
- )
+ val packageName = newInfo.routeInfo.clientPackageName
+ var iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, packageName) {
+ logger.logPackageNotFound(packageName)
+ }
if (newInfo.appNameOverride != null) {
iconInfo = iconInfo.copy(
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
rename to packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
index 1570d43..67e464c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.log.dagger;
+package com.android.systemui.media.taptotransfer.receiver;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -26,8 +26,7 @@
import javax.inject.Qualifier;
/**
- * A {@link LogBuffer} for
- * {@link com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger}.
+ * A {@link LogBuffer} for receiver logs.
*/
@Qualifier
@Documented
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
index 54fc48d..b0c6257 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
@@ -13,14 +13,44 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.systemui.media.taptotransfer.receiver
-import java.lang.annotation.Documented
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
-import javax.inject.Qualifier
+import android.app.StatusBarManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.temporarydisplay.TemporaryViewLogger
+import javax.inject.Inject
-@Qualifier
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-annotation class MediaTttReceiverLogger
+/** A logger for all events related to the media tap-to-transfer receiver experience. */
+@SysUISingleton
+class MediaTttReceiverLogger
+@Inject
+constructor(
+ @MediaTttReceiverLogBuffer buffer: LogBuffer,
+) : TemporaryViewLogger<ChipReceiverInfo>(buffer, TAG) {
+
+ /** Logs a change in the chip state for the given [mediaRouteId]. */
+ fun logStateChange(
+ stateName: String,
+ mediaRouteId: String,
+ packageName: String?,
+ ) {
+ MediaTttLoggerUtils.logStateChange(buffer, TAG, stateName, mediaRouteId, packageName)
+ }
+
+ /** Logs an error in trying to update to [displayState]. */
+ fun logStateChangeError(@StatusBarManager.MediaTransferReceiverState displayState: Int) {
+ MediaTttLoggerUtils.logStateChangeError(buffer, TAG, displayState)
+ }
+
+ /** Logs that we couldn't find information for [packageName]. */
+ fun logPackageNotFound(packageName: String) {
+ MediaTttLoggerUtils.logPackageNotFound(buffer, TAG, packageName)
+ }
+
+ companion object {
+ private const val TAG = "MediaTttReceiver"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 902a10a0..89ca5d3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -23,11 +23,12 @@
import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.CoreStartable
+import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
@@ -35,6 +36,7 @@
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import java.io.PrintWriter
import javax.inject.Inject
/**
@@ -48,14 +50,13 @@
private val chipbarCoordinator: ChipbarCoordinator,
private val commandQueue: CommandQueue,
private val context: Context,
- @MediaTttSenderLogger private val logger: MediaTttLogger<ChipbarInfo>,
+ private val dumpManager: DumpManager,
+ private val logger: MediaTttSenderLogger,
private val mediaTttFlags: MediaTttFlags,
private val uiEventLogger: MediaTttSenderUiEventLogger,
-) : CoreStartable {
+) : CoreStartable, Dumpable {
- private var displayedState: ChipStateSender? = null
// A map to store current chip state per id.
- // TODO(b/265455911): Log whenever we add or remove from the store.
private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()
private val commandQueueCallbacks =
@@ -76,6 +77,7 @@
override fun start() {
if (mediaTttFlags.isMediaTttEnabled()) {
commandQueue.addCallback(commandQueueCallbacks)
+ dumpManager.registerNormalDumpable(this)
}
}
@@ -93,11 +95,11 @@
return
}
- val currentState = stateMap[routeInfo.id]
- if (!ChipStateSender.isValidStateTransition(currentState, chipState)) {
+ val currentStateForId: ChipStateSender? = stateMap[routeInfo.id]
+ if (!ChipStateSender.isValidStateTransition(currentStateForId, chipState)) {
// ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state.
logger.logInvalidStateTransitionError(
- currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
+ currentState = currentStateForId?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
chipState.name
)
return
@@ -105,30 +107,29 @@
uiEventLogger.logSenderStateChange(chipState)
if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
- // No need to store the state since it is the default state
- removeIdFromStore(routeInfo.id)
- // Return early if we're not displaying a chip anyway
- val currentDisplayedState = displayedState ?: return
+ // Return early if we're not displaying a chip for this ID anyway
+ if (currentStateForId == null) return
val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name
if (
- currentDisplayedState.transferStatus == TransferStatus.IN_PROGRESS ||
- currentDisplayedState.transferStatus == TransferStatus.SUCCEEDED
+ currentStateForId.transferStatus == TransferStatus.IN_PROGRESS ||
+ currentStateForId.transferStatus == TransferStatus.SUCCEEDED
) {
// Don't remove the chip if we're in progress or succeeded, since the user should
// still be able to see the status of the transfer.
logger.logRemovalBypass(
removalReason,
- bypassReason = "transferStatus=${currentDisplayedState.transferStatus.name}"
+ bypassReason = "transferStatus=${currentStateForId.transferStatus.name}"
)
return
}
- displayedState = null
+ // No need to store the state since it is the default state
+ removeIdFromStore(routeInfo.id, reason = removalReason)
chipbarCoordinator.removeView(routeInfo.id, removalReason)
} else {
stateMap[routeInfo.id] = chipState
- displayedState = chipState
+ logger.logStateMap(stateMap)
chipbarCoordinator.registerListener(displayListener)
chipbarCoordinator.displayView(
createChipbarInfo(
@@ -150,7 +151,7 @@
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
context: Context,
- logger: MediaTttLogger<ChipbarInfo>,
+ logger: MediaTttSenderLogger,
): ChipbarInfo {
val packageName = routeInfo.clientPackageName
val otherDeviceName =
@@ -159,12 +160,14 @@
} else {
routeInfo.name.toString()
}
+ val icon =
+ MediaTttUtils.getIconInfoFromPackageName(context, packageName) {
+ logger.logPackageNotFound(packageName)
+ }
return ChipbarInfo(
// Display the app's icon as the start icon
- startIcon =
- MediaTttUtils.getIconInfoFromPackageName(context, packageName, logger)
- .toTintedIcon(),
+ startIcon = icon.toTintedIcon(),
text = chipStateSender.getChipTextString(context, otherDeviceName),
endItem =
when (chipStateSender.endItem) {
@@ -231,12 +234,19 @@
}
private val displayListener =
- TemporaryViewDisplayController.Listener { id -> removeIdFromStore(id) }
+ TemporaryViewDisplayController.Listener { id, reason -> removeIdFromStore(id, reason) }
- private fun removeIdFromStore(id: String) {
+ private fun removeIdFromStore(id: String, reason: String) {
+ logger.logStateMapRemoval(id, reason)
stateMap.remove(id)
+ logger.logStateMap(stateMap)
if (stateMap.isEmpty()) {
chipbarCoordinator.unregisterListener(displayListener)
}
}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("Current sender states:")
+ pw.println(stateMap.toString())
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
rename to packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
index bf216c6..a262e97 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.log.dagger;
+package com.android.systemui.media.taptotransfer.sender;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -26,8 +26,7 @@
import javax.inject.Qualifier;
/**
- * A {@link LogBuffer} for
- * {@link com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger}.
+ * A {@link LogBuffer} for sender logs.
*/
@Qualifier
@Documented
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
index 4393af9..964a95b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
@@ -13,14 +13,102 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.systemui.media.taptotransfer.sender
-import java.lang.annotation.Documented
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
-import javax.inject.Qualifier
+import android.app.StatusBarManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
-@Qualifier
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-annotation class MediaTttSenderLogger
+/** A logger for all events related to the media tap-to-transfer sender experience. */
+@SysUISingleton
+class MediaTttSenderLogger
+@Inject
+constructor(
+ @MediaTttSenderLogBuffer private val buffer: LogBuffer,
+) {
+ /** Logs a change in the chip state for the given [mediaRouteId]. */
+ fun logStateChange(
+ stateName: String,
+ mediaRouteId: String,
+ packageName: String?,
+ ) {
+ MediaTttLoggerUtils.logStateChange(buffer, TAG, stateName, mediaRouteId, packageName)
+ }
+
+ /** Logs an error in trying to update to [displayState]. */
+ fun logStateChangeError(@StatusBarManager.MediaTransferSenderState displayState: Int) {
+ MediaTttLoggerUtils.logStateChangeError(buffer, TAG, displayState)
+ }
+
+ /** Logs that we couldn't find information for [packageName]. */
+ fun logPackageNotFound(packageName: String) {
+ MediaTttLoggerUtils.logPackageNotFound(buffer, TAG, packageName)
+ }
+
+ /**
+ * Logs an invalid sender state transition error in trying to update to [desiredState].
+ *
+ * @param currentState the previous state of the chip.
+ * @param desiredState the new state of the chip.
+ */
+ fun logInvalidStateTransitionError(currentState: String, desiredState: String) {
+ buffer.log(
+ TAG,
+ LogLevel.ERROR,
+ {
+ str1 = currentState
+ str2 = desiredState
+ },
+ { "Cannot display state=$str2 after state=$str1; invalid transition" }
+ )
+ }
+
+ /**
+ * Logs that a removal request has been bypassed (ignored).
+ *
+ * @param removalReason the reason that the chip removal was requested.
+ * @param bypassReason the reason that the request was bypassed.
+ */
+ fun logRemovalBypass(removalReason: String, bypassReason: String) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = removalReason
+ str2 = bypassReason
+ },
+ { "Chip removal requested due to $str1; however, removal was ignored because $str2" }
+ )
+ }
+
+ /** Logs the current contents of the state map. */
+ fun logStateMap(map: Map<String, ChipStateSender>) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = map.toString() },
+ { "Current sender states: $str1" }
+ )
+ }
+
+ /** Logs that [id] has been removed from the state map due to [reason]. */
+ fun logStateMapRemoval(id: String, reason: String) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = id
+ str2 = reason
+ },
+ { "State removal: id=$str1 reason=$str2" }
+ )
+ }
+
+ companion object {
+ private const val TAG = "MediaTttSender"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 08d1857..6bfe1a0 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,10 +17,12 @@
package com.android.systemui.notetask
import android.app.KeyguardManager
+import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.os.UserManager
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.util.kotlin.getOrNull
@@ -57,7 +59,7 @@
* If the keyguard is locked, notes will open as a full screen experience. A locked device has
* no contextual information which let us use the whole screen space available.
*
- * If no in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be
+ * If not in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be
* collapsed if the notes bubble is already opened.
*
* That will let users open other apps in full screen, and take contextual notes.
@@ -68,16 +70,23 @@
val bubbles = optionalBubbles.getOrNull() ?: return
val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
val userManager = optionalUserManager.getOrNull() ?: return
- val intent = intentResolver.resolveIntent() ?: return
// TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return
- if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
- context.startActivity(intent)
- } else {
- // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
- bubbles.showOrHideAppBubble(intent)
+ val intent = intentResolver.resolveIntent() ?: return
+
+ // TODO(b/266686199): We should handle when app not available. For now, we log.
+ try {
+ if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
+ context.startActivity(intent)
+ } else {
+ bubbles.showOrHideAppBubble(intent)
+ }
+ } catch (e: ActivityNotFoundException) {
+ val message =
+ "Activity not found for action: ${NoteTaskIntentResolver.ACTION_CREATE_NOTE}."
+ Log.e(TAG, message, e)
}
}
@@ -106,6 +115,8 @@
}
companion object {
+ private val TAG = NoteTaskController::class.simpleName.orEmpty()
+
// TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
const val NOTE_TASK_KEY_EVENT = 311
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
index 4b10d69..11dc1d7 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
@@ -16,70 +16,39 @@
package com.android.systemui.notetask
-import android.content.ComponentName
+import android.app.role.RoleManager
+import android.content.Context
import android.content.Intent
-import android.content.pm.ActivityInfo
-import android.content.pm.PackageManager
-import android.content.pm.PackageManager.ResolveInfoFlags
-import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
import javax.inject.Inject
-/**
- * Class responsible to query all apps and find one that can handle the [ACTION_CREATE_NOTE]. If
- * found, an [Intent] ready for be launched will be returned. Otherwise, returns null.
- *
- * TODO(b/248274123): should be revisited once the notes role is implemented.
- */
internal class NoteTaskIntentResolver
@Inject
constructor(
- private val packageManager: PackageManager,
+ private val context: Context,
+ private val roleManager: RoleManager,
) {
fun resolveIntent(): Intent? {
- val intent = Intent(ACTION_CREATE_NOTE)
- val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
- val infoList = packageManager.queryIntentActivities(intent, flags)
+ val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, context.user).firstOrNull()
- for (info in infoList) {
- val packageName = info.activityInfo.applicationInfo.packageName ?: continue
- val activityName = resolveActivityNameForNotesAction(packageName) ?: continue
+ if (packageName.isNullOrEmpty()) return null
- return Intent(ACTION_CREATE_NOTE)
- .setPackage(packageName)
- .setComponent(ComponentName(packageName, activityName))
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
-
- return null
- }
-
- private fun resolveActivityNameForNotesAction(packageName: String): String? {
- val intent = Intent(ACTION_CREATE_NOTE).setPackage(packageName)
- val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
- val resolveInfo = packageManager.resolveActivity(intent, flags)
-
- val activityInfo = resolveInfo?.activityInfo ?: return null
- if (activityInfo.name.isNullOrBlank()) return null
- if (!activityInfo.exported) return null
- if (!activityInfo.enabled) return null
- if (!activityInfo.showWhenLocked) return null
- if (!activityInfo.turnScreenOn) return null
-
- return activityInfo.name
+ return Intent(ACTION_CREATE_NOTE)
+ .setPackage(packageName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint was
+ // used to start it.
+ .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
}
companion object {
- // TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead.
+ // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead.
const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
// TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
- const val NOTE_ROLE = "android.app.role.NOTES"
+ const val ROLE_NOTES = "android.app.role.NOTES"
+
+ // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead.
+ const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
}
}
-
-private val ActivityInfo.showWhenLocked: Boolean
- get() = flags and ActivityInfo.FLAG_SHOW_WHEN_LOCKED != 0
-
-private val ActivityInfo.turnScreenOn: Boolean
- get() = flags and ActivityInfo.FLAG_TURN_SCREEN_ON != 0
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 22ce121..ec6a16a 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -51,7 +51,7 @@
featureFlags: FeatureFlags,
roleManager: RoleManager,
): Boolean {
- val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.NOTE_ROLE)
+ val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.ROLE_NOTES)
val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
return isRoleAvailable && isFeatureEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
index b48ea23..be93550 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -43,6 +43,7 @@
import javax.inject.Inject
private const val TAG = "AutoAddTracker"
+private const val DELIMITER = ","
/**
* Class to track tiles that have been auto-added
@@ -67,7 +68,7 @@
@GuardedBy("autoAdded")
private val autoAdded = ArraySet<String>()
- private var restoredTiles: Set<String>? = null
+ private var restoredTiles: Map<String, AutoTile>? = null
override val currentUserId: Int
get() = userId
@@ -98,25 +99,26 @@
when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) {
Settings.Secure.QS_TILES -> {
restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
- ?.split(",")
- ?.toSet()
+ ?.split(DELIMITER)
+ ?.mapIndexed(::AutoTile)
+ ?.associateBy(AutoTile::tileType)
?: run {
Log.w(TAG, "Null restored tiles for user $userId")
- emptySet()
+ emptyMap()
}
}
Settings.Secure.QS_AUTO_ADDED_TILES -> {
- restoredTiles?.let { tiles ->
+ restoredTiles?.let { restoredTiles ->
val restoredAutoAdded = intent
.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
- ?.split(",")
+ ?.split(DELIMITER)
?: emptyList()
val autoAddedBeforeRestore = intent
.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)
- ?.split(",")
+ ?.split(DELIMITER)
?: emptyList()
- val tilesToRemove = restoredAutoAdded.filter { it !in tiles }
+ val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles }
if (tilesToRemove.isNotEmpty()) {
qsHost.removeTiles(tilesToRemove)
}
@@ -180,6 +182,9 @@
registerBroadcastReceiver()
}
+ fun getRestoredTilePosition(tile: String): Int =
+ restoredTiles?.get(tile)?.index ?: QSTileHost.POSITION_AT_END
+
/**
* Returns `true` if the tile has been auto-added before
*/
@@ -196,12 +201,12 @@
*/
fun setTileAdded(tile: String) {
val tiles = synchronized(autoAdded) {
- if (autoAdded.add(tile)) {
- getTilesFromListLocked()
- } else {
- null
- }
+ if (autoAdded.add(tile)) {
+ getTilesFromListLocked()
+ } else {
+ null
}
+ }
tiles?.let { saveTiles(it) }
}
@@ -222,7 +227,7 @@
}
private fun getTilesFromListLocked(): String {
- return TextUtils.join(",", autoAdded)
+ return TextUtils.join(DELIMITER, autoAdded)
}
private fun saveTiles(tiles: String) {
@@ -245,7 +250,7 @@
private fun getAdded(): Collection<String> {
val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId)
- return current?.split(",") ?: emptySet()
+ return current?.split(DELIMITER) ?: emptySet()
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -281,4 +286,6 @@
)
}
}
+
+ private data class AutoTile(val index: Int, val tileType: String)
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 1151475..dd7ea76 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -30,6 +30,7 @@
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -652,13 +653,14 @@
}
private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
- boolean bouncerShowing, boolean isDozing, boolean panelExpanded) {
+ boolean bouncerShowing, boolean isDozing, boolean panelExpanded, boolean isDreaming) {
mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
keyguardShowing && !keyguardOccluded)
.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED,
keyguardShowing && keyguardOccluded)
.setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
.setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing)
+ .setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming)
.commitUpdate(mContext.getDisplayId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 017e57f..310baaf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -25,8 +25,22 @@
import com.android.systemui.R
object ActionIntentCreator {
+ /** @return a chooser intent to share the given URI. */
+ fun createShareIntent(uri: Uri) = createShareIntent(uri, null, null)
+
/** @return a chooser intent to share the given URI with the optional provided subject. */
- fun createShareIntent(uri: Uri, subject: String?): Intent {
+ fun createShareIntentWithSubject(uri: Uri, subject: String?) =
+ createShareIntent(uri, subject = subject)
+
+ /** @return a chooser intent to share the given URI with the optional provided extra text. */
+ fun createShareIntentWithExtraText(uri: Uri, extraText: String?) =
+ createShareIntent(uri, extraText = extraText)
+
+ private fun createShareIntent(
+ uri: Uri,
+ subject: String? = null,
+ extraText: String? = null
+ ): Intent {
// Create a share intent, this will always go through the chooser activity first
// which should not trigger auto-enter PiP
val sharingIntent =
@@ -43,6 +57,7 @@
)
putExtra(Intent.EXTRA_SUBJECT, subject)
+ putExtra(Intent.EXTRA_TEXT, extraText)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java
new file mode 100644
index 0000000..146e576
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.IAssistDataReceiver;
+import android.app.assist.AssistContent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Can be used to request the AssistContent from a provided task id, useful for getting the web uri
+ * if provided from the task.
+ *
+ * Forked from
+ * packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/AssistContentRequester.java
+ */
+@SysUISingleton
+public class AssistContentRequester {
+ private static final String TAG = "AssistContentRequester";
+ private static final String ASSIST_KEY_CONTENT = "content";
+
+ /** For receiving content, called on the main thread. */
+ public interface Callback {
+ /**
+ * Called when the {@link android.app.assist.AssistContent} of the requested task is
+ * available.
+ **/
+ void onAssistContentAvailable(AssistContent assistContent);
+ }
+
+ private final IActivityTaskManager mActivityTaskManager;
+ private final String mPackageName;
+ private final Executor mCallbackExecutor;
+ private final Executor mSystemInteractionExecutor;
+
+ // If system loses the callback, our internal cache of original callback will also get cleared.
+ private final Map<Object, Callback> mPendingCallbacks =
+ Collections.synchronizedMap(new WeakHashMap<>());
+
+ @Inject
+ public AssistContentRequester(Context context, @Main Executor mainExecutor,
+ @Background Executor bgExecutor) {
+ mActivityTaskManager = ActivityTaskManager.getService();
+ mPackageName = context.getApplicationContext().getPackageName();
+ mCallbackExecutor = mainExecutor;
+ mSystemInteractionExecutor = bgExecutor;
+ }
+
+ /**
+ * Request the {@link AssistContent} from the task with the provided id.
+ *
+ * @param taskId to query for the content.
+ * @param callback to call when the content is available, called on the main thread.
+ */
+ public void requestAssistContent(final int taskId, final Callback callback) {
+ // ActivityTaskManager interaction here is synchronous, so call off the main thread.
+ mSystemInteractionExecutor.execute(() -> {
+ try {
+ mActivityTaskManager.requestAssistDataForTask(
+ new AssistDataReceiver(callback, this), taskId, mPackageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Requesting assist content failed for task: " + taskId, e);
+ }
+ });
+ }
+
+ private void executeOnMainExecutor(Runnable callback) {
+ mCallbackExecutor.execute(callback);
+ }
+
+ private static final class AssistDataReceiver extends IAssistDataReceiver.Stub {
+
+ // The AssistDataReceiver binder callback object is passed to a system server, that may
+ // keep hold of it for longer than the lifetime of the AssistContentRequester object,
+ // potentially causing a memory leak. In the callback passed to the system server, only
+ // keep a weak reference to the parent object and lookup its callback if it still exists.
+ private final WeakReference<AssistContentRequester> mParentRef;
+ private final Object mCallbackKey = new Object();
+
+ AssistDataReceiver(Callback callback, AssistContentRequester parent) {
+ parent.mPendingCallbacks.put(mCallbackKey, callback);
+ mParentRef = new WeakReference<>(parent);
+ }
+
+ @Override
+ public void onHandleAssistData(Bundle data) {
+ if (data == null) {
+ return;
+ }
+
+ final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
+ if (content == null) {
+ Log.e(TAG, "Received AssistData, but no AssistContent found");
+ return;
+ }
+
+ AssistContentRequester requester = mParentRef.get();
+ if (requester != null) {
+ Callback callback = requester.mPendingCallbacks.get(mCallbackKey);
+ if (callback != null) {
+ requester.executeOnMainExecutor(
+ () -> callback.onAssistContentAvailable(content));
+ } else {
+ Log.d(TAG, "Callback received after calling UI was disposed of");
+ }
+ } else {
+ Log.d(TAG, "Callback received after Requester was collected");
+ }
+ }
+
+ @Override
+ public void onHandleAssistScreenshot(Bitmap screenshot) {}
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index d64b33b..ca8e101 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -366,7 +366,7 @@
private void doShare(Uri uri) {
if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
- Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri, null);
+ Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri);
mActionExecutor.launchIntentAsync(shareIntent, null,
mScreenshotUserHandle.getIdentifier(), false);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index f011aab..4db48ac 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -45,6 +45,7 @@
*
* @param request the request to process
*/
+ // TODO: Delete once SCREENSHOT_METADATA flag is launched
suspend fun process(request: ScreenshotRequest): ScreenshotRequest {
var result = request
@@ -93,12 +94,67 @@
* @param request the request to process
* @param callback the callback to provide the processed request, invoked from the main thread
*/
+ // TODO: Delete once SCREENSHOT_METADATA flag is launched
fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) {
mainScope.launch {
val result = process(request)
callback.accept(result)
}
}
+
+ /**
+ * Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
+ *
+ * @param screenshot the screenshot to process
+ */
+ suspend fun process(screenshot: ScreenshotData): ScreenshotData {
+ var result = screenshot
+
+ // Apply work profile screenshots policy:
+ //
+ // If the focused app belongs to a work profile, transforms a full screen
+ // (or partial) screenshot request to a task snapshot (provided image) screenshot.
+
+ // Whenever displayContentInfo is fetched, the topComponent is also populated
+ // regardless of the managed profile status.
+
+ if (screenshot.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
+ flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+ ) {
+ val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
+ Log.d(TAG, "findPrimaryContent: $info")
+ result.taskId = info.taskId
+ result.topComponent = info.component
+ result.userHandle = info.user
+
+ if (policy.isManagedProfile(info.user.identifier)) {
+ val image = capture.captureTask(info.taskId)
+ ?: error("Task snapshot returned a null Bitmap!")
+
+ // Provide the task snapshot as the screenshot
+ result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
+ result.bitmap = image
+ result.screenBounds = info.bounds
+ }
+ }
+
+ return result
+ }
+
+ /**
+ * Note: This is for compatibility with existing Java. Prefer the suspending function when
+ * calling from a Coroutine context.
+ *
+ * @param screenshot the screenshot to process
+ * @param callback the callback to provide the processed screenshot, invoked from the main
+ * thread
+ */
+ fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) {
+ mainScope.launch {
+ val result = process(screenshot)
+ callback.accept(result)
+ }
+ }
}
private const val TAG = "RequestProcessor"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6d87922..adff6e1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -44,6 +44,7 @@
import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
import android.app.ICompatCameraControlCallback;
import android.app.Notification;
+import android.app.assist.AssistContent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -281,6 +282,7 @@
private final ActionIntentExecutor mActionExecutor;
private final UserManager mUserManager;
private final WorkProfileMessageController mWorkProfileMessageController;
+ private final AssistContentRequester mAssistContentRequester;
private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
if (DEBUG_INPUT) {
@@ -328,7 +330,8 @@
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
ActionIntentExecutor actionExecutor,
UserManager userManager,
- WorkProfileMessageController workProfileMessageController
+ WorkProfileMessageController workProfileMessageController,
+ AssistContentRequester assistContentRequester
) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
@@ -361,6 +364,7 @@
mActionExecutor = actionExecutor;
mUserManager = userManager;
mWorkProfileMessageController = workProfileMessageController;
+ mAssistContentRequester = assistContentRequester;
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -390,16 +394,142 @@
ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
}
+ void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
+ RequestCallback requestCallback) {
+ Assert.isMainThread();
+ mCurrentRequestCallback = requestCallback;
+ if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
+ Rect bounds = getFullScreenRect();
+ screenshot.setBitmap(mImageCapture.captureDisplay(DEFAULT_DISPLAY, bounds));
+ screenshot.setScreenBounds(bounds);
+ }
+
+ if (screenshot.getBitmap() == null) {
+ Log.e(TAG, "handleScreenshot: Screenshot bitmap was null");
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ if (mCurrentRequestCallback != null) {
+ mCurrentRequestCallback.reportError();
+ }
+ return;
+ }
+
+ if (!isUserSetupComplete(Process.myUserHandle())) {
+ Log.w(TAG, "User setup not complete, displaying toast only");
+ // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
+ // and sharing shouldn't be exposed to the user.
+ saveScreenshotAndToast(screenshot.getUserHandle(), finisher);
+ return;
+ }
+
+ mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
+ ClipboardOverlayController.SELF_PERMISSION);
+
+ mScreenshotTakenInPortrait =
+ mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
+
+ String oldPackageName = mPackageName;
+ mPackageName = screenshot.getPackageNameString();
+
+ mScreenBitmap = screenshot.getBitmap();
+ // Optimizations
+ mScreenBitmap.setHasAlpha(false);
+ mScreenBitmap.prepareToDraw();
+
+ prepareViewForNewScreenshot(screenshot, oldPackageName);
+
+ saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
+ this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
+
+ // The window is focusable by default
+ setWindowFocusable(true);
+ mScreenshotView.requestFocus();
+
+ enqueueScrollCaptureRequest(screenshot.getUserHandle());
+
+ attachWindow();
+
+ boolean showFlash = true;
+ if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ if (screenshot.getScreenBounds() != null
+ && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
+ screenshot.getScreenBounds())) {
+ showFlash = false;
+ } else {
+ showFlash = true;
+ screenshot.setInsets(Insets.NONE);
+ screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
+ screenshot.getBitmap().getHeight()));
+ }
+ }
+
+ prepareAnimation(screenshot.getScreenBounds(), showFlash);
+
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
+ mContext.getDrawable(R.drawable.overlay_badge_background),
+ screenshot.getUserHandle()));
+ }
+ mScreenshotView.setScreenshot(screenshot);
+
+ if (screenshot.getTaskId() >= 0) {
+ mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
+ new AssistContentRequester.Callback() {
+ @Override
+ public void onAssistContentAvailable(AssistContent assistContent) {
+ screenshot.setContextUrl(assistContent.getWebUri());
+ }
+ });
+ }
+
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "setContentView: " + mScreenshotView);
+ }
+ setContentView(mScreenshotView);
+ // ignore system bar insets for the purpose of window layout
+ mWindow.getDecorView().setOnApplyWindowInsetsListener(
+ (v, insets) -> WindowInsets.CONSUMED);
+ mScreenshotHandler.cancelTimeout(); // restarted after animation
+ }
+
+ void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
+ withWindowAttached(() -> {
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+ && mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
+ mScreenshotView.announceForAccessibility(mContext.getResources().getString(
+ R.string.screenshot_saving_work_profile_title));
+ } else {
+ mScreenshotView.announceForAccessibility(
+ mContext.getResources().getString(R.string.screenshot_saving_title));
+ }
+ });
+
+ mScreenshotView.reset();
+
+ if (mScreenshotView.isAttachedToWindow()) {
+ // if we didn't already dismiss for another reason
+ if (!mScreenshotView.isDismissing()) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0,
+ oldPackageName);
+ }
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
+ + "(dismissing=" + mScreenshotView.isDismissing() + ")");
+ }
+ }
+
+ mScreenshotView.setPackageName(mPackageName);
+
+ mScreenshotView.updateOrientation(
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+ }
+
@MainThread
void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,
RequestCallback requestCallback) {
Assert.isMainThread();
mCurrentRequestCallback = requestCallback;
- DisplayMetrics displayMetrics = new DisplayMetrics();
- getDefaultDisplay().getRealMetrics(displayMetrics);
- takeScreenshotInternal(
- topComponent, finisher,
- new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
+ takeScreenshotInternal(topComponent, finisher, getFullScreenRect());
}
@MainThread
@@ -641,6 +771,42 @@
setWindowFocusable(true);
mScreenshotView.requestFocus();
+ enqueueScrollCaptureRequest(owner);
+
+ attachWindow();
+ prepareAnimation(screenRect, showFlash);
+
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
+ mContext.getDrawable(R.drawable.overlay_badge_background), owner));
+ }
+ mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "setContentView: " + mScreenshotView);
+ }
+ setContentView(mScreenshotView);
+ // ignore system bar insets for the purpose of window layout
+ mWindow.getDecorView().setOnApplyWindowInsetsListener(
+ (v, insets) -> WindowInsets.CONSUMED);
+ mScreenshotHandler.cancelTimeout(); // restarted after animation
+ }
+
+ private void prepareAnimation(Rect screenRect, boolean showFlash) {
+ mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "onPreDraw: startAnimation");
+ }
+ mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
+ startAnimation(screenRect, showFlash);
+ return true;
+ }
+ });
+ }
+
+ private void enqueueScrollCaptureRequest(UserHandle owner) {
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
withWindowAttached(() -> {
@@ -678,30 +844,6 @@
}
});
});
-
- attachWindow();
- mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "onPreDraw: startAnimation");
- }
- mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
- startAnimation(screenRect, showFlash);
- return true;
- }
- });
- if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
- mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
- mContext.getDrawable(R.drawable.overlay_badge_background), owner));
- }
- mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
-
- // ignore system bar insets for the purpose of window layout
- mWindow.getDecorView().setOnApplyWindowInsetsListener(
- (v, insets) -> WindowInsets.CONSUMED);
- mScreenshotHandler.cancelTimeout(); // restarted after animation
}
private void requestScrollCapture(UserHandle owner) {
@@ -1154,6 +1296,12 @@
return !mIsLowRamDevice;
}
+ private Rect getFullScreenRect() {
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ getDefaultDisplay().getRealMetrics(displayMetrics);
+ return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
+ }
+
/** Does the aspect ratio of the bitmap with insets removed match the bounds. */
private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets,
Rect screenBounds) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
new file mode 100644
index 0000000..c43e4b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -0,0 +1,46 @@
+package com.android.systemui.screenshot
+
+import android.content.ComponentName
+import android.graphics.Bitmap
+import android.graphics.Insets
+import android.graphics.Rect
+import android.net.Uri
+import android.os.UserHandle
+import android.view.WindowManager.ScreenshotSource
+import android.view.WindowManager.ScreenshotType
+import com.android.internal.util.ScreenshotRequest
+
+/** ScreenshotData represents the current state of a single screenshot being acquired. */
+data class ScreenshotData(
+ @ScreenshotType var type: Int,
+ @ScreenshotSource var source: Int,
+ /** UserHandle for the owner of the app being screenshotted, if known. */
+ var userHandle: UserHandle?,
+ /** ComponentName of the top-most app in the screenshot. */
+ var topComponent: ComponentName?,
+ var screenBounds: Rect?,
+ var taskId: Int,
+ var insets: Insets,
+ var bitmap: Bitmap?,
+ /** App-provided URL representing the content the user was looking at in the screenshot. */
+ var contextUrl: Uri? = null,
+) {
+ val packageNameString: String
+ get() = if (topComponent == null) "" else topComponent!!.packageName
+
+ companion object {
+ @JvmStatic
+ fun fromRequest(request: ScreenshotRequest): ScreenshotData {
+ return ScreenshotData(
+ request.type,
+ request.source,
+ if (request.userId >= 0) UserHandle.of(request.userId) else null,
+ request.topComponent,
+ request.boundsInScreen,
+ request.taskId,
+ request.insets,
+ request.bitmap,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 200a7dc..ce7f2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -38,6 +38,7 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -166,6 +167,8 @@
private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>();
private PendingInteraction mPendingInteraction;
+ // Should only be set/used if the SCREENSHOT_METADATA flag is set.
+ private ScreenshotData mScreenshotData;
private final InteractionJankMonitor mInteractionJankMonitor;
private long mDefaultTimeoutOfTimeoutHandler;
@@ -470,6 +473,13 @@
mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
}
+ void setScreenshot(ScreenshotData screenshot) {
+ mScreenshotData = screenshot;
+ setScreenshot(screenshot.getBitmap(), screenshot.getInsets());
+ mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, screenshot.getBitmap(),
+ screenshot.getInsets()));
+ }
+
void setPackageName(String packageName) {
mPackageName = packageName;
}
@@ -808,9 +818,17 @@
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
prepareSharedTransition();
- mActionExecutor.launchIntentAsync(
- ActionIntentCreator.INSTANCE.createShareIntent(
- imageData.uri, imageData.subject),
+
+ Intent shareIntent;
+ if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && mScreenshotData != null
+ && mScreenshotData.getContextUrl() != null) {
+ shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithExtraText(
+ imageData.uri, mScreenshotData.getContextUrl().toString());
+ } else {
+ shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithSubject(
+ imageData.uri, imageData.subject);
+ }
+ mActionExecutor.launchIntentAsync(shareIntent,
imageData.shareTransition.get().bundle,
imageData.owner.getIdentifier(), false);
} else {
@@ -1112,6 +1130,7 @@
mQuickShareChip = null;
setAlpha(1);
mScreenshotStatic.setAlpha(1);
+ mScreenshotData = null;
}
private void startSharedTransition(ActionTransition transition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 7b271a8..4214c8f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -59,6 +59,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagListenable.FlagEvent;
+import com.android.systemui.flags.Flags;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -221,8 +222,33 @@
return;
}
- mProcessor.processAsync(request,
- (r) -> dispatchToController(r, onSaved, callback));
+ if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA)) {
+ Log.d(TAG, "Processing screenshot data");
+ ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
+ mProcessor.processAsync(screenshotData,
+ (data) -> dispatchToController(data, onSaved, callback));
+ } else {
+ mProcessor.processAsync(request,
+ (r) -> dispatchToController(r, onSaved, callback));
+ }
+ }
+
+ private void dispatchToController(ScreenshotData screenshot,
+ Consumer<Uri> uriConsumer, RequestCallback callback) {
+
+ mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
+ screenshot.getPackageNameString());
+
+ if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+ && screenshot.getBitmap() == null) {
+ Log.e(TAG, "Got null bitmap from screenshot message");
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ callback.reportError();
+ return;
+ }
+
+ mScreenshot.handleScreenshot(screenshot, uriConsumer, callback);
}
private void dispatchToController(ScreenshotRequest request,
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index 1558ac5..287e810 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -62,12 +62,24 @@
fun removeCallback(callback: Callback)
/**
- * Ćallback for notifying of changes.
+ * Callback for notifying of changes.
*/
interface Callback {
/**
+ * Notifies that the current user is being changed.
+ * Override this method to run things while the screen is frozen for the user switch.
+ * Please use {@link #onUserChanged} if the task doesn't need to push the unfreezing of the
+ * screen further. Please be aware that code executed in this callback will lengthen the
+ * user switch duration.
+ */
+ @JvmDefault
+ fun onUserChanging(newUser: Int, userContext: Context) {}
+
+ /**
* Notifies that the current user has changed.
+ * Override this method to run things after the screen is unfrozen for the user switch.
+ * Please see {@link #onUserChanging} if you need to hide jank.
*/
@JvmDefault
fun onUserChanged(newUser: Int, userContext: Context) {}
@@ -78,4 +90,4 @@
@JvmDefault
fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 61390c5..9f551c6 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -16,6 +16,8 @@
package com.android.systemui.settings
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
import android.content.BroadcastReceiver
import android.content.ContentResolver
import android.content.Context
@@ -23,6 +25,7 @@
import android.content.IntentFilter
import android.content.pm.UserInfo
import android.os.Handler
+import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
@@ -34,6 +37,7 @@
import java.io.PrintWriter
import java.lang.IllegalStateException
import java.lang.ref.WeakReference
+import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
@@ -56,6 +60,7 @@
class UserTrackerImpl internal constructor(
private val context: Context,
private val userManager: UserManager,
+ private val iActivityManager: IActivityManager,
private val dumpManager: DumpManager,
private val backgroundHandler: Handler
) : UserTracker, Dumpable, BroadcastReceiver() {
@@ -107,7 +112,6 @@
setUserIdInternal(startingUser)
val filter = IntentFilter().apply {
- addAction(Intent.ACTION_USER_SWITCHED)
addAction(Intent.ACTION_USER_INFO_CHANGED)
// These get called when a managed profile goes in or out of quiet mode.
addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
@@ -118,14 +122,13 @@
}
context.registerReceiverForAllUsers(this, filter, null /* permission */, backgroundHandler)
+ registerUserSwitchObserver()
+
dumpManager.registerDumpable(TAG, this)
}
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
- Intent.ACTION_USER_SWITCHED -> {
- handleSwitchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL))
- }
Intent.ACTION_USER_INFO_CHANGED,
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
@@ -157,22 +160,43 @@
return ctx to profiles
}
+ private fun registerUserSwitchObserver() {
+ iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() {
+ override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
+ backgroundHandler.run {
+ handleUserSwitching(newUserId)
+ reply?.sendResult(null)
+ }
+ }
+
+ override fun onUserSwitchComplete(newUserId: Int) {
+ backgroundHandler.run {
+ handleUserSwitchComplete(newUserId)
+ }
+ }
+ }, TAG)
+ }
+
@WorkerThread
- private fun handleSwitchUser(newUser: Int) {
+ private fun handleUserSwitching(newUserId: Int) {
Assert.isNotMainThread()
- if (newUser == UserHandle.USER_NULL) {
- Log.w(TAG, "handleSwitchUser - Couldn't get new id from intent")
- return
- }
+ Log.i(TAG, "Switching to user $newUserId")
- if (newUser == userId) return
- Log.i(TAG, "Switching to user $newUser")
-
- val (ctx, profiles) = setUserIdInternal(newUser)
-
+ setUserIdInternal(newUserId)
notifySubscribers {
- onUserChanged(newUser, ctx)
- onProfilesChanged(profiles)
+ onUserChanging(newUserId, userContext)
+ }.await()
+ }
+
+ @WorkerThread
+ private fun handleUserSwitchComplete(newUserId: Int) {
+ Assert.isNotMainThread()
+ Log.i(TAG, "Switched to user $newUserId")
+
+ setUserIdInternal(newUserId)
+ notifySubscribers {
+ onUserChanged(newUserId, userContext)
+ onProfilesChanged(userProfiles)
}
}
@@ -201,17 +225,25 @@
}
}
- private inline fun notifySubscribers(crossinline action: UserTracker.Callback.() -> Unit) {
+ private inline fun notifySubscribers(
+ crossinline action: UserTracker.Callback.() -> Unit
+ ): CountDownLatch {
val list = synchronized(callbacks) {
callbacks.toList()
}
+ val latch = CountDownLatch(list.size)
+
list.forEach {
if (it.callback.get() != null) {
it.executor.execute {
it.callback.get()?.action()
+ latch.countDown()
}
+ } else {
+ latch.countDown()
}
}
+ return latch
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -258,4 +290,4 @@
fun sameOrEmpty(other: UserTracker.Callback): Boolean {
return callback.get()?.equals(other) ?: true
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
index 2f62e44..809fa29 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.settings.dagger;
import android.app.ActivityManager;
+import android.app.IActivityManager;
import android.content.Context;
import android.os.Handler;
import android.os.UserManager;
@@ -57,11 +58,13 @@
static UserTracker provideUserTracker(
Context context,
UserManager userManager,
+ IActivityManager iActivityManager,
DumpManager dumpManager,
@Background Handler handler
) {
int startingUser = ActivityManager.getCurrentUser();
- UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, dumpManager, handler);
+ UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, iActivityManager,
+ dumpManager, handler);
tracker.initialize(startingUser);
return tracker;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 8867637..197232e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade
+import android.animation.Animator
import android.annotation.IdRes
import android.app.StatusBarManager
import android.content.res.Configuration
@@ -45,7 +46,6 @@
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
-import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
@@ -176,9 +176,13 @@
var shadeExpandedFraction = -1f
set(value) {
if (field != value) {
+ val oldAlpha = header.alpha
header.alpha = ShadeInterpolation.getContentAlpha(value)
field = value
- updateVisibility()
+ if ((oldAlpha == 0f && header.alpha > 0f) ||
+ (oldAlpha > 0f && header.alpha == 0f)) {
+ updateVisibility()
+ }
}
}
@@ -305,6 +309,8 @@
val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
v.pivotX = newPivot
v.pivotY = v.height.toFloat() / 2
+
+ qsCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
}
}
@@ -335,9 +341,28 @@
.setUpdateListener {
updateVisibility()
}
+ .setListener(endAnimationListener)
.start()
}
+ private val endAnimationListener = object : Animator.AnimatorListener {
+ override fun onAnimationCancel(animation: Animator?) {
+ clearListeners()
+ }
+
+ override fun onAnimationEnd(animation: Animator?) {
+ clearListeners()
+ }
+
+ override fun onAnimationRepeat(animation: Animator?) {}
+
+ override fun onAnimationStart(animation: Animator?) {}
+
+ private fun clearListeners() {
+ header.animate().setListener(null).setUpdateListener(null)
+ }
+ }
+
private fun loadConstraints() {
if (header is MotionLayout) {
// Use resources.getXml instead of passing the resource id due to bug b/205018300
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 392a851..b6f08f8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -140,12 +140,16 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
@@ -204,7 +208,6 @@
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
@@ -243,6 +246,7 @@
import javax.inject.Inject;
import javax.inject.Provider;
+import kotlin.Unit;
import kotlinx.coroutines.CoroutineDispatcher;
@CentralSurfacesComponent.CentralSurfacesScope
@@ -698,6 +702,7 @@
private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final KeyguardInteractor mKeyguardInteractor;
private CoroutineDispatcher mMainDispatcher;
private boolean mIsOcclusionTransitionRunning = false;
private int mDreamingToLockscreenTransitionTranslationY;
@@ -827,7 +832,9 @@
LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
@Main CoroutineDispatcher mainDispatcher,
KeyguardTransitionInteractor keyguardTransitionInteractor,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ KeyguardLongPressViewModel keyguardLongPressViewModel,
+ KeyguardInteractor keyguardInteractor) {
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onKeyguardFadingAwayChanged() {
@@ -848,6 +855,7 @@
mGoneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel;
mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mKeyguardInteractor = keyguardInteractor;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -1000,6 +1008,14 @@
updateUserSwitcherFlags();
mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
+ KeyguardLongPressViewBinder.bind(
+ mView.requireViewById(R.id.keyguard_long_press),
+ keyguardLongPressViewModel,
+ () -> {
+ onEmptySpaceClick();
+ return Unit.INSTANCE;
+ },
+ mFalsingManager);
onFinishInflate();
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -2157,6 +2173,7 @@
}
ValueAnimator animator = createHeightAnimator(target, overshootAmount);
if (expand) {
+ maybeVibrateOnOpening(true /* openingWithTouch */);
if (expandBecauseOfFalsing && vel < 0) {
vel = 0;
}
@@ -2167,6 +2184,7 @@
animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION);
}
} else {
+ mHasVibratedOnOpen = false;
if (shouldUseDismissingAnimation()) {
if (vel == 0) {
animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
@@ -2516,7 +2534,7 @@
if (!mSplitShadeEnabled
&& computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) {
mShadeLog.logMotionEvent(event,
- "handleQsTouch: QQS touched while shade collapsing, QS tracking disabled");
+ "handleQsTouch: shade touched while collapsing, QS tracking disabled");
mQsTracking = false;
}
if (!mQsExpandImmediate && mQsTracking) {
@@ -3129,6 +3147,7 @@
mQsClipBottom,
radius,
qsVisible && !mSplitShadeEnabled);
+ mKeyguardInteractor.setQuickSettingsVisible(mQsVisible);
}
// The padding on this area is large enough that we can use a cheaper clipping strategy
mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null);
@@ -3767,7 +3786,8 @@
// change due to "unlock hint animation." In this case, fading out the bottom area
// would also hide the message that says "swipe to unlock," we don't want to do that.
float expansionAlpha = MathUtils.map(
- isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
+ isUnlockHintRunning() ? 0 : KeyguardBouncerConstants.ALPHA_EXPANSION_THRESHOLD, 1f,
+ 0f, 1f,
getExpandedFraction());
float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
@@ -6375,7 +6395,7 @@
mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction);
}
addMovement(event);
- if (!isFullyCollapsed()) {
+ if (!isFullyCollapsed() && !isOnKeyguard()) {
maybeVibrateOnOpening(true /* openingWithTouch */);
}
float h = y - mInitialExpandY;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index ab2e692..156e4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -563,7 +563,8 @@
mCurrentState.keyguardOccluded,
mCurrentState.bouncerShowing,
mCurrentState.dozing,
- mCurrentState.panelExpanded);
+ mCurrentState.panelExpanded,
+ mCurrentState.dreaming);
}
}
@@ -778,6 +779,12 @@
}
@Override
+ public void setDreaming(boolean dreaming) {
+ mCurrentState.dreaming = dreaming;
+ apply(mCurrentState);
+ }
+
+ @Override
public void setForcePluginOpen(boolean forceOpen, Object token) {
if (forceOpen) {
mCurrentState.forceOpenTokens.add(token);
@@ -904,5 +911,10 @@
public void onDozingChanged(boolean isDozing) {
setDozing(isDozing);
}
+
+ @Override
+ public void onDreamingChanged(boolean isDreaming) {
+ setDreaming(isDreaming);
+ }
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 736404aa..fed9b84 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -23,8 +23,8 @@
import com.android.systemui.statusbar.StatusBarState
/**
- * Represents state of shade window, used by [NotificationShadeWindowControllerImpl].
- * Contains nested class [Buffer] for pretty table logging in bug reports.
+ * Represents state of shade window, used by [NotificationShadeWindowControllerImpl]. Contains
+ * nested class [Buffer] for pretty table logging in bug reports.
*/
class NotificationShadeWindowState(
@JvmField var keyguardShowing: Boolean = false,
@@ -55,6 +55,7 @@
@JvmField var remoteInputActive: Boolean = false,
@JvmField var forcePluginOpen: Boolean = false,
@JvmField var dozing: Boolean = false,
+ @JvmField var dreaming: Boolean = false,
@JvmField var scrimsVisibility: Int = 0,
@JvmField var backgroundBlurRadius: Int = 0,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt b/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt
index 1302ec9..88e8ad9 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt
@@ -15,8 +15,6 @@
*/
package com.android.systemui.smartspace.preconditions
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
@@ -24,11 +22,9 @@
/**
* {@link LockscreenPrecondition} covers the conditions that must be met before Smartspace can be
- * used over lockscreen. These conditions include the device being provisioned with a setup user
- * and the Smartspace feature flag enabled.
+ * used over lockscreen. These conditions include the device being provisioned with a setup user.
*/
class LockscreenPrecondition @Inject constructor(
- private val featureFlags: FeatureFlags,
private val deviceProvisionedController: DeviceProvisionedController,
private val execution: Execution
) : SmartspacePrecondition {
@@ -90,6 +86,6 @@
override fun conditionsMet(): Boolean {
execution.assertIsMainThread()
- return featureFlags.isEnabled(Flags.SMARTSPACE) && deviceReady
+ return deviceReady
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 0b1807d..2ca0b00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -143,6 +143,9 @@
/** Sets the state of whether sysui is dozing or not. */
default void setDozing(boolean dozing) {}
+ /** Sets the state of whether sysui is dreaming or not. */
+ default void setDreaming(boolean dreaming) {}
+
/** Sets the state of whether plugin open is forced or not. */
default void setForcePluginOpen(boolean forcePluginOpen, Object token) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 186e6dc..784e2d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -129,6 +129,11 @@
private boolean mIsDozing;
/**
+ * If the device is currently dreaming or not.
+ */
+ private boolean mIsDreaming;
+
+ /**
* If the status bar is currently expanded or not.
*/
private boolean mIsExpanded;
@@ -294,6 +299,29 @@
}
@Override
+ public boolean setIsDreaming(boolean isDreaming) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "setIsDreaming:" + isDreaming);
+ }
+ if (mIsDreaming == isDreaming) {
+ return false;
+ }
+
+ mIsDreaming = isDreaming;
+
+ synchronized (mListeners) {
+ String tag = getClass().getSimpleName() + "#setIsDreaming";
+ DejankUtils.startDetectingBlockingIpcs(tag);
+ for (RankedListener rl : new ArrayList<>(mListeners)) {
+ rl.mListener.onDreamingChanged(isDreaming);
+ }
+ DejankUtils.stopDetectingBlockingIpcs(tag);
+ }
+
+ return true;
+ }
+
+ @Override
public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) {
if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
if (animated && mDozeAmountTarget == dozeAmount) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index e0cf812..088c568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -99,6 +99,13 @@
boolean setIsDozing(boolean isDozing);
/**
+ * Update the dreaming state from {@link CentralSurfaces}'s perspective
+ * @param isDreaming whether we are dreaming
+ * @return {@code true} if the state changed, else {@code false}
+ */
+ boolean setIsDreaming(boolean isDreaming);
+
+ /**
* Changes the current doze amount, also starts the
* {@link com.android.internal.jank.InteractionJankMonitor InteractionJankMonitor} as possible.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 43e2e52..665b1bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -206,7 +206,7 @@
fun isEnabled(): Boolean {
execution.assertIsMainThread()
- return featureFlags.isEnabled(Flags.SMARTSPACE) && plugin != null
+ return plugin != null
}
private fun updateBypassEnabled() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 635ed7c..4856759 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -32,8 +32,6 @@
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
- fun isSmartspaceDedupingEnabled(): Boolean = featureFlags.isEnabled(Flags.SMARTSPACE)
-
fun fullScreenIntentRequiresKeyguard(): Boolean =
featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 1399385..03a3ca5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -88,9 +88,7 @@
mCoordinators.add(viewConfigCoordinator)
mCoordinators.add(visualStabilityCoordinator)
mCoordinators.add(sensitiveContentCoordinator)
- if (notifPipelineFlags.isSmartspaceDedupingEnabled()) {
- mCoordinators.add(smartspaceDedupingCoordinator)
- }
+ mCoordinators.add(smartspaceDedupingCoordinator)
mCoordinators.add(headsUpCoordinator)
mCoordinators.add(gutsCoordinator)
mCoordinators.add(preparationCoordinator)
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 808638a..8436ff7 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
@@ -16,27 +16,15 @@
package com.android.systemui.statusbar.notification.dagger;
-import android.app.INotificationManager;
import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutManager;
-import android.os.Handler;
-import android.view.accessibility.AccessibilityManager;
-import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeEventsModule;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
@@ -50,7 +38,6 @@
import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl;
import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderModule;
import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
@@ -72,22 +59,17 @@
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
-import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.wmshell.BubblesManager;
-import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Provider;
import dagger.Binds;
-import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -109,48 +91,6 @@
@Binds
StackScrollAlgorithm.BypassController bindBypassController(KeyguardBypassController impl);
- /** Provides an instance of {@link NotificationGutsManager} */
- @SysUISingleton
- @Provides
- static NotificationGutsManager provideNotificationGutsManager(
- Context context,
- Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
- @Main Handler mainHandler,
- @Background Handler bgHandler,
- AccessibilityManager accessibilityManager,
- HighPriorityProvider highPriorityProvider,
- INotificationManager notificationManager,
- PeopleSpaceWidgetManager peopleSpaceWidgetManager,
- LauncherApps launcherApps,
- ShortcutManager shortcutManager,
- ChannelEditorDialogController channelEditorDialogController,
- UserContextProvider contextTracker,
- AssistantFeedbackController assistantFeedbackController,
- Optional<BubblesManager> bubblesManagerOptional,
- UiEventLogger uiEventLogger,
- OnUserInteractionCallback onUserInteractionCallback,
- ShadeController shadeController) {
- return new NotificationGutsManager(
- context,
- centralSurfacesOptionalLazy,
- mainHandler,
- bgHandler,
- accessibilityManager,
- highPriorityProvider,
- notificationManager,
- peopleSpaceWidgetManager,
- launcherApps,
- shortcutManager,
- channelEditorDialogController,
- contextTracker,
- assistantFeedbackController,
- bubblesManagerOptional,
- uiEventLogger,
- onUserInteractionCallback,
- shadeController
- );
- }
-
/** Provides an instance of {@link NotifGutsViewManager} */
@Binds
NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index ea12b82..37ff11d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -44,12 +44,10 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.systemui.Dependency;
-import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -65,28 +63,29 @@
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
-import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.wmshell.BubblesManager;
-import java.io.PrintWriter;
import java.util.Optional;
+import javax.inject.Inject;
+
import dagger.Lazy;
/**
* Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
* closing guts, and keeping track of the currently exposed notification guts.
*/
+@SysUISingleton
public class NotificationGutsManager implements NotifGutsViewManager {
private static final String TAG = "NotificationGutsManager";
// Must match constant in Settings. Used to highlight preferences when linking to Settings.
private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
- private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ private final MetricsLogger mMetricsLogger;
private final Context mContext;
private final AccessibilityManager mAccessibilityManager;
private final HighPriorityProvider mHighPriorityProvider;
@@ -94,12 +93,9 @@
private final OnUserInteractionCallback mOnUserInteractionCallback;
// Dependencies:
- private final NotificationLockscreenUserManager mLockscreenUserManager =
- Dependency.get(NotificationLockscreenUserManager.class);
- private final StatusBarStateController mStatusBarStateController =
- Dependency.get(StatusBarStateController.class);
- private final DeviceProvisionedController mDeviceProvisionedController =
- Dependency.get(DeviceProvisionedController.class);
+ private final NotificationLockscreenUserManager mLockscreenUserManager;
+ private final StatusBarStateController mStatusBarStateController;
+ private final DeviceProvisionedController mDeviceProvisionedController;
private final AssistantFeedbackController mAssistantFeedbackController;
// which notification is currently being longpress-examined by the user
@@ -124,9 +120,7 @@
private final ShadeController mShadeController;
private NotifGutsViewListener mGutsListener;
- /**
- * Injected constructor. See {@link NotificationsModule}.
- */
+ @Inject
public NotificationGutsManager(Context context,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
@Main Handler mainHandler,
@@ -143,7 +137,11 @@
Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback,
- ShadeController shadeController) {
+ ShadeController shadeController,
+ NotificationLockscreenUserManager notificationLockscreenUserManager,
+ StatusBarStateController statusBarStateController,
+ DeviceProvisionedController deviceProvisionedController,
+ MetricsLogger metricsLogger) {
mContext = context;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mMainHandler = mainHandler;
@@ -161,6 +159,10 @@
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
mShadeController = shadeController;
+ mLockscreenUserManager = notificationLockscreenUserManager;
+ mStatusBarStateController = statusBarStateController;
+ mDeviceProvisionedController = deviceProvisionedController;
+ mMetricsLogger = metricsLogger;
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -372,7 +374,8 @@
mDeviceProvisionedController.isDeviceProvisioned(),
row.getIsNonblockable(),
mHighPriorityProvider.isHighPriority(row.getEntry()),
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index ea0060a..8a50f2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -204,10 +204,11 @@
boolean isDeviceProvisioned,
boolean isNonblockable,
boolean wasShownHighPriority,
- AssistantFeedbackController assistantFeedbackController)
+ AssistantFeedbackController assistantFeedbackController,
+ MetricsLogger metricsLogger)
throws RemoteException {
mINotificationManager = iNotificationManager;
- mMetricsLogger = Dependency.get(MetricsLogger.class);
+ mMetricsLogger = metricsLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
mChannelEditorDialogController = channelEditorDialogController;
mAssistantFeedbackController = assistantFeedbackController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 149ec54..8b1a02b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -279,7 +279,8 @@
public void onManagedProfileChanged() {
if (mManagedProfileController.hasActiveProfile()) {
if (mAutoTracker.isAdded(WORK)) return;
- mHost.addTile(WORK);
+ final int position = mAutoTracker.getRestoredTilePosition(WORK);
+ mHost.addTile(WORK, position);
mAutoTracker.setTileAdded(WORK);
} else {
if (!mAutoTracker.isAdded(WORK)) return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index dd75d35..f6a06ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1408,12 +1408,14 @@
// to dismiss the lock screen until entering the SIM PIN.
// - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the
// keyguard.
+ // - Shade is in QQS over keyguard - swiping up should take us back to keyguard
if (!isKeyguardShowing()
|| mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
|| isOccluded()
|| !mKeyguardStateController.canDismissLockScreen()
|| mKeyguardViewMediator.isAnySimPinSecure()
- || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)) {
+ || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)
+ || mNotificationPanelViewController.getBarState() == StatusBarState.SHADE_LOCKED) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
deleted file mode 100644
index d318759..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ /dev/null
@@ -1,708 +0,0 @@
-/*
- * Copyright (C) 2014 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.phone;
-
-import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.hardware.biometrics.BiometricSourceType;
-import android.os.Handler;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-
-import com.android.internal.policy.SystemBarUtils;
-import com.android.keyguard.KeyguardHostViewController;
-import com.android.keyguard.KeyguardSecurityModel;
-import com.android.keyguard.KeyguardSecurityView;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.ViewMediatorCallback;
-import com.android.keyguard.dagger.KeyguardBouncerComponent;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.KeyguardResetCallback;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
-import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.ListenerSet;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * A class which manages the primary (pin/pattern/password) bouncer on the lockscreen.
- * @deprecated Use KeyguardBouncerRepository
- */
-@Deprecated
-public class KeyguardBouncer {
-
- private static final String TAG = "PrimaryKeyguardBouncer";
- static final long BOUNCER_FACE_DELAY = 1200;
- public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
- protected final Context mContext;
- protected final ViewMediatorCallback mCallback;
- protected final ViewGroup mContainer;
- private final FalsingCollector mFalsingCollector;
- private final DismissCallbackRegistry mDismissCallbackRegistry;
- private final Handler mHandler;
- private final List<PrimaryBouncerExpansionCallback> mExpansionCallbacks = new ArrayList<>();
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final KeyguardStateController mKeyguardStateController;
- private final KeyguardSecurityModel mKeyguardSecurityModel;
- private final KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
- private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onStrongAuthStateChanged(int userId) {
- mBouncerPromptReason = mCallback.getBouncerPromptReason();
- }
-
- @Override
- public void onLockedOutStateChanged(BiometricSourceType type) {
- if (type == BiometricSourceType.FINGERPRINT) {
- mBouncerPromptReason = mCallback.getBouncerPromptReason();
- }
- }
-
- @Override
- public void onNonStrongBiometricAllowedChanged(int userId) {
- mBouncerPromptReason = mCallback.getBouncerPromptReason();
- }
- };
- private final Runnable mRemoveViewRunnable = this::removeView;
- private final KeyguardBypassController mKeyguardBypassController;
- private KeyguardHostViewController mKeyguardViewController;
- private final ListenerSet<KeyguardResetCallback> mResetCallbacks = new ListenerSet<>();
- private final Runnable mResetRunnable = ()-> {
- if (mKeyguardViewController != null) {
- mKeyguardViewController.resetSecurityContainer();
- for (KeyguardResetCallback callback : mResetCallbacks) {
- callback.onKeyguardReset();
- }
- }
- };
-
- private int mStatusBarHeight;
- private float mExpansion = EXPANSION_HIDDEN;
- private boolean mShowingSoon;
- private int mBouncerPromptReason;
- private boolean mIsAnimatingAway;
- private boolean mIsScrimmed;
- private boolean mInitialized;
-
- private KeyguardBouncer(Context context, ViewMediatorCallback callback,
- ViewGroup container,
- DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector,
- PrimaryBouncerExpansionCallback expansionCallback,
- KeyguardStateController keyguardStateController,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardBypassController keyguardBypassController, @Main Handler handler,
- KeyguardSecurityModel keyguardSecurityModel,
- KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) {
- mContext = context;
- mCallback = callback;
- mContainer = container;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mFalsingCollector = falsingCollector;
- mDismissCallbackRegistry = dismissCallbackRegistry;
- mHandler = handler;
- mKeyguardStateController = keyguardStateController;
- mKeyguardSecurityModel = keyguardSecurityModel;
- mKeyguardBouncerComponentFactory = keyguardBouncerComponentFactory;
- mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
- mKeyguardBypassController = keyguardBypassController;
- mExpansionCallbacks.add(expansionCallback);
- }
-
- /**
- * Get the KeyguardBouncer expansion
- * @return 1=HIDDEN, 0=SHOWING, in between 0 and 1 means the bouncer is in transition.
- */
- public float getExpansion() {
- return mExpansion;
- }
-
- /**
- * Enable/disable only the back button
- */
- public void setBackButtonEnabled(boolean enabled) {
- int vis = mContainer.getSystemUiVisibility();
- if (enabled) {
- vis &= ~View.STATUS_BAR_DISABLE_BACK;
- } else {
- vis |= View.STATUS_BAR_DISABLE_BACK;
- }
- mContainer.setSystemUiVisibility(vis);
- }
-
- public void show(boolean resetSecuritySelection) {
- show(resetSecuritySelection, true /* scrimmed */);
- }
-
- /**
- * Shows the bouncer.
- *
- * @param resetSecuritySelection Cleans keyguard view
- * @param isScrimmed true when the bouncer show show scrimmed, false when the user will be
- * dragging it and translation should be deferred.
- */
- public void show(boolean resetSecuritySelection, boolean isScrimmed) {
- final int keyguardUserId = KeyguardUpdateMonitor.getCurrentUser();
- if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
- // In split system user mode, we never unlock system user.
- return;
- }
-
- try {
- Trace.beginSection("KeyguardBouncer#show");
-
- ensureView();
- mIsScrimmed = isScrimmed;
-
- // On the keyguard, we want to show the bouncer when the user drags up, but it's
- // not correct to end the falsing session. We still need to verify if those touches
- // are valid.
- // Later, at the end of the animation, when the bouncer is at the top of the screen,
- // onFullyShown() will be called and FalsingManager will stop recording touches.
- if (isScrimmed) {
- setExpansion(EXPANSION_VISIBLE);
- }
-
- if (resetSecuritySelection) {
- // showPrimarySecurityScreen() updates the current security method. This is needed
- // in case we are already showing and the current security method changed.
- showPrimarySecurityScreen();
- }
-
- if (mContainer.getVisibility() == View.VISIBLE || mShowingSoon) {
- // Calls to reset must resume the ViewControllers when in fullscreen mode
- if (needsFullscreenBouncer()) {
- mKeyguardViewController.onResume();
- }
- return;
- }
-
- final int activeUserId = KeyguardUpdateMonitor.getCurrentUser();
- final boolean isSystemUser =
- UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
- final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
-
- // If allowed, try to dismiss the Keyguard. If no security auth (password/pin/pattern)
- // is set, this will dismiss the whole Keyguard. Otherwise, show the bouncer.
- if (allowDismissKeyguard && mKeyguardViewController.dismiss(activeUserId)) {
- return;
- }
-
- // This condition may indicate an error on Android, so log it.
- if (!allowDismissKeyguard) {
- Log.w(TAG, "User can't dismiss keyguard: " + activeUserId + " != "
- + keyguardUserId);
- }
-
- mShowingSoon = true;
-
- // Split up the work over multiple frames.
- DejankUtils.removeCallbacks(mResetRunnable);
- if (mKeyguardStateController.isFaceAuthEnabled()
- && !mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- KeyguardUpdateMonitor.getCurrentUser())
- && !needsFullscreenBouncer()
- && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
- BiometricSourceType.FACE)
- && !mKeyguardBypassController.getBypassEnabled()) {
- mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
- } else {
- DejankUtils.postAfterTraversal(mShowRunnable);
- }
-
- mKeyguardStateController.notifyBouncerShowing(true /* showing */);
- dispatchStartingToShow();
- } finally {
- Trace.endSection();
- }
- }
-
- public boolean isScrimmed() {
- return mIsScrimmed;
- }
-
- /**
- * This method must be called at the end of the bouncer animation when
- * the translation is performed manually by the user, otherwise FalsingManager
- * will never be notified and its internal state will be out of sync.
- */
- private void onFullyShown() {
- mFalsingCollector.onBouncerShown();
- if (mKeyguardViewController == null) {
- Log.e(TAG, "onFullyShown when view was null");
- } else {
- mKeyguardViewController.onResume();
- mContainer.announceForAccessibility(
- mKeyguardViewController.getAccessibilityTitleForCurrentMode());
- }
- }
-
- /**
- * @see #onFullyShown()
- */
- private void onFullyHidden() {
-
- }
-
- private void setVisibility(@View.Visibility int visibility) {
- mContainer.setVisibility(visibility);
- if (mKeyguardViewController != null) {
- mKeyguardViewController.onBouncerVisibilityChanged(visibility);
- }
- dispatchVisibilityChanged();
- }
-
- private final Runnable mShowRunnable = new Runnable() {
- @Override
- public void run() {
- setVisibility(View.VISIBLE);
- showPromptReason(mBouncerPromptReason);
- final CharSequence customMessage = mCallback.consumeCustomMessage();
- if (customMessage != null) {
- mKeyguardViewController.showErrorMessage(customMessage);
- }
- mKeyguardViewController.appear(mStatusBarHeight);
- mShowingSoon = false;
- if (mExpansion == EXPANSION_VISIBLE) {
- mKeyguardViewController.onResume();
- mKeyguardViewController.resetSecurityContainer();
- showPromptReason(mBouncerPromptReason);
- }
- }
- };
-
- /**
- * Show a string explaining why the security view needs to be solved.
- *
- * @param reason a flag indicating which string should be shown, see
- * {@link KeyguardSecurityView#PROMPT_REASON_NONE}
- * and {@link KeyguardSecurityView#PROMPT_REASON_RESTART}
- */
- public void showPromptReason(int reason) {
- if (mKeyguardViewController != null) {
- mKeyguardViewController.showPromptReason(reason);
- } else {
- Log.w(TAG, "Trying to show prompt reason on empty bouncer");
- }
- }
-
- public void showMessage(String message, ColorStateList colorState) {
- if (mKeyguardViewController != null) {
- mKeyguardViewController.showMessage(message, colorState);
- } else {
- Log.w(TAG, "Trying to show message on empty bouncer");
- }
- }
-
- private void cancelShowRunnable() {
- DejankUtils.removeCallbacks(mShowRunnable);
- mHandler.removeCallbacks(mShowRunnable);
- mShowingSoon = false;
- }
-
- public void showWithDismissAction(OnDismissAction r, Runnable cancelAction) {
- ensureView();
- setDismissAction(r, cancelAction);
- show(false /* resetSecuritySelection */);
- }
-
- /**
- * Set the actions to run when the keyguard is dismissed or when the dismiss is cancelled. Those
- * actions will still be run even if this bouncer is not shown, for instance when authenticating
- * with an alternate authenticator like the UDFPS.
- */
- public void setDismissAction(OnDismissAction r, Runnable cancelAction) {
- mKeyguardViewController.setOnDismissAction(r, cancelAction);
- }
-
- public void hide(boolean destroyView) {
- Trace.beginSection("KeyguardBouncer#hide");
- if (isShowing()) {
- SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
- SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN);
- mDismissCallbackRegistry.notifyDismissCancelled();
- }
- mIsScrimmed = false;
- mFalsingCollector.onBouncerHidden();
- mKeyguardStateController.notifyBouncerShowing(false /* showing */);
- cancelShowRunnable();
- if (mKeyguardViewController != null) {
- mKeyguardViewController.cancelDismissAction();
- mKeyguardViewController.cleanUp();
- }
- mIsAnimatingAway = false;
- setVisibility(View.INVISIBLE);
- if (destroyView) {
-
- // We have a ViewFlipper that unregisters a broadcast when being detached, which may
- // be slow because of AM lock contention during unlocking. We can delay it a bit.
- mHandler.postDelayed(mRemoveViewRunnable, 50);
- }
- Trace.endSection();
- }
-
- /**
- * See {@link StatusBarKeyguardViewManager#startPreHideAnimation}.
- */
- public void startPreHideAnimation(Runnable runnable) {
- mIsAnimatingAway = true;
- if (mKeyguardViewController != null) {
- mKeyguardViewController.startDisappearAnimation(runnable);
- } else if (runnable != null) {
- runnable.run();
- }
- }
-
- /**
- * Reset the state of the view.
- */
- public void reset() {
- cancelShowRunnable();
- inflateView();
- mFalsingCollector.onBouncerHidden();
- }
-
- public void onScreenTurnedOff() {
- if (mKeyguardViewController != null && mContainer.getVisibility() == View.VISIBLE) {
- mKeyguardViewController.onPause();
- }
- }
-
- public boolean isShowing() {
- return (mShowingSoon || mContainer.getVisibility() == View.VISIBLE)
- && mExpansion == EXPANSION_VISIBLE && !isAnimatingAway();
- }
-
- /**
- * {@link #show(boolean)} was called but we're not showing yet, or being dragged.
- */
- public boolean inTransit() {
- return mShowingSoon || mExpansion != EXPANSION_HIDDEN && mExpansion != EXPANSION_VISIBLE;
- }
-
- /**
- * @return {@code true} when bouncer's pre-hide animation already started but isn't completely
- * hidden yet, {@code false} otherwise.
- */
- public boolean isAnimatingAway() {
- return mIsAnimatingAway;
- }
-
- public void prepare() {
- boolean wasInitialized = mInitialized;
- ensureView();
- if (wasInitialized) {
- showPrimarySecurityScreen();
- }
- mBouncerPromptReason = mCallback.getBouncerPromptReason();
- }
-
- private void showPrimarySecurityScreen() {
- mKeyguardViewController.showPrimarySecurityScreen();
- }
-
- /**
- * Current notification panel expansion
- * @param fraction 0 when notification panel is collapsed and 1 when expanded.
- * @see StatusBarKeyguardViewManager#onPanelExpansionChanged
- */
- public void setExpansion(float fraction) {
- float oldExpansion = mExpansion;
- boolean expansionChanged = mExpansion != fraction;
- mExpansion = fraction;
- if (mKeyguardViewController != null && !mIsAnimatingAway) {
- mKeyguardViewController.setExpansion(fraction);
- }
-
- if (fraction == EXPANSION_VISIBLE && oldExpansion != EXPANSION_VISIBLE) {
- onFullyShown();
- dispatchFullyShown();
- } else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) {
- DejankUtils.postAfterTraversal(mResetRunnable);
- /*
- * There are cases where #hide() was not invoked, such as when
- * NotificationPanelViewController controls the hide animation. Make sure the state gets
- * updated by calling #hide() directly.
- */
- hide(false /* destroyView */);
- dispatchFullyHidden();
- } else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) {
- dispatchStartingToHide();
- if (mKeyguardViewController != null) {
- mKeyguardViewController.onStartingToHide();
- }
- }
-
- if (expansionChanged) {
- dispatchExpansionChanged();
- }
- }
-
- public boolean willDismissWithAction() {
- return mKeyguardViewController != null && mKeyguardViewController.hasDismissActions();
- }
-
- public int getTop() {
- if (mKeyguardViewController == null) {
- return 0;
- }
-
- return mKeyguardViewController.getTop();
- }
-
- protected void ensureView() {
- // Removal of the view might be deferred to reduce unlock latency,
- // in this case we need to force the removal, otherwise we'll
- // end up in an unpredictable state.
- boolean forceRemoval = mHandler.hasCallbacks(mRemoveViewRunnable);
- if (!mInitialized || forceRemoval) {
- inflateView();
- }
- }
-
- protected void inflateView() {
- removeView();
- mHandler.removeCallbacks(mRemoveViewRunnable);
-
- KeyguardBouncerComponent component = mKeyguardBouncerComponentFactory.create(mContainer);
- mKeyguardViewController = component.getKeyguardHostViewController();
- mKeyguardViewController.init();
-
- mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
- setVisibility(View.INVISIBLE);
-
- final WindowInsets rootInsets = mContainer.getRootWindowInsets();
- if (rootInsets != null) {
- mContainer.dispatchApplyWindowInsets(rootInsets);
- }
- mInitialized = true;
- }
-
- protected void removeView() {
- mContainer.removeAllViews();
- mInitialized = false;
- }
-
- /**
- * @return True if and only if the security method should be shown before showing the
- * notifications on Keyguard, like SIM PIN/PUK.
- */
- public boolean needsFullscreenBouncer() {
- SecurityMode mode = mKeyguardSecurityModel.getSecurityMode(
- KeyguardUpdateMonitor.getCurrentUser());
- return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
- }
-
- /**
- * Like {@link #needsFullscreenBouncer}, but uses the currently visible security method, which
- * makes this method much faster.
- */
- public boolean isFullscreenBouncer() {
- if (mKeyguardViewController != null) {
- SecurityMode mode = mKeyguardViewController.getCurrentSecurityMode();
- return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
- }
- return false;
- }
-
- /**
- * WARNING: This method might cause Binder calls.
- */
- public boolean isSecure() {
- return mKeyguardSecurityModel.getSecurityMode(
- KeyguardUpdateMonitor.getCurrentUser()) != SecurityMode.None;
- }
-
- public boolean shouldDismissOnMenuPressed() {
- return mKeyguardViewController.shouldEnableMenuKey();
- }
-
- public boolean interceptMediaKey(KeyEvent event) {
- ensureView();
- return mKeyguardViewController.interceptMediaKey(event);
- }
-
- /**
- * @return true if the pre IME back event should be handled
- */
- public boolean dispatchBackKeyEventPreIme() {
- ensureView();
- return mKeyguardViewController.dispatchBackKeyEventPreIme();
- }
-
- public void notifyKeyguardAuthenticated(boolean strongAuth) {
- ensureView();
- mKeyguardViewController.finish(strongAuth, KeyguardUpdateMonitor.getCurrentUser());
- }
-
- private void dispatchFullyShown() {
- for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
- callback.onFullyShown();
- }
- }
-
- private void dispatchStartingToHide() {
- for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
- callback.onStartingToHide();
- }
- }
-
- private void dispatchStartingToShow() {
- for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
- callback.onStartingToShow();
- }
- }
-
- private void dispatchFullyHidden() {
- for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
- callback.onFullyHidden();
- }
- }
-
- private void dispatchExpansionChanged() {
- for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
- callback.onExpansionChanged(mExpansion);
- }
- }
-
- private void dispatchVisibilityChanged() {
- for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
- callback.onVisibilityChanged(mContainer.getVisibility() == View.VISIBLE);
- }
- }
-
- /**
- * Apply keyguard configuration from the currently active resources. This can be called when the
- * device configuration changes, to re-apply some resources that are qualified on the device
- * configuration.
- */
- public void updateResources() {
- if (mKeyguardViewController != null) {
- mKeyguardViewController.updateResources();
- }
- }
-
- public void dump(PrintWriter pw) {
- pw.println("KeyguardBouncer");
- pw.println(" isShowing(): " + isShowing());
- pw.println(" mStatusBarHeight: " + mStatusBarHeight);
- pw.println(" mExpansion: " + mExpansion);
- pw.println(" mKeyguardViewController; " + mKeyguardViewController);
- pw.println(" mShowingSoon: " + mShowingSoon);
- pw.println(" mBouncerPromptReason: " + mBouncerPromptReason);
- pw.println(" mIsAnimatingAway: " + mIsAnimatingAway);
- pw.println(" mInitialized: " + mInitialized);
- }
-
- /** Update keyguard position based on a tapped X coordinate. */
- public void updateKeyguardPosition(float x) {
- if (mKeyguardViewController != null) {
- mKeyguardViewController.updateKeyguardPosition(x);
- }
- }
-
- public void addKeyguardResetCallback(KeyguardResetCallback callback) {
- mResetCallbacks.addIfAbsent(callback);
- }
-
- public void removeKeyguardResetCallback(KeyguardResetCallback callback) {
- mResetCallbacks.remove(callback);
- }
-
- /**
- * Adds a callback to listen to bouncer expansion updates.
- */
- public void addBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) {
- if (!mExpansionCallbacks.contains(callback)) {
- mExpansionCallbacks.add(callback);
- }
- }
-
- /**
- * Removes a previously added callback. If the callback was never added, this methood
- * does nothing.
- */
- public void removeBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) {
- mExpansionCallbacks.remove(callback);
- }
-
- /** Create a {@link KeyguardBouncer} once a container and bouncer callback are available. */
- public static class Factory {
- private final Context mContext;
- private final ViewMediatorCallback mCallback;
- private final DismissCallbackRegistry mDismissCallbackRegistry;
- private final FalsingCollector mFalsingCollector;
- private final KeyguardStateController mKeyguardStateController;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final KeyguardBypassController mKeyguardBypassController;
- private final Handler mHandler;
- private final KeyguardSecurityModel mKeyguardSecurityModel;
- private final KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
-
- @Inject
- public Factory(Context context, ViewMediatorCallback callback,
- DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector,
- KeyguardStateController keyguardStateController,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardBypassController keyguardBypassController, @Main Handler handler,
- KeyguardSecurityModel keyguardSecurityModel,
- KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) {
- mContext = context;
- mCallback = callback;
- mDismissCallbackRegistry = dismissCallbackRegistry;
- mFalsingCollector = falsingCollector;
- mKeyguardStateController = keyguardStateController;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mKeyguardBypassController = keyguardBypassController;
- mHandler = handler;
- mKeyguardSecurityModel = keyguardSecurityModel;
- mKeyguardBouncerComponentFactory = keyguardBouncerComponentFactory;
- }
-
- /**
- * Construct a KeyguardBouncer that will exist in the given container.
- */
- public KeyguardBouncer create(ViewGroup container,
- PrimaryBouncerExpansionCallback expansionCallback) {
- return new KeyguardBouncer(mContext, mCallback, container,
- mDismissCallbackRegistry, mFalsingCollector, expansionCallback,
- mKeyguardStateController, mKeyguardUpdateMonitor,
- mKeyguardBypassController, mHandler, mKeyguardSecurityModel,
- mKeyguardBouncerComponentFactory);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 24ad55d..11863627 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -501,7 +501,7 @@
@VisibleForTesting
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
if (mStatusBarPipelineFlags.useNewWifiIcon()) {
- throw new IllegalStateException("Attempting to add a mobile icon while the new "
+ throw new IllegalStateException("Attempting to add a wifi icon while the new "
+ "icons are enabled is not supported");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8e22bf4..fd46571 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -129,7 +129,6 @@
private final ConfigurationController mConfigurationController;
private final NavigationModeController mNavigationModeController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
private final DreamOverlayStateController mDreamOverlayStateController;
@Nullable
@@ -206,28 +205,28 @@
Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()");
}
onBackPressed();
- if (shouldPlayBackAnimation()) {
+ if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) {
mPrimaryBouncerView.getDelegate().getBackCallback().onBackInvoked();
}
}
@Override
public void onBackProgressed(BackEvent event) {
- if (shouldPlayBackAnimation()) {
+ if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) {
mPrimaryBouncerView.getDelegate().getBackCallback().onBackProgressed(event);
}
}
@Override
public void onBackCancelled() {
- if (shouldPlayBackAnimation()) {
+ if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) {
mPrimaryBouncerView.getDelegate().getBackCallback().onBackCancelled();
}
}
@Override
public void onBackStarted(BackEvent event) {
- if (shouldPlayBackAnimation()) {
+ if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) {
mPrimaryBouncerView.getDelegate().getBackCallback().onBackStarted(event);
}
}
@@ -256,7 +255,6 @@
private View mNotificationContainer;
- @Nullable protected KeyguardBouncer mPrimaryBouncer;
protected boolean mRemoteInputActive;
private boolean mGlobalActionsVisible = false;
private boolean mLastGlobalActionsVisible = false;
@@ -281,7 +279,6 @@
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
- private boolean mIsModernBouncerEnabled;
private boolean mIsUnoccludeTransitionFlagEnabled;
private boolean mIsModernAlternateBouncerEnabled;
private boolean mIsBackAnimationEnabled;
@@ -329,7 +326,6 @@
NotificationShadeWindowController notificationShadeWindowController,
KeyguardStateController keyguardStateController,
NotificationMediaManager notificationMediaManager,
- KeyguardBouncer.Factory keyguardBouncerFactory,
KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
Optional<SysUIUnfoldComponent> sysUIUnfoldComponent,
Lazy<ShadeController> shadeController,
@@ -352,7 +348,6 @@
mKeyguardUpdateManager = keyguardUpdateMonitor;
mStatusBarStateController = sysuiStatusBarStateController;
mDockManager = dockManager;
- mKeyguardBouncerFactory = keyguardBouncerFactory;
mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
@@ -362,7 +357,6 @@
mPrimaryBouncerView = primaryBouncerView;
mFoldAodAnimationController = sysUIUnfoldComponent
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
- mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER);
mAlternateBouncerInteractor = alternateBouncerInteractor;
@@ -381,11 +375,7 @@
mBiometricUnlockController = biometricUnlockController;
ViewGroup container = mCentralSurfaces.getBouncerContainer();
- if (mIsModernBouncerEnabled) {
- mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
- } else {
- mPrimaryBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
- }
+ mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
mNotificationPanelViewController = notificationPanelViewController;
if (shadeExpansionStateManager != null) {
shadeExpansionStateManager.addExpansionListener(this);
@@ -552,11 +542,7 @@
* show if any subsequent events are to be handled.
*/
if (beginShowingBouncer(event)) {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
- } else {
- mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
- }
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
}
if (!primaryBouncerIsOrWillBeShowing()) {
@@ -564,17 +550,9 @@
}
if (mKeyguardStateController.isShowing()) {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.setExpansion(fraction);
- } else {
- mPrimaryBouncerInteractor.setPanelExpansion(fraction);
- }
+ mPrimaryBouncerInteractor.setPanelExpansion(fraction);
} else {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.setExpansion(EXPANSION_HIDDEN);
- } else {
- mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN);
- }
+ mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN);
}
}
@@ -604,24 +582,17 @@
/**
* Shows the notification keyguard or the bouncer depending on
- * {@link KeyguardBouncer#needsFullscreenBouncer()}.
+ * {@link #needsFullscreenBouncer()}.
*/
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
if (needsFullscreenBouncer() && !mDozing) {
// The keyguard might be showing (already). So we need to hide it.
mCentralSurfaces.hideKeyguard();
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.show(true /* resetSecuritySelection */);
- } else {
- mPrimaryBouncerInteractor.show(true);
- }
+ mPrimaryBouncerInteractor.show(true);
} else {
mCentralSurfaces.showKeyguard();
if (hideBouncerWhenShowing) {
hideBouncer(false /* destroyView */);
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.prepare();
- }
}
}
updateStates();
@@ -648,11 +619,7 @@
*/
@VisibleForTesting
void hideBouncer(boolean destroyView) {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.hide(destroyView);
- } else {
- mPrimaryBouncerInteractor.hide();
- }
+ mPrimaryBouncerInteractor.hide();
if (mKeyguardStateController.isShowing()) {
// If we were showing the bouncer and then aborting, we need to also clear out any
// potential actions unless we actually unlocked.
@@ -671,11 +638,7 @@
hideAlternateBouncer(false);
if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.show(false /* resetSecuritySelection */, scrimmed);
- } else {
- mPrimaryBouncerInteractor.show(scrimmed);
- }
+ mPrimaryBouncerInteractor.show(scrimmed);
}
updateStates();
}
@@ -708,13 +671,8 @@
// instead of the bouncer.
if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) {
if (!afterKeyguardGone) {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.setDismissAction(mAfterKeyguardGoneAction,
- mKeyguardGoneCancelAction);
- } else {
- mPrimaryBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
- mKeyguardGoneCancelAction);
- }
+ mPrimaryBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
+ mKeyguardGoneCancelAction);
mAfterKeyguardGoneAction = null;
mKeyguardGoneCancelAction = null;
}
@@ -726,22 +684,13 @@
if (afterKeyguardGone) {
// we'll handle the dismiss action after keyguard is gone, so just show the
// bouncer
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.show(false /* resetSecuritySelection */);
- } else {
- mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
- }
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
} else {
// after authentication success, run dismiss action with the option to defer
// hiding the keyguard based on the return value of the OnDismissAction
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
- mKeyguardGoneCancelAction);
- } else {
- mPrimaryBouncerInteractor.setDismissAction(
- mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
- mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
- }
+ mPrimaryBouncerInteractor.setDismissAction(
+ mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
// bouncer will handle the dismiss action, so we no longer need to track it here
mAfterKeyguardGoneAction = null;
mKeyguardGoneCancelAction = null;
@@ -841,11 +790,7 @@
@Override
public void onFinishedGoingToSleep() {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.onScreenTurnedOff();
- } else {
- mPrimaryBouncerInteractor.onScreenTurnedOff();
- }
+ mPrimaryBouncerInteractor.onScreenTurnedOff();
}
@Override
@@ -939,11 +884,7 @@
@Override
public void startPreHideAnimation(Runnable finishRunnable) {
if (primaryBouncerIsShowing()) {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.startPreHideAnimation(finishRunnable);
- } else {
- mPrimaryBouncerInteractor.startDisappearAnimation(finishRunnable);
- }
+ mPrimaryBouncerInteractor.startDisappearAnimation(finishRunnable);
mNotificationPanelViewController.startBouncerPreHideAnimation();
// We update the state (which will show the keyguard) only if an animation will run on
@@ -1051,17 +992,7 @@
}
public void onThemeChanged() {
- if (mIsModernBouncerEnabled) {
- updateResources();
- return;
- }
- boolean wasShowing = primaryBouncerIsShowing();
- boolean wasScrimmed = primaryBouncerIsScrimmed();
-
- hideBouncer(true /* destroyView */);
- mPrimaryBouncer.prepare();
-
- if (wasShowing) showPrimaryBouncer(wasScrimmed);
+ updateResources();
}
public void onKeyguardFadedAway() {
@@ -1106,10 +1037,6 @@
* WARNING: This method might cause Binder calls.
*/
public boolean isSecure() {
- if (mPrimaryBouncer != null) {
- return mPrimaryBouncer.isSecure();
- }
-
return mKeyguardSecurityModel.getSecurityMode(
KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None;
}
@@ -1164,10 +1091,8 @@
}
public boolean isFullscreenBouncer() {
- if (mPrimaryBouncerView.getDelegate() != null) {
- return mPrimaryBouncerView.getDelegate().isFullScreenBouncer();
- }
- return mPrimaryBouncer != null && mPrimaryBouncer.isFullscreenBouncer();
+ return mPrimaryBouncerView.getDelegate() != null
+ && mPrimaryBouncerView.getDelegate().isFullScreenBouncer();
}
/**
@@ -1223,17 +1148,9 @@
!= (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
|| mFirstUpdate) {
if (primaryBouncerDismissible || !showing || remoteInputActive) {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.setBackButtonEnabled(true);
- } else {
- mPrimaryBouncerInteractor.setBackButtonEnabled(true);
- }
+ mPrimaryBouncerInteractor.setBackButtonEnabled(true);
} else {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.setBackButtonEnabled(false);
- } else {
- mPrimaryBouncerInteractor.setBackButtonEnabled(false);
- }
+ mPrimaryBouncerInteractor.setBackButtonEnabled(false);
}
}
@@ -1327,27 +1244,21 @@
}
public boolean shouldDismissOnMenuPressed() {
- if (mPrimaryBouncerView.getDelegate() != null) {
- return mPrimaryBouncerView.getDelegate().shouldDismissOnMenuPressed();
- }
- return mPrimaryBouncer != null && mPrimaryBouncer.shouldDismissOnMenuPressed();
+ return mPrimaryBouncerView.getDelegate() != null
+ && mPrimaryBouncerView.getDelegate().shouldDismissOnMenuPressed();
}
public boolean interceptMediaKey(KeyEvent event) {
- if (mPrimaryBouncerView.getDelegate() != null) {
- return mPrimaryBouncerView.getDelegate().interceptMediaKey(event);
- }
- return mPrimaryBouncer != null && mPrimaryBouncer.interceptMediaKey(event);
+ return mPrimaryBouncerView.getDelegate() != null
+ && mPrimaryBouncerView.getDelegate().interceptMediaKey(event);
}
/**
* @return true if the pre IME back event should be handled
*/
public boolean dispatchBackKeyEventPreIme() {
- if (mPrimaryBouncerView.getDelegate() != null) {
- return mPrimaryBouncerView.getDelegate().dispatchBackKeyEventPreIme();
- }
- return mPrimaryBouncer != null && mPrimaryBouncer.dispatchBackKeyEventPreIme();
+ return mPrimaryBouncerView.getDelegate() != null
+ && mPrimaryBouncerView.getDelegate().dispatchBackKeyEventPreIme();
}
public void readyForKeyguardDone() {
@@ -1393,11 +1304,7 @@
* fingerprint.
*/
public void notifyKeyguardAuthenticated(boolean strongAuth) {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.notifyKeyguardAuthenticated(strongAuth);
- } else {
- mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
- }
+ mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
if (mAlternateBouncerInteractor.isVisibleState()) {
hideAlternateBouncer(false);
@@ -1412,11 +1319,7 @@
mKeyguardMessageAreaController.setMessage(message);
}
} else {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.showMessage(message, colorState);
- } else {
- mPrimaryBouncerInteractor.showMessage(message, colorState);
- }
+ mPrimaryBouncerInteractor.showMessage(message, colorState);
}
}
@@ -1472,11 +1375,7 @@
* configuration.
*/
public void updateResources() {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.updateResources();
- } else {
- mPrimaryBouncerInteractor.updateResources();
- }
+ mPrimaryBouncerInteractor.updateResources();
}
public void dump(PrintWriter pw) {
@@ -1494,11 +1393,6 @@
pw.println(" " + callback);
}
- if (mPrimaryBouncer != null) {
- pw.println("PrimaryBouncer:");
- mPrimaryBouncer.dump(pw);
- }
-
if (mOccludingAppBiometricUI != null) {
pw.println("mOccludingAppBiometricUI:");
mOccludingAppBiometricUI.dump(pw);
@@ -1548,11 +1442,6 @@
}
}
- @Nullable
- public KeyguardBouncer getPrimaryBouncer() {
- return mPrimaryBouncer;
- }
-
/**
* For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently
* showing.
@@ -1571,11 +1460,7 @@
/** Update keyguard position based on a tapped X coordinate. */
public void updateKeyguardPosition(float x) {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.updateKeyguardPosition(x);
- } else {
- mPrimaryBouncerInteractor.setKeyguardPosition(x);
- }
+ mPrimaryBouncerInteractor.setKeyguardPosition(x);
}
private static class DismissWithActionRequest {
@@ -1615,56 +1500,35 @@
* Returns if bouncer expansion is between 0 and 1 non-inclusive.
*/
public boolean isPrimaryBouncerInTransit() {
- if (mPrimaryBouncer != null) {
- return mPrimaryBouncer.inTransit();
- } else {
- return mPrimaryBouncerInteractor.isInTransit();
- }
+ return mPrimaryBouncerInteractor.isInTransit();
}
/**
* Returns if bouncer is showing
*/
public boolean primaryBouncerIsShowing() {
- if (mPrimaryBouncer != null) {
- return mPrimaryBouncer.isShowing();
- } else {
- return mPrimaryBouncerInteractor.isFullyShowing();
- }
+ return mPrimaryBouncerInteractor.isFullyShowing();
}
/**
* Returns if bouncer is scrimmed
*/
public boolean primaryBouncerIsScrimmed() {
- if (mPrimaryBouncer != null) {
- return mPrimaryBouncer.isScrimmed();
- } else {
- return mPrimaryBouncerInteractor.isScrimmed();
- }
+ return mPrimaryBouncerInteractor.isScrimmed();
}
/**
* Returns if bouncer is animating away
*/
public boolean bouncerIsAnimatingAway() {
- if (mPrimaryBouncer != null) {
- return mPrimaryBouncer.isAnimatingAway();
- } else {
- return mPrimaryBouncerInteractor.isAnimatingAway();
- }
-
+ return mPrimaryBouncerInteractor.isAnimatingAway();
}
/**
* Returns if bouncer will dismiss with action
*/
public boolean primaryBouncerWillDismissWithAction() {
- if (mPrimaryBouncer != null) {
- return mPrimaryBouncer.willDismissWithAction();
- } else {
- return mPrimaryBouncerInteractor.willDismissWithAction();
- }
+ return mPrimaryBouncerInteractor.willDismissWithAction();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index ae48c2d3..50cce45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -17,5 +17,5 @@
public interface StatusBarWindowCallback {
void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing,
- boolean isDozing, boolean panelExpanded);
+ boolean isDozing, boolean panelExpanded, boolean isDreaming);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 510482d..39ad31f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -53,7 +53,7 @@
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
-import com.android.systemui.util.kotlin.pairwiseBy
+import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -66,10 +66,10 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
@@ -287,22 +287,13 @@
*/
@SuppressLint("MissingPermission")
override val activeSubChangedInGroupEvent =
- flow {
- activeMobileDataSubscriptionId.pairwiseBy { prevVal: Int, newVal: Int ->
- if (!defaultMobileNetworkConnectivity.value.isValidated) {
- return@pairwiseBy
- }
- val prevSub = subscriptionManager.getActiveSubscriptionInfo(prevVal)
- val nextSub = subscriptionManager.getActiveSubscriptionInfo(newVal)
+ activeMobileDataSubscriptionId
+ .pairwise()
+ .mapNotNull { (prevVal: Int, newVal: Int) ->
+ val prevSub = subscriptionManager.getActiveSubscriptionInfo(prevVal)?.groupUuid
+ val nextSub = subscriptionManager.getActiveSubscriptionInfo(newVal)?.groupUuid
- if (prevSub == null || nextSub == null) {
- return@pairwiseBy
- }
-
- if (prevSub.groupUuid != null && prevSub.groupUuid == nextSub.groupUuid) {
- emit(Unit)
- }
- }
+ if (prevSub != null && prevSub == nextSub) Unit else null
}
.flowOn(bgDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index d3ff357..491f3a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -97,15 +97,20 @@
)
}
- fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
+ fun logOnCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities,
+ isDefaultNetworkCallback: Boolean,
+ ) {
buffer.log(
SB_LOGGING_TAG,
LogLevel.INFO,
{
+ bool1 = isDefaultNetworkCallback
int1 = network.getNetId()
str1 = networkCapabilities.toString()
},
- { "onCapabilitiesChanged: net=$int1 capabilities=$str1" }
+ { "onCapabilitiesChanged[default=$bool1]: net=$int1 capabilities=$str1" }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index d26499c..8669047 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -114,13 +114,17 @@
network: Network,
networkCapabilities: NetworkCapabilities
) {
+ logger.logOnCapabilitiesChanged(
+ network,
+ networkCapabilities,
+ isDefaultNetworkCallback = true,
+ )
+
// This method will always be called immediately after the network
// becomes the default, in addition to any time the capabilities change
// while the network is the default.
- // If this network contains valid wifi info, then wifi is the default
- // network.
- val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
- trySend(wifiInfo != null)
+ // If this network is a wifi network, then wifi is the default network.
+ trySend(isWifiNetwork(networkCapabilities))
}
override fun onLost(network: Network) {
@@ -152,7 +156,11 @@
network: Network,
networkCapabilities: NetworkCapabilities
) {
- logger.logOnCapabilitiesChanged(network, networkCapabilities)
+ logger.logOnCapabilitiesChanged(
+ network,
+ networkCapabilities,
+ isDefaultNetworkCallback = false,
+ )
wifiNetworkChangeEvents.tryEmit(Unit)
@@ -253,16 +261,30 @@
networkCapabilities: NetworkCapabilities
): WifiInfo? {
return when {
- networkCapabilities.hasTransport(TRANSPORT_WIFI) ->
- networkCapabilities.transportInfo as WifiInfo?
networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ->
// Sometimes, cellular networks can act as wifi networks (known as VCN --
// virtual carrier network). So, see if this cellular network has wifi info.
Utils.tryGetWifiInfoForVcn(networkCapabilities)
+ networkCapabilities.hasTransport(TRANSPORT_WIFI) ->
+ if (networkCapabilities.transportInfo is WifiInfo) {
+ networkCapabilities.transportInfo as WifiInfo
+ } else {
+ null
+ }
else -> null
}
}
+ /** True if these capabilities represent a wifi network. */
+ private fun isWifiNetwork(networkCapabilities: NetworkCapabilities): Boolean {
+ return when {
+ networkCapabilities.hasTransport(TRANSPORT_WIFI) -> true
+ networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ->
+ Utils.tryGetWifiInfoForVcn(networkCapabilities) != null
+ else -> false
+ }
+ }
+
private fun createWifiNetworkModel(
wifiInfo: WifiInfo,
network: Network,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 9946b4b..5dcafb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import android.annotation.WorkerThread;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
@@ -255,7 +254,6 @@
setTorchMode(enabled);
mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
mSecureSettings.putInt(Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
- mBroadcastSender.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index df8d161..1065d33 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -327,7 +327,7 @@
// appropriately.
activeViews.remove(displayInfo)
listeners.forEach {
- it.onInfoPermanentlyRemoved(id)
+ it.onInfoPermanentlyRemoved(id, removalReason)
}
// No need to time the view out since it's already gone
@@ -393,7 +393,7 @@
activeViews.remove(it)
logger.logViewExpiration(it.info)
listeners.forEach { listener ->
- listener.onInfoPermanentlyRemoved(it.info.id)
+ listener.onInfoPermanentlyRemoved(it.info.id, REMOVAL_REASON_TIME_EXPIRED)
}
}
}
@@ -457,7 +457,7 @@
* Called whenever a [DisplayInfo] with the given [id] has been removed and will never be
* displayed again (unless another call to [updateView] is made).
*/
- fun onInfoPermanentlyRemoved(id: String)
+ fun onInfoPermanentlyRemoved(id: String, reason: String)
}
/** A container for all the display-related state objects. */
@@ -494,6 +494,7 @@
}
private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
+private const val REMOVAL_REASON_TIME_EXPIRED = "TIMEOUT_EXPIRED_BEFORE_REDISPLAY"
private const val MIN_REQUIRED_TIME_FOR_REDISPLAY = 1000
private data class IconInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 9e0bbb7..46f13cc 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -206,31 +206,44 @@
}
override fun animateViewIn(view: ViewGroup) {
- ViewHierarchyAnimator.animateAddition(
- view.getInnerView(),
- ViewHierarchyAnimator.Hotspot.TOP,
- Interpolators.EMPHASIZED_DECELERATE,
- duration = ANIMATION_IN_DURATION,
- includeMargins = true,
- includeFadeIn = true,
- // We can only request focus once the animation finishes.
- onAnimationEnd = {
- maybeGetAccessibilityFocus(view.getTag(INFO_TAG) as ChipbarInfo?, view)
- },
- )
+ val onAnimationEnd = Runnable {
+ maybeGetAccessibilityFocus(view.getTag(INFO_TAG) as ChipbarInfo?, view)
+ }
+ val added =
+ ViewHierarchyAnimator.animateAddition(
+ view.getInnerView(),
+ ViewHierarchyAnimator.Hotspot.TOP,
+ Interpolators.EMPHASIZED_DECELERATE,
+ duration = ANIMATION_IN_DURATION,
+ includeMargins = true,
+ includeFadeIn = true,
+ // We can only request focus once the animation finishes.
+ onAnimationEnd = onAnimationEnd,
+ )
+ // If the view doesn't get animated, the [onAnimationEnd] runnable won't get run. So, just
+ // run it immediately.
+ if (!added) {
+ onAnimationEnd.run()
+ }
}
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val innerView = view.getInnerView()
innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
- ViewHierarchyAnimator.animateRemoval(
- innerView,
- ViewHierarchyAnimator.Hotspot.TOP,
- Interpolators.EMPHASIZED_ACCELERATE,
- ANIMATION_OUT_DURATION,
- includeMargins = true,
- onAnimationEnd,
- )
+ val removed =
+ ViewHierarchyAnimator.animateRemoval(
+ innerView,
+ ViewHierarchyAnimator.Hotspot.TOP,
+ Interpolators.EMPHASIZED_ACCELERATE,
+ ANIMATION_OUT_DURATION,
+ includeMargins = true,
+ onAnimationEnd,
+ )
+ // If the view doesn't get animated, the [onAnimationEnd] runnable won't get run. So, just
+ // run it immediately.
+ if (!removed) {
+ onAnimationEnd.run()
+ }
updateGestureListening()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index c0f0390..8cb4deb 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -174,7 +174,7 @@
val callback =
object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
+ override fun onUserChanging(newUser: Int, userContext: Context) {
send()
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 7033ccd..5d896cb 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -236,7 +236,8 @@
// Store callback in a field so it won't get GC'd
mStatusBarWindowCallback =
- (keyguardShowing, keyguardOccluded, bouncerShowing, isDozing, panelExpanded) ->
+ (keyguardShowing, keyguardOccluded, bouncerShowing, isDozing, panelExpanded,
+ isDreaming) ->
mBubbles.onNotificationPanelExpandedChanged(panelExpanded);
notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index df6752a..d1650b7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -17,7 +17,6 @@
package com.android.keyguard;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
-import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
@@ -41,7 +40,6 @@
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.anyObject;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -95,7 +93,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import android.service.dreams.IDreamManager;
import android.service.trust.TrustAgentService;
import android.telephony.ServiceState;
@@ -238,8 +235,6 @@
@Mock
private UiEventLogger mUiEventLogger;
@Mock
- private PowerManager mPowerManager;
- @Mock
private GlobalSettings mGlobalSettings;
private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
@Mock
@@ -834,6 +829,19 @@
}
@Test
+ public void doesNotTryToAuthenticateWhenKeyguardIsNotShowingButOccluded_whenAssistant() {
+ mKeyguardUpdateMonitor.setKeyguardShowing(false, true);
+ mKeyguardUpdateMonitor.setAssistantVisible(true);
+
+ verify(mFaceManager, never()).authenticate(any(),
+ any(),
+ any(),
+ any(),
+ anyInt(),
+ anyBoolean());
+ }
+
+ @Test
public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
@@ -846,6 +854,32 @@
}
@Test
+ public void faceUnlockDoesNotRunWhenDeviceIsGoingToSleepWithAssistantVisible() {
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
+ mKeyguardUpdateMonitor.setAssistantVisible(true);
+
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ mTestableLooper.processAllMessages();
+ clearInvocations(mFaceManager);
+
+ // Device going to sleep while assistant is visible
+ mKeyguardUpdateMonitor.handleStartedGoingToSleep(0);
+ mKeyguardUpdateMonitor.handleFinishedGoingToSleep(0);
+ mTestableLooper.moveTimeForward(DEFAULT_CANCEL_SIGNAL_TIMEOUT);
+ mTestableLooper.processAllMessages();
+
+ mKeyguardUpdateMonitor.handleKeyguardReset();
+
+ assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse();
+ verify(mFaceManager, never()).authenticate(any(),
+ any(),
+ any(),
+ any(),
+ anyInt(),
+ anyBoolean());
+ }
+
+ @Test
public void testIgnoresAuth_whenTrustAgentOnKeyguard_withoutBypass() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
@@ -1872,28 +1906,6 @@
}
@Test
- public void testFingerAcquired_wakesUpPowerManager() {
- cleanupKeyguardUpdateMonitor();
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.kg_wake_on_acquire_start, true);
- mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
- fingerprintAcquireStart();
-
- verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
- }
-
- @Test
- public void testFingerAcquired_doesNotWakeUpPowerManager() {
- cleanupKeyguardUpdateMonitor();
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.kg_wake_on_acquire_start, false);
- mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
- fingerprintAcquireStart();
-
- verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
- }
-
- @Test
public void testDreamingStopped_faceDoesNotRun() {
mKeyguardUpdateMonitor.dispatchDreamingStopped();
mTestableLooper.processAllMessages();
@@ -2374,11 +2386,6 @@
.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out");
}
- private void fingerprintAcquireStart() {
- mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
- .onAuthenticationAcquired(FINGERPRINT_ACQUIRED_START);
- }
-
private void deviceInPostureStateOpened() {
mKeyguardUpdateMonitor.mPostureCallback.onPostureChanged(DEVICE_POSTURE_OPENED);
}
@@ -2525,7 +2532,7 @@
mAuthController, mTelephonyListenerManager,
mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig,
mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker,
- mPowerManager, mTrustManager, mSubscriptionManager, mUserManager,
+ mTrustManager, mSubscriptionManager, mUserManager,
mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
mFaceWakeUpTriggersConfig, mDevicePostureController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index b92c5d0..fd931b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -51,14 +51,24 @@
import android.view.WindowMetrics
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.MODERN_ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestCoroutineScope
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -101,6 +111,9 @@
@Captor lateinit var overlayCaptor: ArgumentCaptor<View>
@Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
+ private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+ private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+ private val featureFlags = FakeFeatureFlags()
private val executor = FakeExecutor(FakeSystemClock())
private lateinit var overlayController: ISidefpsController
private lateinit var sideFpsController: SideFpsController
@@ -121,6 +134,24 @@
@Before
fun setup() {
+ featureFlags.set(MODERN_ALTERNATE_BOUNCER, true)
+ keyguardBouncerRepository =
+ KeyguardBouncerRepository(
+ mock(ViewMediatorCallback::class.java),
+ FakeSystemClock(),
+ TestCoroutineScope(),
+ mock(TableLogBuffer::class.java),
+ )
+ alternateBouncerInteractor =
+ AlternateBouncerInteractor(
+ keyguardBouncerRepository,
+ FakeBiometricRepository(),
+ FakeDeviceEntryFingerprintAuthRepository(),
+ FakeSystemClock(),
+ mock(KeyguardUpdateMonitor::class.java),
+ featureFlags,
+ )
+
context.addMockSystemService(DisplayManager::class.java, displayManager)
context.addMockSystemService(WindowManager::class.java, windowManager)
@@ -217,7 +248,10 @@
displayManager,
executor,
handler,
- dumpManager
+ alternateBouncerInteractor,
+ TestCoroutineScope(),
+ featureFlags,
+ dumpManager,
)
overlayController =
@@ -507,6 +541,26 @@
private fun verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible: Boolean) {
sideFpsController.overlayOffsets = sensorLocation
+ }
+
+ fun alternateBouncerVisibility_showAndHideSideFpsUI() = testWithDisplay {
+ // WHEN alternate bouncer is visible
+ keyguardBouncerRepository.setAlternateVisible(true)
+ executor.runAllReady()
+
+ // THEN side fps shows UI
+ verify(windowManager).addView(any(), any())
+ verify(windowManager, never()).removeView(any())
+
+ // WHEN alternate bouncer is no longer visible
+ keyguardBouncerRepository.setAlternateVisible(false)
+ executor.runAllReady()
+
+ // THEN side fps UI is hidden
+ verify(windowManager).removeView(any())
+ }
+
+ private fun hidesWithTaskbar(visible: Boolean) {
overlayController.show(SENSOR_ID, REASON_UNKNOWN)
executor.runAllReady()
@@ -515,7 +569,7 @@
verify(windowManager).addView(any(), any())
verify(windowManager, never()).removeView(any())
- verify(sideFpsView).visibility = if (sfpsViewVisible) View.VISIBLE else View.GONE
+ verify(sideFpsView).visibility = if (visible) View.VISIBLE else View.GONE
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index 9c32c38..498cc29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -37,7 +37,6 @@
import com.android.systemui.shade.ShadeExpansionListener;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
@@ -71,7 +70,6 @@
protected @Mock SystemUIDialogManager mDialogManager;
protected @Mock UdfpsController mUdfpsController;
protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
- protected @Mock KeyguardBouncer mBouncer;
protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -152,8 +150,6 @@
mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
mFeatureFlags.set(Flags.MODERN_ALTERNATE_BOUNCER, useModernBouncer);
mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
- when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(
- useModernBouncer ? null : mBouncer);
UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController(
mView,
mStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 7715f7f..f437a8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -32,24 +32,17 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
import com.android.systemui.shade.ShadeExpansionListener;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {
- private @Captor ArgumentCaptor<PrimaryBouncerExpansionCallback>
- mBouncerExpansionCallbackCaptor;
- private PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
-
@Override
public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
return createUdfpsKeyguardViewController(/* useModernBouncer */ false,
@@ -62,11 +55,9 @@
captureStatusBarStateListeners();
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
- captureBouncerExpansionCallback();
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
- mBouncerExpansionCallback.onVisibilityChanged(true);
-
+ when(mView.getUnpausedAlpha()).thenReturn(0);
assertTrue(mController.shouldPauseAuth());
}
@@ -304,11 +295,6 @@
verify(mView, atLeastOnce()).setPauseAuth(false);
}
- private void captureBouncerExpansionCallback() {
- verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
- mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
- }
-
@Test
// TODO(b/259264861): Tracking Bug
public void testUdfpsExpandedOverlayOn() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 9060922..81a6bc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.BiometricRepository
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
@@ -91,6 +92,7 @@
AlternateBouncerInteractor(
keyguardBouncerRepository,
mock(BiometricRepository::class.java),
+ mock(DeviceEntryFingerprintAuthRepository::class.java),
mock(SystemClock::class.java),
mock(KeyguardUpdateMonitor::class.java),
mock(FeatureFlags::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
index 34ddf79..8e20303 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -110,6 +110,28 @@
expectedInteractionEvent = InteractionEvent.UP,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
+ // MotionEvent.ACTION_HOVER_ENTER
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+ expectedInteractionEvent = InteractionEvent.DOWN,
+ expectedPointerOnSensorId = POINTER_ID_1,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+ expectedInteractionEvent = InteractionEvent.UP,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
// MotionEvent.ACTION_MOVE
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_MOVE,
@@ -161,6 +183,35 @@
expectedInteractionEvent = InteractionEvent.UNCHANGED,
expectedPointerOnSensorId = POINTER_ID_2,
),
+ // MotionEvent.ACTION_HOVER_MOVE
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+ expectedInteractionEvent = InteractionEvent.DOWN,
+ expectedPointerOnSensorId = POINTER_ID_1,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = POINTER_ID_1,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+ expectedInteractionEvent = InteractionEvent.UP,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
// MotionEvent.ACTION_UP
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_UP,
@@ -183,6 +234,28 @@
expectedInteractionEvent = InteractionEvent.UNCHANGED,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
+ // MotionEvent.ACTION_HOVER_EXIT
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+ expectedInteractionEvent = InteractionEvent.UP,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+ expectedInteractionEvent = InteractionEvent.UP,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
// MotionEvent.ACTION_CANCEL
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_CANCEL,
@@ -315,13 +388,7 @@
expectedPointerOnSensorId = POINTER_ID_2
)
)
- .flatten() +
- listOf(
- genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_ENTER),
- genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_MOVE),
- genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT)
- )
- .flatten()
+ .flatten()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index bdd496e..71c335e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.clipboardoverlay;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
-
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
import static org.junit.Assert.assertEquals;
@@ -33,7 +31,6 @@
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.os.PersistableBundle;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import androidx.test.filters.SmallTest;
@@ -41,9 +38,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.util.DeviceConfigProxyFake;
import org.junit.Before;
import org.junit.Test;
@@ -63,18 +57,11 @@
@Mock
private ClipboardManager mClipboardManager;
@Mock
- private ClipboardOverlayControllerLegacyFactory mClipboardOverlayControllerLegacyFactory;
- @Mock
- private ClipboardOverlayControllerLegacy mOverlayControllerLegacy;
- @Mock
private ClipboardOverlayController mOverlayController;
@Mock
private ClipboardToast mClipboardToast;
@Mock
private UiEventLogger mUiEventLogger;
- @Mock
- private FeatureFlags mFeatureFlags;
- private DeviceConfigProxyFake mDeviceConfigProxy;
private ClipData mSampleClipData;
private String mSampleSource = "Example source";
@@ -97,8 +84,6 @@
mOverlayControllerProvider = () -> mOverlayController;
MockitoAnnotations.initMocks(this);
- when(mClipboardOverlayControllerLegacyFactory.create(any()))
- .thenReturn(mOverlayControllerLegacy);
when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
Settings.Secure.putInt(
mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 1);
@@ -108,26 +93,13 @@
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
- mDeviceConfigProxy = new DeviceConfigProxyFake();
-
- mClipboardListener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
- mClipboardToast, mClipboardManager, mUiEventLogger, mFeatureFlags);
+ mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
+ mClipboardToast, mClipboardManager, mUiEventLogger);
}
- @Test
- public void test_disabled() {
- mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
- "false", false);
- mClipboardListener.start();
- verifyZeroInteractions(mClipboardManager);
- verifyZeroInteractions(mUiEventLogger);
- }
@Test
- public void test_enabled() {
- mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
- "true", false);
+ public void test_initialization() {
mClipboardListener.start();
verify(mClipboardManager).addPrimaryClipChangedListener(any());
verifyZeroInteractions(mUiEventLogger);
@@ -135,45 +107,6 @@
@Test
public void test_consecutiveCopies() {
- when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
-
- mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
- "true", false);
- mClipboardListener.start();
- mClipboardListener.onPrimaryClipChanged();
-
- verify(mClipboardOverlayControllerLegacyFactory).create(any());
-
- verify(mOverlayControllerLegacy).setClipData(
- mClipDataCaptor.capture(), mStringCaptor.capture());
-
- assertEquals(mSampleClipData, mClipDataCaptor.getValue());
- assertEquals(mSampleSource, mStringCaptor.getValue());
-
- verify(mOverlayControllerLegacy).setOnSessionCompleteListener(mRunnableCaptor.capture());
-
- // Should clear the overlay controller
- mRunnableCaptor.getValue().run();
-
- mClipboardListener.onPrimaryClipChanged();
-
- verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
-
- // Not calling the runnable here, just change the clip again and verify that the overlay is
- // NOT recreated.
-
- mClipboardListener.onPrimaryClipChanged();
-
- verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
- verifyZeroInteractions(mOverlayControllerProvider);
- }
-
- @Test
- public void test_consecutiveCopies_new() {
- when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
-
- mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
- "true", false);
mClipboardListener.start();
mClipboardListener.onPrimaryClipChanged();
@@ -200,7 +133,6 @@
mClipboardListener.onPrimaryClipChanged();
verify(mOverlayControllerProvider, times(2)).get();
- verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
}
@Test
@@ -231,23 +163,6 @@
@Test
public void test_logging_enterAndReenter() {
- when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
-
- mClipboardListener.start();
-
- mClipboardListener.onPrimaryClipChanged();
- mClipboardListener.onPrimaryClipChanged();
-
- verify(mUiEventLogger, times(1)).log(
- ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
- verify(mUiEventLogger, times(1)).log(
- ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
- }
-
- @Test
- public void test_logging_enterAndReenter_new() {
- when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
-
mClipboardListener.start();
mClipboardListener.onPrimaryClipChanged();
@@ -271,6 +186,5 @@
ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
verify(mClipboardToast, times(1)).showCopiedToast();
verifyZeroInteractions(mOverlayControllerProvider);
- verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
new file mode 100644
index 0000000..fe352fd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.common.ui.view
+
+import android.view.ViewConfiguration
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel
+import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel.Down
+import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel.Move
+import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel.Up
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class LongPressHandlingViewInteractionHandlerTest : SysuiTestCase() {
+
+ @Mock private lateinit var postDelayed: (Runnable, Long) -> DisposableHandle
+ @Mock private lateinit var onLongPressDetected: (Int, Int) -> Unit
+ @Mock private lateinit var onSingleTapDetected: () -> Unit
+
+ private lateinit var underTest: LongPressHandlingViewInteractionHandler
+
+ private var isAttachedToWindow: Boolean = true
+ private var delayedRunnable: Runnable? = null
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(postDelayed.invoke(any(), any())).thenAnswer { invocation ->
+ delayedRunnable = invocation.arguments[0] as Runnable
+ DisposableHandle { delayedRunnable = null }
+ }
+
+ underTest =
+ LongPressHandlingViewInteractionHandler(
+ postDelayed = postDelayed,
+ isAttachedToWindow = { isAttachedToWindow },
+ onLongPressDetected = onLongPressDetected,
+ onSingleTapDetected = onSingleTapDetected,
+ )
+ underTest.isLongPressHandlingEnabled = true
+ }
+
+ @Test
+ fun `long-press`() = runTest {
+ val downX = 123
+ val downY = 456
+ dispatchTouchEvents(
+ Down(
+ x = downX,
+ y = downY,
+ ),
+ Move(
+ distanceMoved = ViewConfiguration.getTouchSlop() - 0.1f,
+ ),
+ )
+ delayedRunnable?.run()
+
+ verify(onLongPressDetected).invoke(downX, downY)
+ verify(onSingleTapDetected, never()).invoke()
+ }
+
+ @Test
+ fun `long-press but feature not enabled`() = runTest {
+ underTest.isLongPressHandlingEnabled = false
+ dispatchTouchEvents(
+ Down(
+ x = 123,
+ y = 456,
+ ),
+ )
+
+ assertThat(delayedRunnable).isNull()
+ verify(onLongPressDetected, never()).invoke(any(), any())
+ verify(onSingleTapDetected, never()).invoke()
+ }
+
+ @Test
+ fun `long-press but view not attached`() = runTest {
+ isAttachedToWindow = false
+ dispatchTouchEvents(
+ Down(
+ x = 123,
+ y = 456,
+ ),
+ )
+ delayedRunnable?.run()
+
+ verify(onLongPressDetected, never()).invoke(any(), any())
+ verify(onSingleTapDetected, never()).invoke()
+ }
+
+ @Test
+ fun `dragged too far to be considered a long-press`() = runTest {
+ dispatchTouchEvents(
+ Down(
+ x = 123,
+ y = 456,
+ ),
+ Move(
+ distanceMoved = ViewConfiguration.getTouchSlop() + 0.1f,
+ ),
+ )
+
+ assertThat(delayedRunnable).isNull()
+ verify(onLongPressDetected, never()).invoke(any(), any())
+ verify(onSingleTapDetected, never()).invoke()
+ }
+
+ @Test
+ fun `held down too briefly to be considered a long-press`() = runTest {
+ dispatchTouchEvents(
+ Down(
+ x = 123,
+ y = 456,
+ ),
+ Up(
+ distanceMoved = ViewConfiguration.getTouchSlop().toFloat(),
+ gestureDuration = ViewConfiguration.getLongPressTimeout() - 1L,
+ ),
+ )
+
+ assertThat(delayedRunnable).isNull()
+ verify(onLongPressDetected, never()).invoke(any(), any())
+ verify(onSingleTapDetected).invoke()
+ }
+
+ private fun dispatchTouchEvents(
+ vararg models: MotionEventModel,
+ ) {
+ models.forEach { model -> underTest.onTouchEvent(model) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index ed40c90..85f9961 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -20,6 +20,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.os.UserHandle
import android.service.controls.ControlsProviderService
@@ -95,6 +96,7 @@
@Mock lateinit var dumpManager: DumpManager
@Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
@Mock lateinit var featureFlags: FeatureFlags
+ @Mock lateinit var packageManager: PackageManager
val sharedPreferences = FakeSharedPreferences()
lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
@@ -124,6 +126,7 @@
ControlsUiControllerImpl(
Lazy { controlsController },
context,
+ packageManager,
uiExecutor,
bgExecutor,
Lazy { controlsListingController },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
new file mode 100644
index 0000000..dbaf94f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.controls.ui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class OverflowMenuAdapterTest : SysuiTestCase() {
+
+ @Test
+ fun testGetItemId() {
+ val ids = listOf(27L, 73L)
+ val labels = listOf("first", "second")
+ val adapter =
+ OverflowMenuAdapter(
+ context,
+ layoutId = 0,
+ labels.zip(ids).map { OverflowMenuAdapter.MenuItem(it.first, it.second) }
+ ) { true }
+
+ ids.forEachIndexed { index, id -> assertThat(adapter.getItemId(index)).isEqualTo(id) }
+ }
+
+ @Test
+ fun testCheckEnabled() {
+ val ids = listOf(27L, 73L)
+ val labels = listOf("first", "second")
+ val adapter =
+ OverflowMenuAdapter(
+ context,
+ layoutId = 0,
+ labels.zip(ids).map { OverflowMenuAdapter.MenuItem(it.first, it.second) }
+ ) { position -> position == 0 }
+
+ assertThat(adapter.isEnabled(0)).isTrue()
+ assertThat(adapter.isEnabled(1)).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index ff883eb..84c9786 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -39,8 +39,6 @@
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
import com.android.systemui.statusbar.BlurUtils;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import org.junit.Before;
import org.junit.Test;
@@ -81,12 +79,6 @@
BlurUtils mBlurUtils;
@Mock
- StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-
- @Mock
- KeyguardBouncer mBouncer;
-
- @Mock
ViewRootImpl mViewRoot;
@Mock
@@ -106,7 +98,6 @@
when(mDreamOverlayContainerView.getResources()).thenReturn(mResources);
when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
- when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(mBouncer);
when(mDreamOverlayContainerView.getViewRootImpl()).thenReturn(mViewRoot);
mController = new DreamOverlayContainerViewController(
@@ -114,7 +105,6 @@
mComplicationHostViewController,
mDreamOverlayContentView,
mDreamOverlayStatusBarViewController,
- mStatusBarKeyguardViewManager,
mBlurUtils,
mHandler,
mResources,
@@ -170,7 +160,8 @@
final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor =
ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
mController.onViewAttached();
- verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture());
+ verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback(
+ bouncerExpansionCaptor.capture());
bouncerExpansionCaptor.getValue().onExpansionChanged(0.5f);
verify(mBlurUtils, never()).applyBlur(eq(mViewRoot), anyInt(), eq(false));
@@ -181,7 +172,8 @@
final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor =
ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
mController.onViewAttached();
- verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture());
+ verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback(
+ bouncerExpansionCaptor.capture());
final float blurRadius = 1337f;
when(mBlurUtils.blurRadiusOfRatio(anyFloat())).thenReturn(blurRadius);
@@ -217,6 +209,27 @@
}
@Test
+ public void testSkipEntryAnimationsWhenExitingLowLight() {
+ ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ when(mStateController.isLowLightActive()).thenReturn(false);
+
+ // Call onInit so that the callback is added.
+ mController.onInit();
+ verify(mStateController).addCallback(callbackCaptor.capture());
+
+ // Send the signal that low light is exiting
+ callbackCaptor.getValue().onExitLowLight();
+
+ // View is attached to trigger animations.
+ mController.onViewAttached();
+
+ // Entry animations should be started then immediately ended to skip to the end.
+ verify(mAnimationsController).startEntryAnimations();
+ verify(mAnimationsController).endAnimations();
+ }
+
+ @Test
public void testCancelDreamEntryAnimationsOnDetached() {
mController.onViewAttached();
mController.onViewDetached();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index ee989d1..b7d0f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -251,6 +251,30 @@
}
@Test
+ public void testNotifyLowLightExit() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor, true);
+
+ stateController.addCallback(mCallback);
+ mExecutor.runAllReady();
+ assertThat(stateController.isLowLightActive()).isFalse();
+
+ // Turn low light on then off to trigger the exiting callback.
+ stateController.setLowLightActive(true);
+ stateController.setLowLightActive(false);
+
+ // Callback was only called once, when
+ mExecutor.runAllReady();
+ verify(mCallback, times(1)).onExitLowLight();
+ assertThat(stateController.isLowLightActive()).isFalse();
+
+ // Set with false again, which should not cause the callback to trigger again.
+ stateController.setLowLightActive(false);
+ mExecutor.runAllReady();
+ verify(mCallback, times(1)).onExitLowLight();
+ }
+
+ @Test
public void testNotifyEntryAnimationsFinishedChanged() {
final DreamOverlayStateController stateController =
new DreamOverlayStateController(mExecutor, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
new file mode 100644
index 0000000..19347c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.dreams.conditions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.condition.Condition;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamConditionTest extends SysuiTestCase {
+ @Mock
+ Context mContext;
+
+ @Mock
+ Condition.Callback mCallback;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Ensure a dreaming state immediately triggers the condition.
+ */
+ @Test
+ public void testInitialState() {
+ final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED);
+ when(mContext.registerReceiver(any(), any())).thenReturn(intent);
+ final DreamCondition condition = new DreamCondition(mContext);
+ condition.addCallback(mCallback);
+ condition.start();
+
+ verify(mCallback).onConditionChanged(eq(condition));
+ assertThat(condition.isConditionMet()).isTrue();
+ }
+
+ /**
+ * Ensure that changing dream state triggers condition.
+ */
+ @Test
+ public void testChange() {
+ final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED);
+ final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ when(mContext.registerReceiver(receiverCaptor.capture(), any())).thenReturn(intent);
+ final DreamCondition condition = new DreamCondition(mContext);
+ condition.addCallback(mCallback);
+ condition.start();
+ clearInvocations(mCallback);
+ receiverCaptor.getValue().onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STOPPED));
+ verify(mCallback).onConditionChanged(eq(condition));
+ assertThat(condition.isConditionMet()).isFalse();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index a807407..178b9cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -424,6 +424,32 @@
verify(gestureListener2).onDown(eq(followupEvent));
}
+ @Test
+ public void testOnRemovedCallbackOnStopMonitoring() {
+ final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+ final DreamTouchHandler.TouchSession.Callback callback =
+ Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)));
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+ session.registerCallback(callback);
+
+ environment.executeAll();
+
+ environment.updateLifecycle(observerOwnerPair -> {
+ observerOwnerPair.first.onPause(observerOwnerPair.second);
+ });
+
+ environment.executeAll();
+
+ verify(callback).onRemoved();
+ }
+
public GestureDetector.OnGestureListener registerGestureListener(DreamTouchHandler handler) {
final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
GestureDetector.OnGestureListener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index c0af0cb..fb54d6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -62,6 +62,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -184,6 +185,7 @@
mainDispatcher = testDispatcher,
backgroundHandler = backgroundHandler,
)
+ underTest.mainDispatcher = UnconfinedTestDispatcher()
underTest.attachInfoForTesting(
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index 8da4eae..58cdec4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -19,6 +19,7 @@
import android.app.StatusBarManager
import android.content.Context
+import android.content.pm.PackageManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.camera.CameraGestureHelper
@@ -32,7 +33,6 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
-import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -43,16 +43,19 @@
@Mock private lateinit var cameraGestureHelper: CameraGestureHelper
@Mock private lateinit var context: Context
+ @Mock private lateinit var packageManager: PackageManager
private lateinit var underTest: CameraQuickAffordanceConfig
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ setLaunchable(true)
underTest =
CameraQuickAffordanceConfig(
context,
+ packageManager,
) {
cameraGestureHelper
}
@@ -86,6 +89,7 @@
}
private fun setLaunchable(isLaunchable: Boolean) {
- whenever(cameraGestureHelper.canCameraGestureBeLaunched(anyInt())).thenReturn(isLaunchable)
+ whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY))
+ .thenReturn(isLaunchable)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index c4ae2db..9203f05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -55,7 +55,11 @@
MockitoAnnotations.initMocks(this)
testScope = TestScope()
- underTest = DeviceEntryFingerprintAuthRepositoryImpl(keyguardUpdateMonitor)
+ underTest =
+ DeviceEntryFingerprintAuthRepositoryImpl(
+ keyguardUpdateMonitor,
+ testScope.backgroundScope,
+ )
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 1da7241..68fff26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.util.time.FakeSystemClock
@@ -46,6 +47,8 @@
private lateinit var underTest: AlternateBouncerInteractor
private lateinit var bouncerRepository: KeyguardBouncerRepository
private lateinit var biometricRepository: FakeBiometricRepository
+ private lateinit var deviceEntryFingerprintAuthRepository:
+ FakeDeviceEntryFingerprintAuthRepository
@Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var bouncerLogger: TableLogBuffer
@@ -62,11 +65,13 @@
bouncerLogger,
)
biometricRepository = FakeBiometricRepository()
+ deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
underTest =
AlternateBouncerInteractor(
bouncerRepository,
biometricRepository,
+ deviceEntryFingerprintAuthRepository,
systemClock,
keyguardUpdateMonitor,
featureFlags,
@@ -112,6 +117,14 @@
}
@Test
+ fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() {
+ givenCanShowAlternateBouncer()
+ deviceEntryFingerprintAuthRepository.setLockedOut(true)
+
+ assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+ }
+
+ @Test
fun show_whenCanShow() {
givenCanShowAlternateBouncer()
@@ -148,6 +161,7 @@
biometricRepository.setFingerprintEnrolled(true)
biometricRepository.setStrongBiometricAllowed(true)
biometricRepository.setFingerprintEnabledByDevicePolicy(true)
+ deviceEntryFingerprintAuthRepository.setLockedOut(false)
}
private fun givenCannotShowAlternateBouncer() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
new file mode 100644
index 0000000..9d60b16
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -0,0 +1,213 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+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.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardLongPressInteractorTest : SysuiTestCase() {
+
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var logger: UiEventLogger
+
+ private lateinit var underTest: KeyguardLongPressInteractor
+
+ private lateinit var testScope: TestScope
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ runBlocking { createUnderTest() }
+ }
+
+ @Test
+ fun isEnabled() =
+ testScope.runTest {
+ val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled)
+ KeyguardState.values().forEach { keyguardState ->
+ setUpState(
+ keyguardState = keyguardState,
+ )
+
+ if (keyguardState == KeyguardState.LOCKSCREEN) {
+ assertThat(isEnabled()).isTrue()
+ } else {
+ assertThat(isEnabled()).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun `isEnabled - always false when quick settings are visible`() =
+ testScope.runTest {
+ val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled)
+ KeyguardState.values().forEach { keyguardState ->
+ setUpState(
+ keyguardState = keyguardState,
+ isQuickSettingsVisible = true,
+ )
+
+ assertThat(isEnabled()).isFalse()
+ }
+ }
+
+ @Test
+ fun `long-pressed - pop-up clicked - starts activity`() =
+ testScope.runTest {
+ val menu = collectLastValue(underTest.menu)
+ runCurrent()
+
+ val x = 100
+ val y = 123
+ underTest.onLongPress(x, y)
+ assertThat(menu()).isNotNull()
+ assertThat(menu()?.position?.x).isEqualTo(x)
+ assertThat(menu()?.position?.y).isEqualTo(y)
+
+ menu()?.onClicked?.invoke()
+
+ assertThat(menu()).isNull()
+ verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+ }
+
+ @Test
+ fun `long-pressed - pop-up dismissed - never starts activity`() =
+ testScope.runTest {
+ val menu = collectLastValue(underTest.menu)
+ runCurrent()
+
+ menu()?.onDismissed?.invoke()
+
+ assertThat(menu()).isNull()
+ verify(activityStarter, never()).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+ }
+
+ @Suppress("DEPRECATION") // We're okay using ACTION_CLOSE_SYSTEM_DIALOGS on system UI.
+ @Test
+ fun `long pressed - close dialogs broadcast received - popup dismissed`() =
+ testScope.runTest {
+ val menu = collectLastValue(underTest.menu)
+ runCurrent()
+
+ underTest.onLongPress(123, 456)
+ assertThat(menu()).isNotNull()
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { broadcastReceiver ->
+ broadcastReceiver.onReceive(context, Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
+ }
+
+ assertThat(menu()).isNull()
+ }
+
+ @Test
+ fun `logs when menu is shown`() =
+ testScope.runTest {
+ collectLastValue(underTest.menu)
+ runCurrent()
+
+ underTest.onLongPress(100, 123)
+
+ verify(logger)
+ .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN)
+ }
+
+ @Test
+ fun `logs when menu is clicked`() =
+ testScope.runTest {
+ val menu = collectLastValue(underTest.menu)
+ runCurrent()
+
+ underTest.onLongPress(100, 123)
+ menu()?.onClicked?.invoke()
+
+ verify(logger)
+ .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
+ }
+
+ private suspend fun createUnderTest(
+ isLongPressFeatureEnabled: Boolean = true,
+ isRevampedWppFeatureEnabled: Boolean = true,
+ ) {
+ testScope = TestScope()
+ keyguardRepository = FakeKeyguardRepository()
+ keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+
+ underTest =
+ KeyguardLongPressInteractor(
+ unsafeContext = context,
+ scope = testScope.backgroundScope,
+ transitionInteractor =
+ KeyguardTransitionInteractor(
+ repository = keyguardTransitionRepository,
+ ),
+ repository = keyguardRepository,
+ activityStarter = activityStarter,
+ logger = logger,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
+ set(Flags.REVAMPED_WALLPAPER_UI, isRevampedWppFeatureEnabled)
+ },
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ )
+ setUpState()
+ }
+
+ private suspend fun setUpState(
+ keyguardState: KeyguardState = KeyguardState.LOCKSCREEN,
+ isQuickSettingsVisible: Boolean = false,
+ ) {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ to = keyguardState,
+ ),
+ )
+ keyguardRepository.setQuickSettingsVisible(isVisible = isQuickSettingsVisible)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 5a7a3d4..3a871b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -519,6 +519,43 @@
}
@Test
+ fun `GONE to LOCKSREEN`() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GONE
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN the keyguard starts to show
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to AOD should occur
+ assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.GONE)
+ assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun `GONE to DREAMING`() =
testScope.runTest {
// GIVEN a device that is not dreaming or dozing
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 022afdd..4b04b7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -234,7 +234,10 @@
@Test
fun `startButton - in preview mode - visible even when keyguard not showing`() =
testScope.runTest {
- underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
+ underTest.enablePreviewMode(
+ initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ shouldHighlightSelectedAffordance = true,
+ )
repository.setKeyguardShowing(false)
val latest = collectLastValue(underTest.startButton)
@@ -263,6 +266,7 @@
icon = icon,
canShowWhileLocked = false,
intent = Intent("action"),
+ isSelected = true,
),
configKey = configKey,
)
@@ -270,6 +274,60 @@
}
@Test
+ fun `endButton - in higlighted preview mode - dimmed when other is selected`() =
+ testScope.runTest {
+ underTest.enablePreviewMode(
+ initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ shouldHighlightSelectedAffordance = true,
+ )
+ repository.setKeyguardShowing(false)
+ val startButton = collectLastValue(underTest.startButton)
+ val endButton = collectLastValue(underTest.endButton)
+
+ val icon: Icon = mock()
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ isActivated = true,
+ icon = icon,
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ ),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ isActivated = true,
+ icon = icon,
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ ),
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = endButton(),
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = false,
+ isActivated = true,
+ icon = icon,
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ isDimmed = true,
+ ),
+ configKey = configKey,
+ )
+ }
+
+ @Test
fun `endButton - present - visible model - do nothing on click`() =
testScope.runTest {
repository.setKeyguardShowing(true)
@@ -377,7 +435,10 @@
@Test
fun `alpha - in preview mode - does not change`() =
testScope.runTest {
- underTest.enablePreviewMode(null)
+ underTest.enablePreviewMode(
+ initiallySelectedSlotId = null,
+ shouldHighlightSelectedAffordance = false,
+ )
val value = collectLastValue(underTest.alpha)
assertThat(value()).isEqualTo(1f)
@@ -639,6 +700,8 @@
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
+ assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected)
+ assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed)
if (testConfig.isVisible) {
assertThat(viewModel.icon).isEqualTo(testConfig.icon)
viewModel.onClicked.invoke(
@@ -664,6 +727,8 @@
val icon: Icon? = null,
val canShowWhileLocked: Boolean = false,
val intent: Intent? = null,
+ val isSelected: Boolean = false,
+ val isDimmed: Boolean = false,
) {
init {
check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
index c88f84a..54fc493 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -71,7 +71,7 @@
waitUntilComplete(info.animator!!)
}
- suspend private fun waitUntilComplete(animator: ValueAnimator) {
+ private suspend fun waitUntilComplete(animator: ValueAnimator) {
withContext(Dispatchers.Main) {
val startTime = System.currentTimeMillis()
while (!isTerminated && animator.isRunning()) {
@@ -96,6 +96,6 @@
override fun setFrameDelay(delay: Long) {}
companion object {
- private const val MAX_TEST_DURATION = 100L
+ private const val MAX_TEST_DURATION = 200L
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
similarity index 70%
copy from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
copy to packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
index 0e7bf8d..8da1c64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
@@ -22,7 +22,6 @@
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogcatEchoTracker
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
@@ -31,16 +30,15 @@
import org.mockito.Mockito.mock
@SmallTest
-class MediaTttLoggerTest : SysuiTestCase() {
+class MediaTttLoggerUtilsTest : SysuiTestCase() {
private lateinit var buffer: LogBuffer
- private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
@Before
- fun setUp () {
- buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
- .create("buffer", 10)
- logger = MediaTttLogger(DEVICE_TYPE_TAG, buffer)
+ fun setUp() {
+ buffer =
+ LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+ .create("buffer", 10)
}
@Test
@@ -49,35 +47,33 @@
val id = "test id"
val packageName = "this.is.a.package"
- logger.logStateChange(stateName, id, packageName)
+ MediaTttLoggerUtils.logStateChange(buffer, TAG, stateName, id, packageName)
val actualString = getStringFromBuffer()
- assertThat(actualString).contains(DEVICE_TYPE_TAG)
+ assertThat(actualString).contains(TAG)
assertThat(actualString).contains(stateName)
assertThat(actualString).contains(id)
assertThat(actualString).contains(packageName)
}
@Test
- fun logPackageNotFound_bufferHasPackageName() {
- val packageName = "this.is.a.package"
-
- logger.logPackageNotFound(packageName)
+ fun logStateChangeError_hasState() {
+ MediaTttLoggerUtils.logStateChangeError(buffer, TAG, 3456)
val actualString = getStringFromBuffer()
- assertThat(actualString).contains(packageName)
+ assertThat(actualString).contains(TAG)
+ assertThat(actualString).contains("3456")
}
@Test
- fun logRemovalBypass_bufferHasReasons() {
- val removalReason = "fakeRemovalReason"
- val bypassReason = "fakeBypassReason"
+ fun logPackageNotFound_bufferHasPackageName() {
+ val packageName = "this.is.a.package"
- logger.logRemovalBypass(removalReason, bypassReason)
+ MediaTttLoggerUtils.logPackageNotFound(buffer, TAG, packageName)
val actualString = getStringFromBuffer()
- assertThat(actualString).contains(removalReason)
- assertThat(actualString).contains(bypassReason)
+ assertThat(actualString).contains(TAG)
+ assertThat(actualString).contains(packageName)
}
private fun getStringFromBuffer(): String {
@@ -87,4 +83,4 @@
}
}
-private const val DEVICE_TYPE_TAG = "TEST TYPE"
+private const val TAG = "TEST TAG"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 561867f..8055b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -25,7 +25,6 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -41,7 +40,6 @@
private lateinit var appIconFromPackageName: Drawable
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var applicationInfo: ApplicationInfo
- @Mock private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
@Before
fun setUp() {
@@ -67,8 +65,7 @@
@Test
fun getIconInfoFromPackageName_nullPackageName_returnsDefault() {
- val iconInfo =
- MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger)
+ val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null) {}
assertThat(iconInfo.isAppIcon).isFalse()
assertThat(iconInfo.contentDescription.loadContentDescription(context))
@@ -77,8 +74,19 @@
}
@Test
+ fun getIconInfoFromPackageName_nullPackageName_exceptionFnNotTriggered() {
+ var exceptionTriggered = false
+
+ MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null) {
+ exceptionTriggered = true
+ }
+
+ assertThat(exceptionTriggered).isFalse()
+ }
+
+ @Test
fun getIconInfoFromPackageName_invalidPackageName_returnsDefault() {
- val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger)
+ val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName") {}
assertThat(iconInfo.isAppIcon).isFalse()
assertThat(iconInfo.contentDescription.loadContentDescription(context))
@@ -87,8 +95,19 @@
}
@Test
+ fun getIconInfoFromPackageName_invalidPackageName_exceptionFnTriggered() {
+ var exceptionTriggered = false
+
+ MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = "fakePackageName") {
+ exceptionTriggered = true
+ }
+
+ assertThat(exceptionTriggered).isTrue()
+ }
+
+ @Test
fun getIconInfoFromPackageName_validPackageName_returnsAppInfo() {
- val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger)
+ val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME) {}
assertThat(iconInfo.isAppIcon).isTrue()
assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Loaded(appIconFromPackageName))
@@ -96,6 +115,17 @@
}
@Test
+ fun getIconInfoFromPackageName_validPackageName_exceptionFnNotTriggered() {
+ var exceptionTriggered = false
+
+ MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME) {
+ exceptionTriggered = true
+ }
+
+ assertThat(exceptionTriggered).isFalse()
+ }
+
+ @Test
fun iconInfo_toTintedIcon_loaded() {
val contentDescription = ContentDescription.Loaded("test")
val drawable = context.getDrawable(R.drawable.ic_cake)!!
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
index b3e621e..bd042c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -24,7 +24,6 @@
import android.view.accessibility.AccessibilityManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -35,7 +34,7 @@
class FakeMediaTttChipControllerReceiver(
commandQueue: CommandQueue,
context: Context,
- logger: MediaTttLogger<ChipReceiverInfo>,
+ logger: MediaTttReceiverLogger,
windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 5e40898..dba2da7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -36,7 +36,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
@@ -68,7 +67,7 @@
@Mock
private lateinit var applicationInfo: ApplicationInfo
@Mock
- private lateinit var logger: MediaTttLogger<ChipReceiverInfo>
+ private lateinit var logger: MediaTttReceiverLogger
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
similarity index 71%
rename from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
index 0e7bf8d..95df484 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.taptotransfer.common
+package com.android.systemui.media.taptotransfer.receiver
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -22,7 +22,6 @@
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogcatEchoTracker
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
@@ -31,16 +30,17 @@
import org.mockito.Mockito.mock
@SmallTest
-class MediaTttLoggerTest : SysuiTestCase() {
+class MediaTttReceiverLoggerTest : SysuiTestCase() {
private lateinit var buffer: LogBuffer
- private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
+ private lateinit var logger: MediaTttReceiverLogger
@Before
- fun setUp () {
- buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
- .create("buffer", 10)
- logger = MediaTttLogger(DEVICE_TYPE_TAG, buffer)
+ fun setUp() {
+ buffer =
+ LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+ .create("buffer", 10)
+ logger = MediaTttReceiverLogger(buffer)
}
@Test
@@ -52,13 +52,20 @@
logger.logStateChange(stateName, id, packageName)
val actualString = getStringFromBuffer()
- assertThat(actualString).contains(DEVICE_TYPE_TAG)
assertThat(actualString).contains(stateName)
assertThat(actualString).contains(id)
assertThat(actualString).contains(packageName)
}
@Test
+ fun logStateChangeError_hasState() {
+ logger.logStateChangeError(3456)
+
+ val actualString = getStringFromBuffer()
+ assertThat(actualString).contains("3456")
+ }
+
+ @Test
fun logPackageNotFound_bufferHasPackageName() {
val packageName = "this.is.a.package"
@@ -68,23 +75,9 @@
assertThat(actualString).contains(packageName)
}
- @Test
- fun logRemovalBypass_bufferHasReasons() {
- val removalReason = "fakeRemovalReason"
- val bypassReason = "fakeBypassReason"
-
- logger.logRemovalBypass(removalReason, bypassReason)
-
- val actualString = getStringFromBuffer()
- assertThat(actualString).contains(removalReason)
- assertThat(actualString).contains(bypassReason)
- }
-
private fun getStringFromBuffer(): String {
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
return stringWriter.toString()
}
}
-
-private const val DEVICE_TYPE_TAG = "TEST TYPE"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 54d4460..c63ca3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -40,16 +40,13 @@
import com.android.systemui.common.shared.model.Text.Companion.loadText
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
-import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
-import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -92,7 +89,7 @@
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var chipbarLogger: ChipbarLogger
- @Mock private lateinit var logger: MediaTttLogger<ChipbarInfo>
+ @Mock private lateinit var logger: MediaTttSenderLogger
@Mock private lateinit var mediaTttFlags: MediaTttFlags
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var powerManager: PowerManager
@@ -139,7 +136,7 @@
uiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
chipbarCoordinator =
- FakeChipbarCoordinator(
+ ChipbarCoordinator(
context,
chipbarLogger,
windowManager,
@@ -163,6 +160,7 @@
chipbarCoordinator,
commandQueue,
context,
+ dumpManager,
logger,
mediaTttFlags,
uiEventLogger,
@@ -181,6 +179,7 @@
chipbarCoordinator,
commandQueue,
context,
+ dumpManager,
logger,
mediaTttFlags,
uiEventLogger,
@@ -542,6 +541,7 @@
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
verify(windowManager).addView(viewCaptor.capture(), any())
verify(windowManager).removeView(viewCaptor.value)
+ verify(logger).logStateMapRemoval(eq(DEFAULT_ID), any())
}
@Test
@@ -741,6 +741,99 @@
verify(windowManager, never()).addView(any(), any())
}
+ /** Regression test for b/266217596. */
+ @Test
+ fun toReceiver_triggeredThenFar_thenSucceeded_updatesToSucceeded() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null,
+ )
+
+ // WHEN a FAR command comes in
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null,
+ )
+
+ // THEN it is ignored and the chipbar is stilled displayed
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+ verify(windowManager, never()).removeView(any())
+
+ // WHEN a SUCCEEDED command comes in
+ val succeededRouteInfo =
+ MediaRoute2Info.Builder(DEFAULT_ID, "Tablet Succeeded")
+ .addFeature("feature")
+ .setClientPackageName(PACKAGE_NAME)
+ .build()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ succeededRouteInfo,
+ /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ },
+ )
+
+ // THEN it is *not* marked as an invalid transition and the chipbar updates to the succeeded
+ // state. (The "invalid transition" would be FAR => SUCCEEDED.)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(
+ ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText(
+ "Tablet Succeeded"
+ )
+ )
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ }
+
+ /** Regression test for b/266217596. */
+ @Test
+ fun toThisDevice_triggeredThenFar_thenSucceeded_updatesToSucceeded() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null,
+ )
+
+ // WHEN a FAR command comes in
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null,
+ )
+
+ // THEN it is ignored and the chipbar is stilled displayed
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+ verify(windowManager, never()).removeView(any())
+
+ // WHEN a SUCCEEDED command comes in
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ },
+ )
+
+ // THEN it is *not* marked as an invalid transition and the chipbar updates to the succeeded
+ // state. (The "invalid transition" would be FAR => SUCCEEDED.)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(
+ ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText(
+ "Tablet Succeeded"
+ )
+ )
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ }
+
@Test
fun receivesNewStateFromCommandQueue_isLogged() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
@@ -934,6 +1027,7 @@
mockChipbarCoordinator,
commandQueue,
context,
+ dumpManager,
logger,
mediaTttFlags,
uiEventLogger,
@@ -960,6 +1054,7 @@
mockChipbarCoordinator,
commandQueue,
context,
+ dumpManager,
logger,
mediaTttFlags,
uiEventLogger,
@@ -977,9 +1072,10 @@
verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor))
// WHEN the listener is notified that the view has been removed
- listenerCaptor.value.onInfoPermanentlyRemoved(DEFAULT_ID)
+ listenerCaptor.value.onInfoPermanentlyRemoved(DEFAULT_ID, "reason")
// THEN the media coordinator unregisters the listener
+ verify(logger).logStateMapRemoval(DEFAULT_ID, "reason")
verify(mockChipbarCoordinator).unregisterListener(listenerCaptor.value)
}
@@ -991,6 +1087,7 @@
mockChipbarCoordinator,
commandQueue,
context,
+ dumpManager,
logger,
mediaTttFlags,
uiEventLogger,
@@ -1008,7 +1105,7 @@
verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor))
// WHEN the listener is notified that a different view has been removed
- listenerCaptor.value.onInfoPermanentlyRemoved("differentViewId")
+ listenerCaptor.value.onInfoPermanentlyRemoved("differentViewId", "reason")
// THEN the media coordinator doesn't unregister the listener
verify(mockChipbarCoordinator, never()).unregisterListener(listenerCaptor.value)
@@ -1022,6 +1119,7 @@
mockChipbarCoordinator,
commandQueue,
context,
+ dumpManager,
logger,
mediaTttFlags,
uiEventLogger,
@@ -1057,6 +1155,7 @@
mockChipbarCoordinator,
commandQueue,
context,
+ dumpManager,
logger,
mediaTttFlags,
uiEventLogger,
@@ -1086,10 +1185,173 @@
verify(mockChipbarCoordinator, atLeast(1)).registerListener(capture(listenerCaptor))
// THEN one of them is removed
- listenerCaptor.value.onInfoPermanentlyRemoved("route1")
+ listenerCaptor.value.onInfoPermanentlyRemoved("route1", "reason")
// THEN the media coordinator doesn't unregister the listener (since route2 is still active)
verify(mockChipbarCoordinator, never()).unregisterListener(listenerCaptor.value)
+ verify(logger).logStateMapRemoval("route1", "reason")
+ }
+
+ /** Regression test for b/266218672. */
+ @Test
+ fun twoIdsDisplayed_oldIdIsFar_viewStillDisplayed() {
+ // WHEN there are two different media transfers with different IDs
+ val route1 =
+ MediaRoute2Info.Builder("route1", OTHER_DEVICE_NAME)
+ .addFeature("feature")
+ .setClientPackageName(PACKAGE_NAME)
+ .build()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ route1,
+ null,
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ MediaRoute2Info.Builder("route2", "Route 2 name")
+ .addFeature("feature")
+ .setClientPackageName(PACKAGE_NAME)
+ .build(),
+ null,
+ )
+ val newView = getChipbarView()
+
+ // WHEN there's a FAR event for the earlier one
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ route1,
+ null,
+ )
+
+ // THEN it's ignored and the more recent one is still displayed
+ assertThat(newView.getChipText())
+ .isEqualTo(
+ ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText("Route 2 name")
+ )
+ }
+
+ /** Regression test for b/266218672. */
+ @Test
+ fun receiverSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToEnd() {
+ displayReceiverTriggered()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null,
+ )
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+ verify(windowManager).removeView(any())
+
+ reset(windowManager)
+
+ // WHEN we try to show ALMOST_CLOSE_TO_END
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null,
+ )
+
+ // THEN it succeeds
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST.getExpectedStateText())
+ }
+
+ /** Regression test for b/266218672. */
+ @Test
+ fun receiverSucceededThenTimedOut_internalStateResetAndCanDisplayReceiverTriggered() {
+ displayReceiverTriggered()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null,
+ )
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+ verify(windowManager).removeView(any())
+
+ reset(windowManager)
+
+ // WHEN we try to show RECEIVER_TRIGGERED
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null,
+ )
+
+ // THEN it succeeds
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+ }
+
+ /** Regression test for b/266218672. */
+ @Test
+ fun toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToStart() {
+ displayThisDeviceTriggered()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null,
+ )
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+ verify(windowManager).removeView(any())
+
+ reset(windowManager)
+
+ // WHEN we try to show ALMOST_CLOSE_TO_START
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null,
+ )
+
+ // THEN it succeeds
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText())
+ }
+
+ /** Regression test for b/266218672. */
+ @Test
+ fun toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayThisDeviceTriggered() {
+ displayThisDeviceTriggered()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null,
+ )
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+ verify(windowManager).removeView(any())
+
+ reset(windowManager)
+
+ // WHEN we try to show THIS_DEVICE_TRIGGERED
+ val newRouteInfo =
+ MediaRoute2Info.Builder(DEFAULT_ID, "New Name")
+ .addFeature("feature")
+ .setClientPackageName(PACKAGE_NAME)
+ .build()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ newRouteInfo,
+ null,
+ )
+
+ // THEN it succeeds
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(
+ ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText("New Name")
+ )
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
}
private fun getChipbarView(): ViewGroup {
@@ -1109,8 +1371,10 @@
private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button)
- private fun ChipStateSender.getExpectedStateText(): String? {
- return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
+ private fun ChipStateSender.getExpectedStateText(
+ otherDeviceName: String = OTHER_DEVICE_NAME,
+ ): String? {
+ return this.getChipTextString(context, otherDeviceName).loadText(context)
}
// display receiver triggered state helper method to make sure we start from a valid state
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
similarity index 62%
copy from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
copy to packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
index 0e7bf8d..0033757 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.taptotransfer.common
+package com.android.systemui.media.taptotransfer.sender
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -22,7 +22,6 @@
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogcatEchoTracker
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
@@ -31,16 +30,17 @@
import org.mockito.Mockito.mock
@SmallTest
-class MediaTttLoggerTest : SysuiTestCase() {
+class MediaTttSenderLoggerTest : SysuiTestCase() {
private lateinit var buffer: LogBuffer
- private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
+ private lateinit var logger: MediaTttSenderLogger
@Before
- fun setUp () {
- buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
- .create("buffer", 10)
- logger = MediaTttLogger(DEVICE_TYPE_TAG, buffer)
+ fun setUp() {
+ buffer =
+ LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+ .create("buffer", 10)
+ logger = MediaTttSenderLogger(buffer)
}
@Test
@@ -52,13 +52,20 @@
logger.logStateChange(stateName, id, packageName)
val actualString = getStringFromBuffer()
- assertThat(actualString).contains(DEVICE_TYPE_TAG)
assertThat(actualString).contains(stateName)
assertThat(actualString).contains(id)
assertThat(actualString).contains(packageName)
}
@Test
+ fun logStateChangeError_hasState() {
+ logger.logStateChangeError(3456)
+
+ val actualString = getStringFromBuffer()
+ assertThat(actualString).contains("3456")
+ }
+
+ @Test
fun logPackageNotFound_bufferHasPackageName() {
val packageName = "this.is.a.package"
@@ -80,11 +87,35 @@
assertThat(actualString).contains(bypassReason)
}
+ @Test
+ fun logStateMap_bufferHasInfo() {
+ val map =
+ mapOf(
+ "123" to ChipStateSender.ALMOST_CLOSE_TO_START_CAST,
+ "456" to ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ )
+
+ logger.logStateMap(map)
+
+ val actualString = getStringFromBuffer()
+ assertThat(actualString).contains("123")
+ assertThat(actualString).contains(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.name)
+ assertThat(actualString).contains("456")
+ assertThat(actualString).contains(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.name)
+ }
+
+ @Test
+ fun logStateMapRemoval_bufferHasInfo() {
+ logger.logStateMapRemoval("456", "testReason")
+
+ val actualString = getStringFromBuffer()
+ assertThat(actualString).contains("456")
+ assertThat(actualString).contains("testReason")
+ }
+
private fun getStringFromBuffer(): String {
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
return stringWriter.toString()
}
}
-
-private const val DEVICE_TYPE_TAG = "TEST TYPE"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
index bbe60f4..18be92b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
@@ -16,17 +16,14 @@
package com.android.systemui.notetask
-import android.content.ComponentName
+import android.app.role.RoleManager
import android.content.Intent
-import android.content.pm.ActivityInfo
-import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
-import android.content.pm.PackageManager.ResolveInfoFlags
-import android.content.pm.ResolveInfo
import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -47,172 +44,39 @@
internal class NoteTaskIntentResolverTest : SysuiTestCase() {
@Mock lateinit var packageManager: PackageManager
+ @Mock lateinit var roleManager: RoleManager
- private lateinit var resolver: NoteTaskIntentResolver
+ private lateinit var underTest: NoteTaskIntentResolver
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- resolver = NoteTaskIntentResolver(packageManager)
- }
-
- private fun createResolveInfo(
- activityInfo: ActivityInfo? = createActivityInfo(),
- ): ResolveInfo {
- return ResolveInfo().apply { this.activityInfo = activityInfo }
- }
-
- private fun createActivityInfo(
- packageName: String = "PackageName",
- name: String? = "ActivityName",
- exported: Boolean = true,
- enabled: Boolean = true,
- showWhenLocked: Boolean = true,
- turnScreenOn: Boolean = true,
- ): ActivityInfo {
- return ActivityInfo().apply {
- this.name = name
- this.exported = exported
- this.enabled = enabled
- if (showWhenLocked) {
- flags = flags or ActivityInfo.FLAG_SHOW_WHEN_LOCKED
- }
- if (turnScreenOn) {
- flags = flags or ActivityInfo.FLAG_TURN_SCREEN_ON
- }
- this.applicationInfo = ApplicationInfo().apply { this.packageName = packageName }
- }
- }
-
- private fun givenQueryIntentActivities(block: () -> List<ResolveInfo>) {
- whenever(packageManager.queryIntentActivities(any(), any<ResolveInfoFlags>()))
- .thenReturn(block())
- }
-
- private fun givenResolveActivity(block: () -> ResolveInfo?) {
- whenever(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenReturn(block())
+ underTest = NoteTaskIntentResolver(context, roleManager)
}
@Test
- fun resolveIntent_shouldReturnNotesIntent() {
- givenQueryIntentActivities { listOf(createResolveInfo()) }
- givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo()) }
+ fun resolveIntent_shouldReturnIntentInStylusMode() {
+ val packageName = "com.android.note.app"
+ whenever(roleManager.getRoleHoldersAsUser(NoteTaskIntentResolver.ROLE_NOTES, context.user))
+ .then { listOf(packageName) }
- val actual = resolver.resolveIntent()
+ val actual = underTest.resolveIntent()
- val expected =
- Intent(ACTION_CREATE_NOTE)
- .setPackage("PackageName")
- .setComponent(ComponentName("PackageName", "ActivityName"))
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- // Compares the string representation of both intents, as they are different instances.
- assertThat(actual.toString()).isEqualTo(expected.toString())
+ requireNotNull(actual) { "Intent must not be null" }
+ assertThat(actual.action).isEqualTo(ACTION_CREATE_NOTE)
+ assertThat(actual.`package`).isEqualTo(packageName)
+ val expectedExtra = actual.getExtra(NoteTaskIntentResolver.INTENT_EXTRA_USE_STYLUS_MODE)
+ assertThat(expectedExtra).isEqualTo(true)
+ val expectedFlag = actual.flags and Intent.FLAG_ACTIVITY_NEW_TASK
+ assertThat(expectedFlag).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
}
@Test
- fun resolveIntent_activityInfoEnabledIsFalse_shouldReturnNull() {
- givenQueryIntentActivities { listOf(createResolveInfo()) }
- givenResolveActivity {
- createResolveInfo(activityInfo = createActivityInfo(enabled = false))
- }
+ fun resolveIntent_noRoleHolderIsSet_shouldReturnNull() {
+ whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskIntentResolver.ROLE_NOTES), any()))
+ .then { listOf<String>() }
- val actual = resolver.resolveIntent()
-
- assertThat(actual).isNull()
- }
-
- @Test
- fun resolveIntent_activityInfoExportedIsFalse_shouldReturnNull() {
- givenQueryIntentActivities { listOf(createResolveInfo()) }
- givenResolveActivity {
- createResolveInfo(activityInfo = createActivityInfo(exported = false))
- }
-
- val actual = resolver.resolveIntent()
-
- assertThat(actual).isNull()
- }
-
- @Test
- fun resolveIntent_activityInfoShowWhenLockedIsFalse_shouldReturnNull() {
- givenQueryIntentActivities { listOf(createResolveInfo()) }
- givenResolveActivity {
- createResolveInfo(activityInfo = createActivityInfo(showWhenLocked = false))
- }
-
- val actual = resolver.resolveIntent()
-
- assertThat(actual).isNull()
- }
-
- @Test
- fun resolveIntent_activityInfoTurnScreenOnIsFalse_shouldReturnNull() {
- givenQueryIntentActivities { listOf(createResolveInfo()) }
- givenResolveActivity {
- createResolveInfo(activityInfo = createActivityInfo(turnScreenOn = false))
- }
-
- val actual = resolver.resolveIntent()
-
- assertThat(actual).isNull()
- }
-
- @Test
- fun resolveIntent_activityInfoNameIsBlank_shouldReturnNull() {
- givenQueryIntentActivities { listOf(createResolveInfo()) }
- givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = "")) }
-
- val actual = resolver.resolveIntent()
-
- assertThat(actual).isNull()
- }
-
- @Test
- fun resolveIntent_activityInfoNameIsNull_shouldReturnNull() {
- givenQueryIntentActivities { listOf(createResolveInfo()) }
- givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = null)) }
-
- val actual = resolver.resolveIntent()
-
- assertThat(actual).isNull()
- }
-
- @Test
- fun resolveIntent_activityInfoIsNull_shouldReturnNull() {
- givenQueryIntentActivities { listOf(createResolveInfo()) }
- givenResolveActivity { createResolveInfo(activityInfo = null) }
-
- val actual = resolver.resolveIntent()
-
- assertThat(actual).isNull()
- }
-
- @Test
- fun resolveIntent_resolveActivityIsNull_shouldReturnNull() {
- givenQueryIntentActivities { listOf(createResolveInfo()) }
- givenResolveActivity { null }
-
- val actual = resolver.resolveIntent()
-
- assertThat(actual).isNull()
- }
-
- @Test
- fun resolveIntent_packageNameIsBlank_shouldReturnNull() {
- givenQueryIntentActivities {
- listOf(createResolveInfo(createActivityInfo(packageName = "")))
- }
-
- val actual = resolver.resolveIntent()
-
- assertThat(actual).isNull()
- }
-
- @Test
- fun resolveIntent_activityNotFoundForAction_shouldReturnNull() {
- givenQueryIntentActivities { emptyList() }
-
- val actual = resolver.resolveIntent()
+ val actual = underTest.resolveIntent()
assertThat(actual).isNull()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
index 43fb1bd..dee1cc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
@@ -59,6 +59,7 @@
@SmallTest
public class AutoAddTrackerTest extends SysuiTestCase {
+ private static final int END_POSITION = -1;
private static final int USER = 0;
@Mock
@@ -142,6 +143,29 @@
}
@Test
+ public void testRestoredTilePositionPreserved() {
+ verify(mBroadcastDispatcher).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
+ String restoredTiles = "saver,internet,work,cast";
+ Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
+
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ assertEquals(2, mAutoTracker.getRestoredTilePosition("work"));
+ }
+
+ @Test
+ public void testNoRestoredTileReturnsEndPosition() {
+ verify(mBroadcastDispatcher).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
+ Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, null);
+
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ assertEquals(END_POSITION, mAutoTracker.getRestoredTilePosition("work"));
+ }
+
+ @Test
public void testBroadcastReceiverRegistered() {
verify(mBroadcastDispatcher).registerReceiver(
any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
index b6a595b..7ba2cf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -35,9 +35,33 @@
@Test
fun testCreateShareIntent() {
val uri = Uri.parse("content://fake")
+
+ val output = ActionIntentCreator.createShareIntent(uri)
+
+ assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER)
+ assertFlagsSet(
+ Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_CLEAR_TASK or
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ output.flags
+ )
+
+ val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND)
+ assertThat(wrappedIntent?.data).isEqualTo(uri)
+ assertThat(wrappedIntent?.type).isEqualTo("image/png")
+ assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull()
+ assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isNull()
+ assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
+ .isEqualTo(uri)
+ }
+
+ @Test
+ fun testCreateShareIntentWithSubject() {
+ val uri = Uri.parse("content://fake")
val subject = "Example subject"
- val output = ActionIntentCreator.createShareIntent(uri, subject)
+ val output = ActionIntentCreator.createShareIntentWithSubject(uri, subject)
assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER)
assertFlagsSet(
@@ -52,16 +76,34 @@
assertThat(wrappedIntent?.data).isEqualTo(uri)
assertThat(wrappedIntent?.type).isEqualTo("image/png")
assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isEqualTo(subject)
+ assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isNull()
assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
.isEqualTo(uri)
}
@Test
- fun testCreateShareIntent_noSubject() {
+ fun testCreateShareIntentWithExtraText() {
val uri = Uri.parse("content://fake")
- val output = ActionIntentCreator.createShareIntent(uri, null)
+ val extraText = "Extra text"
+
+ val output = ActionIntentCreator.createShareIntentWithExtraText(uri, extraText)
+
+ assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER)
+ assertFlagsSet(
+ Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_CLEAR_TASK or
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ output.flags
+ )
+
val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND)
+ assertThat(wrappedIntent?.data).isEqualTo(uri)
+ assertThat(wrappedIntent?.type).isEqualTo("image/png")
assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull()
+ assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(extraText)
+ assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
+ .isEqualTo(uri)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index ed3f1a0..541d6c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -73,6 +73,30 @@
assertThat(result).isEqualTo(request)
}
+ /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
+ @Test
+ fun testProcessAsync_ScreenshotData() {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
+ val request = ScreenshotData.fromRequest(
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build())
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ var result: ScreenshotData? = null
+ var callbackCount = 0
+ val callback: (ScreenshotData) -> Unit = { processedRequest: ScreenshotData ->
+ result = processedRequest
+ callbackCount++
+ }
+
+ // runs synchronously, using Unconfined Dispatcher
+ processor.processAsync(request, callback)
+
+ // Callback invoked once returning the same request (no changes)
+ assertThat(callbackCount).isEqualTo(1)
+ assertThat(result).isEqualTo(request)
+ }
+
@Test
fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
@@ -85,6 +109,11 @@
// No changes
assertThat(processedRequest).isEqualTo(request)
+
+ val screenshotData = ScreenshotData.fromRequest(request)
+ val processedData = processor.process(screenshotData)
+
+ assertThat(processedData).isEqualTo(screenshotData)
}
@Test
@@ -108,6 +137,13 @@
assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
assertThat(processedRequest.source).isEqualTo(SCREENSHOT_OTHER)
assertThat(processedRequest.topComponent).isEqualTo(component)
+
+ val processedData = processor.process(ScreenshotData.fromRequest(request))
+
+ // Request has topComponent added, but otherwise unchanged.
+ assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
+ assertThat(processedData.source).isEqualTo(SCREENSHOT_OTHER)
+ assertThat(processedData.topComponent).isEqualTo(component)
}
@Test
@@ -140,6 +176,18 @@
assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
assertThat(processedRequest.userId).isEqualTo(USER_ID)
assertThat(processedRequest.topComponent).isEqualTo(component)
+
+ val processedData = processor.process(ScreenshotData.fromRequest(request))
+
+ // Expect a task snapshot is taken, overriding the full screen mode
+ assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
+ assertThat(processedData.bitmap).isEqualTo(bitmap)
+ assertThat(processedData.screenBounds).isEqualTo(bounds)
+ assertThat(processedData.insets).isEqualTo(Insets.NONE)
+ assertThat(processedData.taskId).isEqualTo(TASK_ID)
+ assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
+ assertThat(processedRequest.userId).isEqualTo(USER_ID)
+ assertThat(processedRequest.topComponent).isEqualTo(component)
}
@Test
@@ -165,6 +213,11 @@
// No changes
assertThat(processedRequest).isEqualTo(request)
+
+ val screenshotData = ScreenshotData.fromRequest(request)
+ val processedData = processor.process(screenshotData)
+
+ assertThat(processedData).isEqualTo(screenshotData)
}
@Test
@@ -192,6 +245,11 @@
// No changes
assertThat(processedRequest).isEqualTo(request)
+
+ val screenshotData = ScreenshotData.fromRequest(request)
+ val processedData = processor.process(screenshotData)
+
+ assertThat(processedData).isEqualTo(screenshotData)
}
@Test
@@ -220,6 +278,11 @@
// Work profile, but already a task snapshot, so no changes
assertThat(processedRequest).isEqualTo(request)
+
+ val screenshotData = ScreenshotData.fromRequest(request)
+ val processedData = processor.process(screenshotData)
+
+ assertThat(processedData).isEqualTo(screenshotData)
}
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
new file mode 100644
index 0000000..43e9939
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.screenshot
+
+import android.content.ComponentName
+import android.graphics.Insets
+import android.graphics.Rect
+import android.os.UserHandle
+import android.view.WindowManager
+import com.android.internal.util.ScreenshotRequest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class ScreenshotDataTest {
+ private val type = WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+ private val source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
+ private val bounds = Rect(1, 2, 3, 4)
+ private val taskId = 123
+ private val userId = 1
+ private val insets = Insets.of(1, 2, 3, 4)
+ private val component = ComponentName("android.test", "android.test.Component")
+
+ @Test
+ fun testConstruction() {
+ val request =
+ ScreenshotRequest.Builder(type, source)
+ .setBoundsOnScreen(bounds)
+ .setInsets(insets)
+ .setTaskId(taskId)
+ .setUserId(userId)
+ .setTopComponent(component)
+ .build()
+
+ val data = ScreenshotData.fromRequest(request)
+
+ assertThat(data.source).isEqualTo(source)
+ assertThat(data.type).isEqualTo(type)
+ assertThat(data.screenBounds).isEqualTo(bounds)
+ assertThat(data.insets).isEqualTo(insets)
+ assertThat(data.taskId).isEqualTo(taskId)
+ assertThat(data.userHandle).isEqualTo(UserHandle.of(userId))
+ assertThat(data.topComponent).isEqualTo(component)
+ }
+
+ @Test
+ fun testNegativeUserId() {
+ val request = ScreenshotRequest.Builder(type, source).setUserId(-1).build()
+
+ val data = ScreenshotData.fromRequest(request)
+
+ assertThat(data.userHandle).isNull()
+ }
+
+ @Test
+ fun testPackageNameAsString() {
+ val request = ScreenshotRequest.Builder(type, source).setTopComponent(component).build()
+
+ val data = ScreenshotData.fromRequest(request)
+
+ assertThat(data.packageNameString).isEqualTo("android.test")
+ }
+
+ @Test
+ fun testPackageNameAsString_null() {
+ val request = ScreenshotRequest.Builder(type, source).build()
+
+ val data = ScreenshotData.fromRequest(request)
+
+ assertThat(data.packageNameString).isEqualTo("")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index f935019..74969d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -29,7 +29,7 @@
import android.os.UserHandle
import android.os.UserManager
import android.testing.AndroidTestingRunner
-import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
@@ -39,7 +39,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
-import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD
+import com.android.systemui.flags.Flags.SCREENSHOT_METADATA
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
import com.android.systemui.util.mockito.any
@@ -106,15 +107,22 @@
// Stub request processor as a synchronous no-op for tests with the flag enabled
doAnswer {
- val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
- val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
- consumer.accept(request)
- }
- .`when`(requestProcessor)
- .processAsync(/* request= */ any(), /* callback= */ any())
+ val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
+ val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
+ consumer.accept(request)
+ }.`when`(requestProcessor).processAsync(
+ /* request= */ any(ScreenshotRequest::class.java), /* callback= */ any())
+
+ doAnswer {
+ val request: ScreenshotData = it.getArgument(0) as ScreenshotData
+ val consumer: Consumer<ScreenshotData> = it.getArgument(1)
+ consumer.accept(request)
+ }.`when`(requestProcessor).processAsync(
+ /* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
// Flipped in selected test cases
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
+ flags.set(SCREENSHOT_METADATA, false)
service.attach(
mContext,
@@ -141,7 +149,7 @@
@Test
fun takeScreenshotFullscreen() {
val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
.setTopComponent(topComponent)
.build()
@@ -157,16 +165,34 @@
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
val logEvent = eventLogger.get(0)
- assertEquals(
- "Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId,
- SCREENSHOT_REQUESTED_KEY_CHORD.id
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- eventLogger.get(0).packageName
- )
+ assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
+ logEvent.eventId, SCREENSHOT_REQUESTED_KEY_OTHER.id)
+ assertEquals("Expected supplied package name",
+ topComponent.packageName, eventLogger.get(0).packageName)
+ }
+
+ @Test
+ fun takeScreenshotFullscreen_screenshotDataEnabled() {
+ flags.set(SCREENSHOT_METADATA, true)
+
+ val request = ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_FULLSCREEN,
+ SCREENSHOT_KEY_OTHER).setTopComponent(topComponent).build()
+
+ service.handleRequest(request, { /* onSaved */ }, callback)
+
+ verify(controller, times(1)).handleScreenshot(
+ eq(ScreenshotData.fromRequest(request)),
+ /* onSavedListener = */ any(),
+ /* requestCallback = */ any())
+
+ assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+ val logEvent = eventLogger.get(0)
+
+ assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
+ logEvent.eventId, SCREENSHOT_REQUESTED_KEY_OTHER.id)
+ assertEquals("Expected supplied package name",
+ topComponent.packageName, eventLogger.get(0).packageName)
}
@Test
@@ -218,7 +244,7 @@
whenever(userManager.isUserUnlocked).thenReturn(false)
val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
.setTopComponent(topComponent)
.build()
@@ -244,7 +270,7 @@
.thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
.setTopComponent(topComponent)
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
index 3710281..57b6b2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.settings
+import android.app.IActivityManager
import android.content.Context
import android.content.Intent
import android.content.pm.UserInfo
@@ -51,6 +52,7 @@
@Mock private lateinit var context: Context
@Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var iActivityManager: IActivityManager
@Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
@Mock(stubOnly = true) private lateinit var handler: Handler
@@ -67,7 +69,7 @@
`when`(context.user).thenReturn(UserHandle.SYSTEM)
`when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context)
- tracker = UserTrackerImpl(context, userManager, dumpManager, handler)
+ tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index e65bbb1..71ba215 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -16,11 +16,14 @@
package com.android.systemui.settings
+import android.app.IActivityManager
+import android.app.IUserSwitchObserver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.UserInfo
import android.os.Handler
+import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import android.testing.AndroidTestingRunner
@@ -29,19 +32,20 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -51,6 +55,10 @@
private lateinit var context: Context
@Mock
private lateinit var userManager: UserManager
+ @Mock
+ private lateinit var iActivityManager: IActivityManager
+ @Mock
+ private lateinit var userSwitchingReply: IRemoteCallback
@Mock(stubOnly = true)
private lateinit var dumpManager: DumpManager
@Mock(stubOnly = true)
@@ -76,7 +84,7 @@
listOf(info)
}
- tracker = UserTrackerImpl(context, userManager, dumpManager, handler)
+ tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler)
}
@Test
@@ -125,8 +133,7 @@
verify(context).registerReceiverForAllUsers(
eq(tracker), capture(captor), isNull(), eq(handler))
with(captor.value) {
- assertThat(countActions()).isEqualTo(7)
- assertThat(hasAction(Intent.ACTION_USER_SWITCHED)).isTrue()
+ assertThat(countActions()).isEqualTo(6)
assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
@@ -158,8 +165,10 @@
tracker.initialize(0)
val newID = 5
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID)
- tracker.onReceive(context, intent)
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ verify(userSwitchingReply).sendResult(any())
verify(userManager).getProfiles(newID)
@@ -272,6 +281,24 @@
}
@Test
+ fun testCallbackCalledOnUserChanging() {
+ tracker.initialize(0)
+ val callback = TestCallback()
+ tracker.addCallback(callback, executor)
+
+ val newID = 5
+
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ verify(userSwitchingReply).sendResult(any())
+
+ assertThat(callback.calledOnUserChanging).isEqualTo(1)
+ assertThat(callback.lastUser).isEqualTo(newID)
+ assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
+ }
+
+ @Test
fun testCallbackCalledOnUserChanged() {
tracker.initialize(0)
val callback = TestCallback()
@@ -279,8 +306,9 @@
val newID = 5
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID)
- tracker.onReceive(context, intent)
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitchComplete(newID)
assertThat(callback.calledOnUserChanged).isEqualTo(1)
assertThat(callback.lastUser).isEqualTo(newID)
@@ -330,25 +358,36 @@
tracker.addCallback(callback, executor)
tracker.removeCallback(callback)
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, 5)
- tracker.onReceive(context, intent)
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ verify(userSwitchingReply).sendResult(any())
+ captor.value.onUserSwitchComplete(newID)
val intentProfiles = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
.putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
tracker.onReceive(context, intentProfiles)
+ assertThat(callback.calledOnUserChanging).isEqualTo(0)
assertThat(callback.calledOnUserChanged).isEqualTo(0)
assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
}
private class TestCallback : UserTracker.Callback {
+ var calledOnUserChanging = 0
var calledOnUserChanged = 0
var calledOnProfilesChanged = 0
var lastUser: Int? = null
var lastUserContext: Context? = null
var lastUserProfiles = emptyList<UserInfo>()
+ override fun onUserChanging(newUser: Int, userContext: Context) {
+ calledOnUserChanging++
+ lastUser = newUser
+ lastUserContext = userContext
+ }
+
override fun onUserChanged(newUser: Int, userContext: Context) {
calledOnUserChanged++
lastUser = newUser
@@ -360,4 +399,4 @@
lastUserProfiles = profiles
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index ed9baf5b1..3706859 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -90,7 +90,7 @@
fun testEdgeElementsAlignedWithEdgeOrGuide_qs() {
with(qsConstraint) {
assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
- assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+ assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0.5f)
assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID)
assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0.5f)
@@ -342,7 +342,6 @@
R.id.clock to "clock",
R.id.date to "date",
R.id.privacy_container to "privacy",
- R.id.carrier_group to "carriers",
)
views.forEach { (id, name) ->
assertWithMessage("$name has 0 height in qqs")
@@ -361,7 +360,6 @@
val views = mapOf(
R.id.clock to "clock",
R.id.privacy_container to "privacy",
- R.id.carrier_group to "carriers",
)
views.forEach { (id, name) ->
expect.withMessage("$name changes height")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index f580f5e..91fef1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -692,6 +692,19 @@
assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
}
+ @Test
+ fun `carrier left padding is set when clock layout changes`() {
+ val width = 200
+ whenever(clock.width).thenReturn(width)
+ whenever(clock.scaleX).thenReturn(2.57f) // 2.57 comes from qs_header.xml
+ val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+
+ verify(clock).addOnLayoutChangeListener(capture(captor))
+ captor.value.onLayoutChange(clock, 0, 0, width, 0, 0, 0, 0, 0)
+
+ verify(carrierGroup).setPaddingRelative(514, 0, 0, 0)
+ }
+
private fun View.executeLayoutChange(
left: Int,
top: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index b568122..2bf2a81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.shade
+import android.animation.Animator
import android.animation.ValueAnimator
import android.app.StatusBarManager
import android.content.Context
@@ -45,8 +46,8 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -293,6 +294,34 @@
}
@Test
+ fun animatorListenersClearedAtEnd() {
+ val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
+ whenever(view.animate()).thenReturn(animator)
+
+ mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, 0L)
+ val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
+ verify(animator).setListener(capture(listenerCaptor))
+
+ listenerCaptor.value.onAnimationEnd(mock())
+ verify(animator).setListener(null)
+ verify(animator).setUpdateListener(null)
+ }
+
+ @Test
+ fun animatorListenersClearedOnCancel() {
+ val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
+ whenever(view.animate()).thenReturn(animator)
+
+ mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, 0L)
+ val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
+ verify(animator).setListener(capture(listenerCaptor))
+
+ listenerCaptor.value.onAnimationCancel(mock())
+ verify(animator).setListener(null)
+ verify(animator).setUpdateListener(null)
+ }
+
+ @Test
fun demoMode_attachDemoMode() {
val cb = argumentCaptor<DemoMode>()
verify(demoModeController).addCallback(capture(cb))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 0f3d4a8..28f7edf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -97,6 +97,7 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.common.ui.view.LongPressHandlingView;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
@@ -105,10 +106,12 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
@@ -197,7 +200,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class NotificationPanelViewControllerTest extends SysuiTestCase {
private static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400;
@@ -302,6 +305,8 @@
@Mock private GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ @Mock private KeyguardInteractor mKeyguardInteractor;
+ @Mock private KeyguardLongPressViewModel mKeyuardLongPressViewModel;
@Mock private CoroutineDispatcher mMainDispatcher;
@Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock private MotionEvent mDownMotionEvent;
@@ -459,6 +464,9 @@
mMainHandler = new Handler(Looper.getMainLooper());
+ when(mView.requireViewById(R.id.keyguard_long_press))
+ .thenReturn(mock(LongPressHandlingView.class));
+
mNotificationPanelViewController = new NotificationPanelViewController(
mView,
mMainHandler,
@@ -528,7 +536,9 @@
mLockscreenToOccludedTransitionViewModel,
mMainDispatcher,
mKeyguardTransitionInteractor,
- mDumpManager);
+ mDumpManager,
+ mKeyuardLongPressViewModel,
+ mKeyguardInteractor);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
index d29e9a6..fa7d869 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
@@ -20,8 +20,6 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.smartspace.preconditions.LockscreenPrecondition
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
@@ -41,9 +39,6 @@
@TestableLooper.RunWithLooper
class LockscreenPreconditionTest : SysuiTestCase() {
@Mock
- private lateinit var featureFlags: FeatureFlags
-
- @Mock
private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock
@@ -64,10 +59,7 @@
fun testFullyEnabled() {
`when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
`when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
- `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE))
- .thenReturn(true)
- val precondition = LockscreenPrecondition(featureFlags, deviceProvisionedController,
- execution)
+ val precondition = LockscreenPrecondition(deviceProvisionedController, execution)
precondition.addListener(listener)
`verify`(listener).onCriteriaChanged()
@@ -81,10 +73,8 @@
fun testProvisioning() {
`when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
`when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
- `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE))
- .thenReturn(true)
val precondition =
- LockscreenPrecondition(featureFlags, deviceProvisionedController, execution)
+ LockscreenPrecondition(deviceProvisionedController, execution)
precondition.addListener(listener)
verify(listener).onCriteriaChanged()
@@ -109,10 +99,8 @@
fun testUserSetup() {
`when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
`when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
- `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE))
- .thenReturn(true)
val precondition =
- LockscreenPrecondition(featureFlags, deviceProvisionedController, execution)
+ LockscreenPrecondition(deviceProvisionedController, execution)
precondition.addListener(listener)
verify(listener).onCriteriaChanged()
@@ -129,4 +117,4 @@
verify(listener).onCriteriaChanged()
assertThat(precondition.conditionsMet()).isTrue()
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 5124eb9..e6f272b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -37,6 +37,7 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -152,4 +153,18 @@
// and cause us to drop a frame during the LOCKSCREEN_TRANSITION_FROM_AOD CUJ.
assertEquals(0.99f, controller.dozeAmount, 0.009f)
}
+
+ @Test
+ fun testSetDreamState_invokesCallback() {
+ val listener = mock(StatusBarStateController.StateListener::class.java)
+ controller.addCallback(listener)
+
+ controller.setIsDreaming(true)
+ verify(listener).onDreamingChanged(true)
+
+ Mockito.clearInvocations(listener)
+
+ controller.setIsDreaming(false)
+ verify(listener).onDreamingChanged(false)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 4bcb54d..43b6e41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -34,7 +34,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceConfigPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin
@@ -177,8 +176,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(featureFlags.isEnabled(Flags.SMARTSPACE)).thenReturn(true)
-
`when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING))
.thenReturn(fakePrivateLockscreenSettingUri)
`when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING))
@@ -221,17 +218,6 @@
deviceProvisionedListener = deviceProvisionedCaptor.value
}
- @Test(expected = RuntimeException::class)
- fun testThrowsIfFlagIsDisabled() {
- // GIVEN the feature flag is disabled
- `when`(featureFlags.isEnabled(Flags.SMARTSPACE)).thenReturn(false)
-
- // WHEN we try to build the view
- controller.buildAndConnectView(fakeParent)
-
- // THEN an exception is thrown
- }
-
@Test
fun connectOnlyAfterDeviceIsProvisioned() {
// GIVEN an unprovisioned device and an attempt to connect
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index b6a1bb3..d7ac6b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -66,9 +66,9 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -133,18 +133,13 @@
@Mock private ShadeController mShadeController;
@Mock private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
@Mock private AssistantFeedbackController mAssistantFeedbackController;
+ @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+ @Mock private StatusBarStateController mStatusBarStateController;
@Before
public void setUp() {
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
- mDependency.injectTestDependency(DeviceProvisionedController.class,
- mDeviceProvisionedController);
- mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
- mDependency.injectTestDependency(
- OnUserInteractionCallback.class,
- mOnUserInteractionCallback);
- mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mHandler = Handler.createAsync(mTestableLooper.getLooper());
mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
@@ -155,7 +150,11 @@
mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
- mShadeController);
+ mShadeController,
+ mNotificationLockscreenUserManager,
+ mStatusBarStateController,
+ mDeviceProvisionedController,
+ mMetricsLogger);
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
mOnSettingsClickListener);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -372,7 +371,8 @@
eq(false),
eq(false),
eq(true), /* wasShownHighPriority */
- eq(mAssistantFeedbackController));
+ eq(mAssistantFeedbackController),
+ any(MetricsLogger.class));
}
@Test
@@ -406,7 +406,8 @@
eq(true),
eq(false),
eq(false), /* wasShownHighPriority */
- eq(mAssistantFeedbackController));
+ eq(mAssistantFeedbackController),
+ any(MetricsLogger.class));
}
@Test
@@ -438,7 +439,8 @@
eq(false),
eq(false),
eq(false), /* wasShownHighPriority */
- eq(mAssistantFeedbackController));
+ eq(mAssistantFeedbackController),
+ any(MetricsLogger.class));
}
////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 80a81a5..8dd0488 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -130,7 +130,6 @@
mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
- mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
// Inflate the layout
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
@@ -194,7 +193,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
assertTrue(textView.getText().toString().contains("App Name"));
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -220,7 +220,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final ImageView iconView = mNotificationInfo.findViewById(R.id.pkg_icon);
assertEquals(iconDrawable, iconView.getDrawable());
}
@@ -242,7 +243,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(GONE, nameView.getVisibility());
}
@@ -273,7 +275,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
assertTrue(nameView.getText().toString().contains("Proxied"));
@@ -296,7 +299,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(GONE, groupNameView.getVisibility());
}
@@ -324,7 +328,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(View.VISIBLE, groupNameView.getVisibility());
assertEquals("Test Group Name", groupNameView.getText());
@@ -347,7 +352,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@@ -369,7 +375,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(GONE, textView.getVisibility());
}
@@ -395,7 +402,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(VISIBLE, textView.getVisibility());
}
@@ -417,7 +425,8 @@
true,
true,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(VISIBLE, textView.getVisibility());
}
@@ -443,7 +452,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
settingsButton.performClick();
@@ -468,7 +478,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -493,7 +504,8 @@
false,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -515,7 +527,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.bindNotification(
mMockPackageManager,
mMockINotificationManager,
@@ -531,7 +544,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertEquals(View.VISIBLE, settingsButton.getVisibility());
}
@@ -556,7 +570,8 @@
true,
true,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.info).performClick();
// Verify that listener was triggered.
@@ -582,7 +597,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView channelNameView =
mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(GONE, channelNameView.getVisibility());
@@ -606,7 +622,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertEquals(GONE, mNotificationInfo.findViewById(
R.id.interruptiveness_settings).getVisibility());
assertEquals(VISIBLE, mNotificationInfo.findViewById(
@@ -630,7 +647,8 @@
true,
true,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_text);
assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(mContext.getString(R.string.notification_unblockable_desc),
@@ -673,7 +691,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_call_text);
assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(mContext.getString(R.string.notification_unblockable_call_desc),
@@ -716,7 +735,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertEquals(GONE,
mNotificationInfo.findViewById(R.id.non_configurable_call_text).getVisibility());
assertEquals(VISIBLE,
@@ -743,7 +763,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic).getVisibility());
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility());
}
@@ -765,7 +786,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic).getVisibility());
assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility());
}
@@ -789,7 +811,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertTrue(mNotificationInfo.findViewById(R.id.automatic).isSelected());
}
@@ -810,7 +833,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertTrue(mNotificationInfo.findViewById(R.id.alert).isSelected());
}
@@ -831,7 +855,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertTrue(mNotificationInfo.findViewById(R.id.silence).isSelected());
}
@@ -852,7 +877,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mTestableLooper.processAllMessages();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), eq(TEST_UID), any());
@@ -875,7 +901,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertEquals(1, mUiEventLogger.numLogs());
assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(),
mUiEventLogger.eventId(0));
@@ -899,7 +926,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.alert).performClick();
mTestableLooper.processAllMessages();
@@ -926,7 +954,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.silence).performClick();
mTestableLooper.processAllMessages();
@@ -953,7 +982,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.automatic).performClick();
mTestableLooper.processAllMessages();
@@ -981,7 +1011,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.handleCloseControls(true, false);
mTestableLooper.processAllMessages();
@@ -1008,7 +1039,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.handleCloseControls(true, false);
mTestableLooper.processAllMessages();
@@ -1043,7 +1075,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.handleCloseControls(true, false);
@@ -1071,7 +1104,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.silence).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1112,7 +1146,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.alert).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1149,7 +1184,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.automatic).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1181,7 +1217,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.silence).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1217,7 +1254,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertEquals(mContext.getString(R.string.inline_done_button),
((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -1255,7 +1293,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.silence).performClick();
mNotificationInfo.handleCloseControls(false, false);
@@ -1286,7 +1325,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertEquals(mContext.getString(R.string.inline_done_button),
((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -1324,7 +1364,8 @@
true,
false,
true,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.silence).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1353,7 +1394,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertEquals(mContext.getString(R.string.inline_done_button),
((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -1384,7 +1426,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.alert).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1419,7 +1462,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.alert).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1452,7 +1496,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.alert).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1485,7 +1530,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
mNotificationInfo.findViewById(R.id.alert).performClick();
@@ -1511,7 +1557,8 @@
true,
false,
false,
- mAssistantFeedbackController);
+ mAssistantFeedbackController,
+ mMetricsLogger);
assertFalse(mNotificationInfo.willBeRemoved());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 091bb54..8cfcc07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -510,6 +510,7 @@
@Test
public void managedProfileAdded_tileAdded() {
when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false);
+ when(mAutoAddTracker.getRestoredTilePosition(eq("work"))).thenReturn(2);
mAutoTileManager = createAutoTileManager(mContext);
Mockito.doAnswer((Answer<Object>) invocation -> {
mManagedProfileCallback = invocation.getArgument(0);
@@ -520,7 +521,7 @@
mManagedProfileCallback.onManagedProfileChanged();
- verify(mQsTileHost, times(1)).addTile(eq("work"));
+ verify(mQsTileHost, times(1)).addTile(eq("work"), eq(2));
verify(mAutoAddTracker, times(1)).setTileAdded(eq("work"));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
deleted file mode 100644
index 2f49535..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.hardware.biometrics.BiometricSourceType;
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardHostViewController;
-import com.android.keyguard.KeyguardSecurityModel;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.ViewMediatorCallback;
-import com.android.keyguard.dagger.KeyguardBouncerComponent;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
-import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class KeyguardBouncerTest extends SysuiTestCase {
-
- @Mock
- private FalsingCollector mFalsingCollector;
- @Mock
- private ViewMediatorCallback mViewMediatorCallback;
- @Mock
- private DismissCallbackRegistry mDismissCallbackRegistry;
- @Mock
- private KeyguardHostViewController mKeyguardHostViewController;
- @Mock
- private PrimaryBouncerExpansionCallback mExpansionCallback;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardBypassController mKeyguardBypassController;
- @Mock
- private Handler mHandler;
- @Mock
- private KeyguardSecurityModel mKeyguardSecurityModel;
- @Mock
- private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
- @Mock
- private KeyguardBouncerComponent mKeyguardBouncerComponent;
- private ViewGroup mContainer;
- @Rule
- public MockitoRule mRule = MockitoJUnit.rule();
- private Integer mRootVisibility = View.INVISIBLE;
- private KeyguardBouncer mBouncer;
-
- @Before
- public void setup() {
- allowTestableLooperAsMainThread();
- when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
- .thenReturn(KeyguardSecurityModel.SecurityMode.None);
- DejankUtils.setImmediate(true);
-
- mContainer = spy(new FrameLayout(getContext()));
- when(mKeyguardBouncerComponentFactory.create(mContainer)).thenReturn(
- mKeyguardBouncerComponent);
- when(mKeyguardBouncerComponent.getKeyguardHostViewController())
- .thenReturn(mKeyguardHostViewController);
-
- mBouncer = new KeyguardBouncer.Factory(getContext(), mViewMediatorCallback,
- mDismissCallbackRegistry, mFalsingCollector,
- mKeyguardStateController, mKeyguardUpdateMonitor,
- mKeyguardBypassController, mHandler, mKeyguardSecurityModel,
- mKeyguardBouncerComponentFactory)
- .create(mContainer, mExpansionCallback);
- }
-
- @Test
- public void testInflateView_doesntCrash() {
- mBouncer.inflateView();
- }
-
- @Test
- public void testShow_notifiesFalsingManager() {
- mBouncer.show(true);
- verify(mFalsingCollector).onBouncerShown();
-
- mBouncer.show(true, false);
- verifyNoMoreInteractions(mFalsingCollector);
- }
-
- /**
- * Regression test: Invisible bouncer when occluded.
- */
- @Test
- public void testShow_bouncerIsVisible() {
- // Expand notification panel as if we were in the keyguard.
- mBouncer.ensureView();
- mBouncer.setExpansion(1);
-
- reset(mKeyguardHostViewController);
-
- mBouncer.show(true);
- verify(mKeyguardHostViewController).setExpansion(0);
- }
-
- @Test
- public void testShow_notifiesVisibility() {
- mBouncer.show(true);
- verify(mKeyguardStateController).notifyBouncerShowing(eq(true));
- verify(mExpansionCallback).onStartingToShow();
-
- // Not called again when visible
- reset(mViewMediatorCallback);
- mBouncer.show(true);
- verifyNoMoreInteractions(mViewMediatorCallback);
- }
-
- @Test
- public void testShow_triesToDismissKeyguard() {
- mBouncer.show(true);
- verify(mKeyguardHostViewController).dismiss(anyInt());
- }
-
- @Test
- public void testShow_resetsSecuritySelection() {
- mBouncer.show(false);
- verify(mKeyguardHostViewController, never()).showPrimarySecurityScreen();
-
- mBouncer.hide(false);
- mBouncer.show(true);
- verify(mKeyguardHostViewController).showPrimarySecurityScreen();
- }
-
- @Test
- public void testShow_animatesKeyguardView() {
- mBouncer.show(true);
- verify(mKeyguardHostViewController).appear(anyInt());
- }
-
- @Test
- public void testShow_showsErrorMessage() {
- final String errorMessage = "an error message";
- when(mViewMediatorCallback.consumeCustomMessage()).thenReturn(errorMessage);
- mBouncer.show(true);
- verify(mKeyguardHostViewController).showErrorMessage(eq(errorMessage));
- }
-
- @Test
- public void testSetExpansion_notifiesFalsingManager() {
- mBouncer.ensureView();
- mBouncer.setExpansion(0.5f);
-
- mBouncer.setExpansion(EXPANSION_HIDDEN);
- verify(mFalsingCollector).onBouncerHidden();
- verify(mExpansionCallback).onFullyHidden();
-
- mBouncer.setExpansion(EXPANSION_VISIBLE);
- verify(mFalsingCollector).onBouncerShown();
- verify(mExpansionCallback).onFullyShown();
-
- verify(mExpansionCallback, never()).onStartingToHide();
- verify(mKeyguardHostViewController, never()).onStartingToHide();
- mBouncer.setExpansion(0.9f);
- verify(mExpansionCallback).onStartingToHide();
- verify(mKeyguardHostViewController).onStartingToHide();
- }
-
- @Test
- public void testSetExpansion_notifiesKeyguardView() {
- mBouncer.ensureView();
- mBouncer.setExpansion(0.1f);
-
- mBouncer.setExpansion(0);
- verify(mKeyguardHostViewController).onResume();
- verify(mContainer).announceForAccessibility(any());
- }
-
- @Test
- public void show_notifiesKeyguardViewController() {
- mBouncer.ensureView();
-
- mBouncer.show(/* resetSecuritySelection= */ false);
-
- verify(mKeyguardHostViewController).onBouncerVisibilityChanged(View.VISIBLE);
- }
-
- @Test
- public void testHide_notifiesFalsingManager() {
- mBouncer.hide(false);
- verify(mFalsingCollector).onBouncerHidden();
- }
-
- @Test
- public void testHide_notifiesVisibility() {
- mBouncer.hide(false);
- verify(mKeyguardStateController).notifyBouncerShowing(eq(false));
- }
-
- @Test
- public void testHide_notifiesDismissCallbackIfVisible() {
- mBouncer.hide(false);
- verifyZeroInteractions(mDismissCallbackRegistry);
- mBouncer.show(false);
- mBouncer.hide(false);
- verify(mDismissCallbackRegistry).notifyDismissCancelled();
- }
-
- @Test
- public void testHide_notShowingAnymore() {
- mBouncer.ensureView();
- mBouncer.show(false /* resetSecuritySelection */);
- mBouncer.hide(false /* destroyViews */);
- Assert.assertFalse("Not showing", mBouncer.isShowing());
- }
-
- @Test
- public void testShowPromptReason_propagates() {
- mBouncer.ensureView();
- mBouncer.showPromptReason(1);
- verify(mKeyguardHostViewController).showPromptReason(eq(1));
- }
-
- @Test
- public void testShowMessage_propagates() {
- final String message = "a message";
- mBouncer.ensureView();
- mBouncer.showMessage(message, ColorStateList.valueOf(Color.GREEN));
- verify(mKeyguardHostViewController).showMessage(
- eq(message), eq(ColorStateList.valueOf(Color.GREEN)));
- }
-
- @Test
- public void testShowOnDismissAction_showsBouncer() {
- final OnDismissAction dismissAction = () -> false;
- final Runnable cancelAction = () -> {};
- mBouncer.showWithDismissAction(dismissAction, cancelAction);
- verify(mKeyguardHostViewController).setOnDismissAction(dismissAction, cancelAction);
- Assert.assertTrue("Should be showing", mBouncer.isShowing());
- }
-
- @Test
- public void testStartPreHideAnimation_notifiesView() {
- final boolean[] ran = {false};
- final Runnable r = () -> ran[0] = true;
- mBouncer.startPreHideAnimation(r);
- Assert.assertTrue("Callback should have been invoked", ran[0]);
-
- ran[0] = false;
- mBouncer.ensureView();
- mBouncer.startPreHideAnimation(r);
- verify(mKeyguardHostViewController).startDisappearAnimation(r);
- Assert.assertFalse("Callback should have been deferred", ran[0]);
- }
-
- @Test
- public void testIsShowing_animated() {
- Assert.assertFalse("Show wasn't invoked yet", mBouncer.isShowing());
- mBouncer.show(true /* reset */);
- Assert.assertTrue("Should be showing", mBouncer.isShowing());
- }
-
- @Test
- public void testIsShowing_forSwipeUp() {
- mBouncer.setExpansion(1f);
- mBouncer.show(true /* reset */, false /* animated */);
- Assert.assertFalse("Should only be showing after collapsing notification panel",
- mBouncer.isShowing());
- mBouncer.setExpansion(0f);
- Assert.assertTrue("Should be showing", mBouncer.isShowing());
- }
-
- @Test
- public void testSetExpansion() {
- mBouncer.ensureView();
- mBouncer.setExpansion(0.5f);
- verify(mKeyguardHostViewController).setExpansion(0.5f);
- }
-
- @Test
- public void testIsFullscreenBouncer_asksKeyguardView() {
- mBouncer.ensureView();
- mBouncer.isFullscreenBouncer();
- verify(mKeyguardHostViewController).getCurrentSecurityMode();
- }
-
- @Test
- public void testIsHiding_preHideOrHide() {
- Assert.assertFalse("Should not be hiding on initial state", mBouncer.isAnimatingAway());
- mBouncer.startPreHideAnimation(null /* runnable */);
- Assert.assertTrue("Should be hiding during pre-hide", mBouncer.isAnimatingAway());
- mBouncer.hide(false /* destroyView */);
- Assert.assertFalse("Should be hidden after hide()", mBouncer.isAnimatingAway());
- }
-
- @Test
- public void testIsHiding_skipsTranslation() {
- mBouncer.show(false /* reset */);
- reset(mKeyguardHostViewController);
- mBouncer.startPreHideAnimation(null /* runnable */);
- mBouncer.setExpansion(0.5f);
- verify(mKeyguardHostViewController, never()).setExpansion(anyFloat());
- }
-
- @Test
- public void testIsSecure() {
- mBouncer.ensureView();
- for (KeyguardSecurityModel.SecurityMode mode : KeyguardSecurityModel.SecurityMode.values()){
- reset(mKeyguardSecurityModel);
- when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(mode);
- Assert.assertEquals("Security doesn't match for mode: " + mode,
- mBouncer.isSecure(), mode != KeyguardSecurityModel.SecurityMode.None);
- }
- }
-
- @Test
- public void testIsShowingScrimmed_true() {
- doAnswer(invocation -> {
- assertThat(mBouncer.isScrimmed()).isTrue();
- return null;
- }).when(mExpansionCallback).onFullyShown();
- mBouncer.show(false /* resetSecuritySelection */, true /* animate */);
- assertThat(mBouncer.isScrimmed()).isTrue();
- mBouncer.hide(false /* destroyView */);
- assertThat(mBouncer.isScrimmed()).isFalse();
- }
-
- @Test
- public void testIsShowingScrimmed_false() {
- doAnswer(invocation -> {
- assertThat(mBouncer.isScrimmed()).isFalse();
- return null;
- }).when(mExpansionCallback).onFullyShown();
- mBouncer.show(false /* resetSecuritySelection */, false /* animate */);
- assertThat(mBouncer.isScrimmed()).isFalse();
- }
-
- @Test
- public void testWillDismissWithAction() {
- mBouncer.ensureView();
- Assert.assertFalse("Action not set yet", mBouncer.willDismissWithAction());
- when(mKeyguardHostViewController.hasDismissActions()).thenReturn(true);
- Assert.assertTrue("Action should exist", mBouncer.willDismissWithAction());
- }
-
- @Test
- public void testShow_delaysIfFaceAuthIsRunning() {
- when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
- .thenReturn(true);
- when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
- mBouncer.show(true /* reset */);
-
- ArgumentCaptor<Runnable> showRunnable = ArgumentCaptor.forClass(Runnable.class);
- verify(mHandler).postDelayed(showRunnable.capture(),
- eq(KeyguardBouncer.BOUNCER_FACE_DELAY));
-
- mBouncer.hide(false /* destroyView */);
- verify(mHandler).removeCallbacks(eq(showRunnable.getValue()));
- }
-
- @Test
- public void testShow_doesNotDelaysIfFaceAuthIsNotAllowed() {
- when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
- when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
- .thenReturn(false);
- mBouncer.show(true /* reset */);
-
- verify(mHandler, never()).postDelayed(any(), anyLong());
- }
-
- @Test
- public void testShow_delaysIfFaceAuthIsRunning_unlessBypassEnabled() {
- when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
- when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
- mBouncer.show(true /* reset */);
-
- verify(mHandler, never()).postDelayed(any(), anyLong());
- }
-
- @Test
- public void testShow_delaysIfFaceAuthIsRunning_unlessFingerprintEnrolled() {
- when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0))
- .thenReturn(true);
- mBouncer.show(true /* reset */);
-
- verify(mHandler, never()).postDelayed(any(), anyLong());
- }
-
- @Test
- public void testRegisterUpdateMonitorCallback() {
- verify(mKeyguardUpdateMonitor).registerCallback(any());
- }
-
- @Test
- public void testInTransit_whenTranslation() {
- mBouncer.show(true);
- mBouncer.setExpansion(EXPANSION_HIDDEN);
- assertThat(mBouncer.inTransit()).isFalse();
- mBouncer.setExpansion(0.5f);
- assertThat(mBouncer.inTransit()).isTrue();
- mBouncer.setExpansion(EXPANSION_VISIBLE);
- assertThat(mBouncer.inTransit()).isFalse();
- }
-
- @Test
- public void testUpdateResources_delegatesToRootView() {
- mBouncer.ensureView();
- mBouncer.updateResources();
-
- // This is mocked, so won't pick up on the call to updateResources via
- // mKeyguardViewController.init(), only updateResources above.
- verify(mKeyguardHostViewController).updateResources();
- }
-
- @Test
- public void testUpdateKeyguardPosition_delegatesToRootView() {
- mBouncer.ensureView();
- mBouncer.updateKeyguardPosition(1.0f);
-
- verify(mKeyguardHostViewController).updateKeyguardPosition(1.0f);
- }
-
- @Test
- public void testExpansion_notifiesCallback() {
- mBouncer.ensureView();
- mBouncer.setExpansion(0.5f);
-
- final PrimaryBouncerExpansionCallback callback =
- mock(PrimaryBouncerExpansionCallback.class);
- mBouncer.addBouncerExpansionCallback(callback);
-
- mBouncer.setExpansion(EXPANSION_HIDDEN);
- verify(callback).onFullyHidden();
- verify(callback).onExpansionChanged(EXPANSION_HIDDEN);
-
- Mockito.clearInvocations(callback);
- mBouncer.setExpansion(EXPANSION_VISIBLE);
- verify(callback).onFullyShown();
- verify(callback).onExpansionChanged(EXPANSION_VISIBLE);
-
- Mockito.clearInvocations(callback);
- float bouncerHideAmount = 0.9f;
- // Ensure the callback only triggers once despite multiple calls to setExpansion
- // with the same value.
- mBouncer.setExpansion(bouncerHideAmount);
- mBouncer.setExpansion(bouncerHideAmount);
- verify(callback, times(1)).onStartingToHide();
- verify(callback, times(1)).onExpansionChanged(bouncerHideAmount);
-
- Mockito.clearInvocations(callback);
- mBouncer.removeBouncerExpansionCallback(callback);
- bouncerHideAmount = 0.5f;
- mBouncer.setExpansion(bouncerHideAmount);
- verify(callback, never()).onExpansionChanged(bouncerHideAmount);
- }
-
- @Test
- public void testOnResumeCalledForFullscreenBouncerOnSecondShow() {
- // GIVEN a security mode which requires fullscreen bouncer
- when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
- .thenReturn(KeyguardSecurityModel.SecurityMode.SimPin);
- mBouncer.show(true);
-
- // WHEN a second call to show occurs, the bouncer will already by visible
- reset(mKeyguardHostViewController);
- mBouncer.show(true);
-
- // THEN ensure the ViewController is told to resume
- verify(mKeyguardHostViewController).onResume();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 1ba0a36..d8446f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
@@ -109,7 +108,6 @@
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private View mNotificationContainer;
@Mock private KeyguardBypassController mBypassController;
- @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
@Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
@Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
@Mock private KeyguardMessageArea mKeyguardMessageArea;
@@ -153,8 +151,6 @@
.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM))
.thenReturn(true);
- when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(true);
-
mStatusBarKeyguardViewManager =
new StatusBarKeyguardViewManager(
getContext(),
@@ -169,7 +165,6 @@
mock(NotificationShadeWindowController.class),
mKeyguardStateController,
mock(NotificationMediaManager.class),
- mKeyguardBouncerFactory,
mKeyguardMessageAreaFactory,
Optional.of(mSysUiUnfoldComponent),
() -> mShadeController,
@@ -660,7 +655,6 @@
mock(NotificationShadeWindowController.class),
mKeyguardStateController,
mock(NotificationMediaManager.class),
- mKeyguardBouncerFactory,
mKeyguardMessageAreaFactory,
Optional.of(mSysUiUnfoldComponent),
() -> mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
deleted file mode 100644
index 55ab681..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- * Copyright (C) 2018 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.phone;
-
-import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-
-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.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewRootImpl;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
-import android.window.WindowOnBackInvokedDispatcher;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.util.LatencyTracker;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardMessageArea;
-import com.android.keyguard.KeyguardMessageAreaController;
-import com.android.keyguard.KeyguardSecurityModel;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyguard.data.BouncerView;
-import com.android.systemui.keyguard.data.BouncerViewDelegate;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
-import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.unfold.SysUIUnfoldComponent;
-
-import com.google.common.truth.Truth;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-/**
- * StatusBarKeyguardViewManager Test with deprecated KeyguardBouncer.java.
- * TODO: Delete when deleting {@link KeyguardBouncer}
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase {
- private static final ShadeExpansionChangeEvent EXPANSION_EVENT =
- expansionEvent(/* fraction= */ 0.5f, /* expanded= */ false, /* tracking= */ true);
-
- @Mock private ViewMediatorCallback mViewMediatorCallback;
- @Mock private LockPatternUtils mLockPatternUtils;
- @Mock private CentralSurfaces mCentralSurfaces;
- @Mock private ViewGroup mContainer;
- @Mock private NotificationPanelViewController mNotificationPanelView;
- @Mock private BiometricUnlockController mBiometricUnlockController;
- @Mock private SysuiStatusBarStateController mStatusBarStateController;
- @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock private View mNotificationContainer;
- @Mock private KeyguardBypassController mBypassController;
- @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
- @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
- @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
- @Mock private KeyguardBouncer mPrimaryBouncer;
- @Mock private KeyguardMessageArea mKeyguardMessageArea;
- @Mock private ShadeController mShadeController;
- @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
- @Mock private DreamOverlayStateController mDreamOverlayStateController;
- @Mock private LatencyTracker mLatencyTracker;
- @Mock private FeatureFlags mFeatureFlags;
- @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
- @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
- @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
- @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
- @Mock private BouncerView mBouncerView;
- @Mock private BouncerViewDelegate mBouncerViewDelegate;
-
- private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
- mBouncerExpansionCallback;
- private FakeKeyguardStateController mKeyguardStateController =
- spy(new FakeKeyguardStateController());
-
- @Mock private ViewRootImpl mViewRootImpl;
- @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
- @Captor
- private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
-
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mKeyguardBouncerFactory.create(
- any(ViewGroup.class),
- any(PrimaryBouncerExpansionCallback.class)))
- .thenReturn(mPrimaryBouncer);
- when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
- when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
- when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
- .thenReturn(mKeyguardMessageAreaController);
- when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
-
- mStatusBarKeyguardViewManager =
- new StatusBarKeyguardViewManager(
- getContext(),
- mViewMediatorCallback,
- mLockPatternUtils,
- mStatusBarStateController,
- mock(ConfigurationController.class),
- mKeyguardUpdateMonitor,
- mDreamOverlayStateController,
- mock(NavigationModeController.class),
- mock(DockManager.class),
- mock(NotificationShadeWindowController.class),
- mKeyguardStateController,
- mock(NotificationMediaManager.class),
- mKeyguardBouncerFactory,
- mKeyguardMessageAreaFactory,
- Optional.of(mSysUiUnfoldComponent),
- () -> mShadeController,
- mLatencyTracker,
- mKeyguardSecurityModel,
- mFeatureFlags,
- mPrimaryBouncerCallbackInteractor,
- mPrimaryBouncerInteractor,
- mBouncerView,
- mAlternateBouncerInteractor) {
- @Override
- public ViewRootImpl getViewRootImpl() {
- return mViewRootImpl;
- }
- };
- when(mViewRootImpl.getOnBackInvokedDispatcher())
- .thenReturn(mOnBackInvokedDispatcher);
- mStatusBarKeyguardViewManager.registerCentralSurfaces(
- mCentralSurfaces,
- mNotificationPanelView,
- new ShadeExpansionStateManager(),
- mBiometricUnlockController,
- mNotificationContainer,
- mBypassController);
- mStatusBarKeyguardViewManager.show(null);
- ArgumentCaptor<PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
- ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
- verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
- callbackArgumentCaptor.capture());
- mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
- }
-
- @Test
- public void dismissWithAction_AfterKeyguardGoneSetToFalse() {
- OnDismissAction action = () -> false;
- Runnable cancelAction = () -> {};
- mStatusBarKeyguardViewManager.dismissWithAction(
- action, cancelAction, false /* afterKeyguardGone */);
- verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction));
- }
-
- @Test
- public void showBouncer_onlyWhenShowing() {
- mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
- mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
- verify(mPrimaryBouncer, never()).show(anyBoolean());
- }
-
- @Test
- public void showBouncer_notWhenBouncerAlreadyShowing() {
- mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
- when(mPrimaryBouncer.isSecure()).thenReturn(true);
- mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
- verify(mPrimaryBouncer, never()).show(anyBoolean());
- }
-
- @Test
- public void showBouncer_showsTheBouncer() {
- mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncer).show(anyBoolean(), eq(true));
- }
-
- @Test
- public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
- when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
- }
-
- @Test
- public void onPanelExpansionChanged_propagatesToBouncerOnlyIfShowing() {
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
-
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(
- expansionEvent(/* fraction= */ 0.6f, /* expanded= */ false, /* tracking= */ true));
- verify(mPrimaryBouncer).setExpansion(eq(0.6f));
- }
-
- @Test
- public void onPanelExpansionChanged_duplicateEventsAreIgnored() {
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).setExpansion(eq(0.5f));
-
- reset(mPrimaryBouncer);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
- }
-
- @Test
- public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
- mStatusBarKeyguardViewManager.hide(0, 0);
- when(mPrimaryBouncer.inTransit()).thenReturn(true);
-
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).setExpansion(eq(EXPANSION_HIDDEN));
- }
-
- @Test
- public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
- mKeyguardStateController.setCanDismissLockScreen(false);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).show(eq(false), eq(false));
-
- // But not when it's already visible
- reset(mPrimaryBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
-
- // Or animating away
- reset(mPrimaryBouncer);
- when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
- }
-
- @Test
- public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
- when(mBiometricUnlockController.getMode())
- .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(
- expansionEvent(
- /* fraction= */ EXPANSION_VISIBLE,
- /* expanded= */ true,
- /* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
- }
-
- @Test
- public void onPanelExpansionChanged_neverTranslatesBouncerWhenDismissBouncer() {
- // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
- // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
- // which would mistakenly cause the bouncer to show briefly before its visibility
- // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
- // bouncer if the bouncer is dismissing as a result of a biometric unlock.
- when(mBiometricUnlockController.getMode())
- .thenReturn(BiometricUnlockController.MODE_DISMISS_BOUNCER);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(
- expansionEvent(
- /* fraction= */ EXPANSION_VISIBLE,
- /* expanded= */ true,
- /* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
- }
-
- @Test
- public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
- when(mKeyguardStateController.isOccluded()).thenReturn(true);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(
- expansionEvent(
- /* fraction= */ EXPANSION_VISIBLE,
- /* expanded= */ true,
- /* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
- }
-
- @Test
- public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
- // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
- // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
- // which would mistakenly cause the bouncer to show briefly before its visibility
- // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
- // bouncer if the bouncer is dismissing as a result of a biometric unlock.
- when(mBiometricUnlockController.getMode())
- .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(
- expansionEvent(
- /* fraction= */ EXPANSION_VISIBLE,
- /* expanded= */ true,
- /* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
- }
-
- @Test
- public void onPanelExpansionChanged_neverTranslatesBouncerWhenShadeLocked() {
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(
- expansionEvent(
- /* fraction= */ EXPANSION_VISIBLE,
- /* expanded= */ true,
- /* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
- }
-
- @Test
- public void setOccluded_animatesPanelExpansion_onlyIfBouncerHidden() {
- mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
- verify(mCentralSurfaces).animateKeyguardUnoccluding();
-
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
- clearInvocations(mCentralSurfaces);
- mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
- verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
- }
-
- @Test
- public void setOccluded_onKeyguardOccludedChangedCalled() {
- clearInvocations(mKeyguardStateController);
- clearInvocations(mKeyguardUpdateMonitor);
-
- mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
- verify(mKeyguardStateController).notifyKeyguardState(true, false);
-
- clearInvocations(mKeyguardUpdateMonitor);
- clearInvocations(mKeyguardStateController);
-
- mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
- verify(mKeyguardStateController).notifyKeyguardState(true, true);
-
- clearInvocations(mKeyguardUpdateMonitor);
- clearInvocations(mKeyguardStateController);
-
- mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
- verify(mKeyguardStateController).notifyKeyguardState(true, false);
- }
-
- @Test
- public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
- mStatusBarKeyguardViewManager.show(null);
-
- mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
- verify(mKeyguardStateController).notifyKeyguardState(true, true);
- }
-
- @Test
- public void setOccluded_isLaunchingActivityOverLockscreen_onKeyguardOccludedChangedCalled() {
- when(mCentralSurfaces.isLaunchingActivityOverLockscreen()).thenReturn(true);
- mStatusBarKeyguardViewManager.show(null);
-
- mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
- verify(mKeyguardStateController).notifyKeyguardState(true, true);
- }
-
- @Test
- public void testHiding_cancelsGoneRunnable() {
- OnDismissAction action = mock(OnDismissAction.class);
- Runnable cancelAction = mock(Runnable.class);
- mStatusBarKeyguardViewManager.dismissWithAction(
- action, cancelAction, true /* afterKeyguardGone */);
-
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
- mStatusBarKeyguardViewManager.hideBouncer(true);
- mStatusBarKeyguardViewManager.hide(0, 30);
- verify(action, never()).onDismiss();
- verify(cancelAction).run();
- }
-
- @Test
- public void testHidingBouncer_cancelsGoneRunnable() {
- OnDismissAction action = mock(OnDismissAction.class);
- Runnable cancelAction = mock(Runnable.class);
- mStatusBarKeyguardViewManager.dismissWithAction(
- action, cancelAction, true /* afterKeyguardGone */);
-
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
- mStatusBarKeyguardViewManager.hideBouncer(true);
-
- verify(action, never()).onDismiss();
- verify(cancelAction).run();
- }
-
- @Test
- public void testHiding_doesntCancelWhenShowing() {
- OnDismissAction action = mock(OnDismissAction.class);
- Runnable cancelAction = mock(Runnable.class);
- mStatusBarKeyguardViewManager.dismissWithAction(
- action, cancelAction, true /* afterKeyguardGone */);
-
- mStatusBarKeyguardViewManager.hide(0, 30);
- verify(action).onDismiss();
- verify(cancelAction, never()).run();
- }
-
- @Test
- public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
- when(mPrimaryBouncer.inTransit()).thenReturn(true);
-
- assertTrue(
- "Is or will be showing should be true when bouncer is in transit",
- mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
- }
-
- @Test
- public void testUpdateResources_delegatesToBouncer() {
- mStatusBarKeyguardViewManager.updateResources();
-
- verify(mPrimaryBouncer).updateResources();
- }
-
- @Test
- public void updateKeyguardPosition_delegatesToBouncer() {
- mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
-
- verify(mPrimaryBouncer).updateKeyguardPosition(1.0f);
- }
-
- @Test
- public void testIsBouncerInTransit() {
- when(mPrimaryBouncer.inTransit()).thenReturn(true);
- Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
- when(mPrimaryBouncer.inTransit()).thenReturn(false);
- Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
- mPrimaryBouncer = null;
- Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
- }
-
- private static ShadeExpansionChangeEvent expansionEvent(
- float fraction, boolean expanded, boolean tracking) {
- return new ShadeExpansionChangeEvent(
- fraction, expanded, tracking, /* dragDownPxAmount= */ 0f);
- }
-
- @Test
- public void testPredictiveBackCallback_registration() {
- /* verify that a predictive back callback is registered when the bouncer becomes visible */
- mBouncerExpansionCallback.onVisibilityChanged(true);
- verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
- mOnBackInvokedCallback.capture());
-
- /* verify that the same callback is unregistered when the bouncer becomes invisible */
- mBouncerExpansionCallback.onVisibilityChanged(false);
- verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
- eq(mOnBackInvokedCallback.getValue()));
- }
-
- @Test
- public void testPredictiveBackCallback_invocationHidesBouncer() {
- mBouncerExpansionCallback.onVisibilityChanged(true);
- /* capture the predictive back callback during registration */
- verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
- mOnBackInvokedCallback.capture());
-
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
- when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
- /* invoke the back callback directly */
- mOnBackInvokedCallback.getValue().onBackInvoked();
-
- /* verify that the bouncer will be hidden as a result of the invocation */
- verify(mCentralSurfaces).setBouncerShowing(eq(false));
- }
-
- @Test
- public void testReportBouncerOnDreamWhenVisible() {
- mBouncerExpansionCallback.onVisibilityChanged(true);
- verify(mCentralSurfaces).setBouncerShowingOverDream(false);
- Mockito.clearInvocations(mCentralSurfaces);
- when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
- mBouncerExpansionCallback.onVisibilityChanged(true);
- verify(mCentralSurfaces).setBouncerShowingOverDream(true);
- }
-
- @Test
- public void testReportBouncerOnDreamWhenNotVisible() {
- mBouncerExpansionCallback.onVisibilityChanged(false);
- verify(mCentralSurfaces).setBouncerShowingOverDream(false);
- Mockito.clearInvocations(mCentralSurfaces);
- when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
- mBouncerExpansionCallback.onVisibilityChanged(false);
- verify(mCentralSurfaces).setBouncerShowingOverDream(false);
- }
-
- @Test
- public void flag_off_DoesNotCallBouncerInteractor() {
- when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
- mStatusBarKeyguardViewManager.hideBouncer(false);
- verify(mPrimaryBouncerInteractor, never()).hide();
- }
-
- @Test
- public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
- mStatusBarKeyguardViewManager =
- new StatusBarKeyguardViewManager(
- getContext(),
- mViewMediatorCallback,
- mLockPatternUtils,
- mStatusBarStateController,
- mock(ConfigurationController.class),
- mKeyguardUpdateMonitor,
- mDreamOverlayStateController,
- mock(NavigationModeController.class),
- mock(DockManager.class),
- mock(NotificationShadeWindowController.class),
- mKeyguardStateController,
- mock(NotificationMediaManager.class),
- mKeyguardBouncerFactory,
- mKeyguardMessageAreaFactory,
- Optional.of(mSysUiUnfoldComponent),
- () -> mShadeController,
- mLatencyTracker,
- mKeyguardSecurityModel,
- mFeatureFlags,
- mPrimaryBouncerCallbackInteractor,
- mPrimaryBouncerInteractor,
- mBouncerView,
- mAlternateBouncerInteractor) {
- @Override
- public ViewRootImpl getViewRootImpl() {
- return mViewRootImpl;
- }
- };
-
- // the following call before registering centralSurfaces should NOT throw a NPE:
- mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index ae390a0..db8172a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -22,6 +22,7 @@
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.os.ParcelUuid
import android.provider.Settings
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
@@ -50,6 +51,7 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
+import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -104,6 +106,17 @@
mock<TableLogBuffer>()
}
+ // For convenience, set up the subscription info callbacks
+ whenever(subscriptionManager.getActiveSubscriptionInfo(anyInt())).thenAnswer { invocation ->
+ when (invocation.getArgument(0) as Int) {
+ 1 -> SUB_1
+ 2 -> SUB_2
+ 3 -> SUB_3
+ 4 -> SUB_4
+ else -> null
+ }
+ }
+
wifiRepository = FakeWifiRepository()
connectionFactory =
@@ -686,6 +699,38 @@
job.cancel()
}
+ @Test
+ fun `active data change - in same group - emits unit`() =
+ runBlocking(IMMEDIATE) {
+ var latest: Unit? = null
+ val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_4_ID_GROUPED)
+
+ assertThat(latest).isEqualTo(Unit)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `active data change - not in same group - does not emit`() =
+ runBlocking(IMMEDIATE) {
+ var latest: Unit? = null
+ val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_1_ID)
+
+ assertThat(latest).isEqualTo(null)
+
+ job.cancel()
+ }
+
private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)
@@ -719,19 +764,50 @@
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
+
+ // Subscription 1
private const val SUB_1_ID = 1
private val SUB_1 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_1_ID)
+ whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID()))
+ }
private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
+ // Subscription 2
private const val SUB_2_ID = 2
private val SUB_2 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_2_ID)
+ whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID()))
+ }
private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
+ // Subs 3 and 4 are considered to be in the same group ------------------------------------
+ private val GROUP_ID_3_4 = ParcelUuid(UUID.randomUUID())
+
+ // Subscription 3
+ private const val SUB_3_ID_GROUPED = 3
+ private val SUB_3 =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_3_ID_GROUPED)
+ whenever(it.groupUuid).thenReturn(GROUP_ID_3_4)
+ }
+
+ // Subscription 4
+ private const val SUB_4_ID_GROUPED = 4
+ private val SUB_4 =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_4_ID_GROUPED)
+ whenever(it.groupUuid).thenReturn(GROUP_ID_3_4)
+ }
+
+ // Subs 3 and 4 are considered to be in the same group ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
private const val NET_ID = 123
private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
+ // Carrier merged subscription
private const val SUB_CM_ID = 5
private val SUB_CM =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index b32058f..3dccbbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -45,7 +45,7 @@
@Test
fun testLogNetworkCapsChange_bufferHasInfo() {
- logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS)
+ logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS, isDefaultNetworkCallback = true)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -54,6 +54,7 @@
val expectedNetId = NET_1_ID.toString()
val expectedCaps = NET_1_CAPS.toString()
+ assertThat(actualString).contains("true")
assertThat(actualString).contains(expectedNetId)
assertThat(actualString).contains(expectedCaps)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 87ce8fa..7099f1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -21,7 +21,10 @@
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.TransportInfo
+import android.net.VpnTransportInfo
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
@@ -243,6 +246,54 @@
job.cancel()
}
+ /** Regression test for b/266628069. */
+ @Test
+ fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val transportInfo = VpnTransportInfo(
+ /* type= */ 0,
+ /* sessionId= */ "sessionId",
+ )
+ val networkCapabilities = mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(transportInfo)
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
+
+ assertThat(underTest.isWifiDefault.value).isFalse()
+
+ job.cancel()
+ }
+
+ /** Regression test for b/266628069. */
+ @Test
+ fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val transportInfo = VpnTransportInfo(
+ /* type= */ 0,
+ /* sessionId= */ "sessionId",
+ )
+ val networkCapabilities = mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(transportInfo)
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
+
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
+ }
+
@Test
fun isWifiDefault_cellularVcnNetwork_isTrue() = runBlocking(IMMEDIATE) {
val job = underTest.isWifiDefault.launchIn(this)
@@ -260,6 +311,24 @@
}
@Test
+ fun wifiNetwork_cellularAndWifiTransports_usesCellular_isTrue() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val capabilities = mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
fun isWifiDefault_cellularNotVcnNetwork_isFalse() = runBlocking(IMMEDIATE) {
val job = underTest.isWifiDefault.launchIn(this)
@@ -467,6 +536,28 @@
job.cancel()
}
+ /** Regression test for b/266628069. */
+ @Test
+ fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest
+ .wifiNetwork
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val transportInfo = VpnTransportInfo(
+ /* type= */ 0,
+ /* sessionId= */ "sessionId",
+ )
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(transportInfo))
+
+ assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+ job.cancel()
+ }
+
@Test
fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
@@ -535,6 +626,31 @@
}
@Test
+ fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest
+ .wifiNetwork
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val capabilities = mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+ }
+
+ getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ val latestActive = latest as WifiNetworkModel.Active
+ assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+ assertThat(latestActive.ssid).isEqualTo(SSID)
+
+ job.cancel()
+ }
+
+ @Test
fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
val job = underTest
@@ -870,12 +986,12 @@
}
private fun createWifiNetworkCapabilities(
- wifiInfo: WifiInfo,
+ transportInfo: TransportInfo,
isValidated: Boolean = true,
): NetworkCapabilities {
return mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(wifiInfo)
+ whenever(it.transportInfo).thenReturn(transportInfo)
whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(isValidated)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 45f7df3..c7c6b94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -1172,7 +1172,7 @@
inner class Listener : TemporaryViewDisplayController.Listener {
val permanentlyRemovedIds = mutableListOf<String>()
- override fun onInfoPermanentlyRemoved(id: String) {
+ override fun onInfoPermanentlyRemoved(id: String, reason: String) {
permanentlyRemovedIds.add(id)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 45eb1f9..dd04ac4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -66,7 +66,7 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class ChipbarCoordinatorTest : SysuiTestCase() {
- private lateinit var underTest: FakeChipbarCoordinator
+ private lateinit var underTest: ChipbarCoordinator
@Mock private lateinit var logger: ChipbarLogger
@Mock private lateinit var accessibilityManager: AccessibilityManager
@@ -100,7 +100,7 @@
uiEventLoggerFake = UiEventLoggerFake()
underTest =
- FakeChipbarCoordinator(
+ ChipbarCoordinator(
context,
logger,
windowManager,
@@ -436,6 +436,23 @@
verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("new title text"), any())
}
+ /** Regression test for b/266209420. */
+ @Test
+ fun displayViewThenImmediateRemoval_viewStillRemoved() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Error,
+ ),
+ )
+ val chipbarView = getChipbarView()
+
+ underTest.removeView(DEVICE_ID, "test reason")
+
+ verify(windowManager).removeView(chipbarView)
+ }
+
@Test
fun swipeToDismiss_false_neverListensForGesture() {
underTest.displayView(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
deleted file mode 100644
index ffac8f6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.temporarydisplay.chipbar
-
-import android.content.Context
-import android.os.PowerManager
-import android.view.ViewGroup
-import android.view.WindowManager
-import android.view.accessibility.AccessibilityManager
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.time.SystemClock
-import com.android.systemui.util.view.ViewUtil
-import com.android.systemui.util.wakelock.WakeLock
-
-/** A fake implementation of [ChipbarCoordinator] for testing. */
-class FakeChipbarCoordinator(
- context: Context,
- logger: ChipbarLogger,
- windowManager: WindowManager,
- mainExecutor: DelayableExecutor,
- accessibilityManager: AccessibilityManager,
- configurationController: ConfigurationController,
- dumpManager: DumpManager,
- powerManager: PowerManager,
- falsingManager: FalsingManager,
- falsingCollector: FalsingCollector,
- swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler,
- viewUtil: ViewUtil,
- vibratorHelper: VibratorHelper,
- wakeLockBuilder: WakeLock.Builder,
- systemClock: SystemClock,
-) :
- ChipbarCoordinator(
- context,
- logger,
- windowManager,
- mainExecutor,
- accessibilityManager,
- configurationController,
- dumpManager,
- powerManager,
- falsingManager,
- falsingCollector,
- swipeChipbarAwayGestureHandler,
- viewUtil,
- vibratorHelper,
- wakeLockBuilder,
- systemClock,
- ) {
- override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
- // Just bypass the animation in tests
- onAnimationEnd.run()
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
new file mode 100644
index 0000000..5641832
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.keyguard.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository {
+ private val _isLockedOut = MutableStateFlow<Boolean>(false)
+ override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow()
+
+ fun setLockedOut(lockedOut: Boolean) {
+ _isLockedOut.value = lockedOut
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 15b4736..065fe89 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -29,6 +29,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
/** Fake implementation of [KeyguardRepository] */
class FakeKeyguardRepository : KeyguardRepository {
@@ -101,6 +102,13 @@
private val _biometricUnlockSource = MutableStateFlow<BiometricUnlockSource?>(null)
override val biometricUnlockSource: Flow<BiometricUnlockSource?> = _biometricUnlockSource
+ private val _isQuickSettingsVisible = MutableStateFlow(false)
+ override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
+
+ override fun setQuickSettingsVisible(isVisible: Boolean) {
+ _isQuickSettingsVisible.value = isVisible
+ }
+
override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value
}
@@ -169,6 +177,10 @@
_dozeTransitionModel.value = model
}
+ fun setStatusBarState(state: StatusBarState) {
+ _statusBarState.value = state
+ }
+
override fun isUdfpsSupported(): Boolean {
return _isUdfpsSupported.value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 6c44244..eac1bd1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -22,13 +22,15 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import java.util.UUID
+import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
/** Fake implementation of [KeyguardTransitionRepository] */
class FakeKeyguardTransitionRepository : KeyguardTransitionRepository {
- private val _transitions = MutableSharedFlow<TransitionStep>()
+ private val _transitions =
+ MutableSharedFlow<TransitionStep>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val transitions: SharedFlow<TransitionStep> = _transitions
suspend fun sendTransitionStep(step: TransitionStep) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 0dd1fc7..251014f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -67,7 +67,10 @@
_userHandle = UserHandle.of(_userId)
val copy = callbacks.toList()
- copy.forEach { it.onUserChanged(_userId, userContext) }
+ copy.forEach {
+ it.onUserChanging(_userId, userContext)
+ it.onUserChanged(_userId, userContext)
+ }
}
fun onProfileChanged() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 278c98f..0589cfc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -56,6 +56,7 @@
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
@@ -434,6 +435,48 @@
return device;
}
+ private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = {
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+ AudioDeviceInfo.TYPE_WIRED_HEADSET,
+ AudioDeviceInfo.TYPE_USB_HEADSET,
+ AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
+ AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ AudioDeviceInfo.TYPE_USB_DEVICE,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ AudioDeviceInfo.TYPE_LINE_ANALOG,
+ AudioDeviceInfo.TYPE_HDMI,
+ AudioDeviceInfo.TYPE_AUX_LINE
+ };
+
+ /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+ for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
+ if (device.getType() == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /* package */ static List<AudioDeviceInfo> getAvailableCommunicationDevices() {
+ ArrayList<AudioDeviceInfo> commDevices = new ArrayList<>();
+ AudioDeviceInfo[] allDevices =
+ AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo device : allDevices) {
+ if (isValidCommunicationDevice(device)) {
+ commDevices.add(device);
+ }
+ }
+ return commDevices;
+ }
+
+ private @Nullable AudioDeviceInfo getCommunicationDeviceOfType(int type) {
+ return getAvailableCommunicationDevices().stream().filter(d -> d.getType() == type)
+ .findFirst().orElse(null);
+ }
+
/**
* Returns the device currently requested for communication use case.
* @return AudioDeviceInfo the requested device for communication.
@@ -441,7 +484,29 @@
/* package */ AudioDeviceInfo getCommunicationDevice() {
synchronized (mDeviceStateLock) {
updateActiveCommunicationDevice();
- return mActiveCommunicationDevice;
+ AudioDeviceInfo device = mActiveCommunicationDevice;
+ // make sure we return a valid communication device (i.e. a device that is allowed by
+ // setCommunicationDevice()) for consistency.
+ if (device != null) {
+ // a digital dock is used instead of the speaker in speakerphone mode and should
+ // be reflected as such
+ if (device.getType() == AudioDeviceInfo.TYPE_DOCK) {
+ device = getCommunicationDeviceOfType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+ }
+ }
+ // Try to default to earpiece when current communication device is not valid. This can
+ // happen for instance if no call is active. If no earpiece device is available take the
+ // first valid communication device
+ if (device == null || !AudioDeviceBroker.isValidCommunicationDevice(device)) {
+ device = getCommunicationDeviceOfType(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+ if (device == null) {
+ List<AudioDeviceInfo> commDevices = getAvailableCommunicationDevices();
+ if (!commDevices.isEmpty()) {
+ device = commDevices.get(0);
+ }
+ }
+ }
+ return device;
}
}
@@ -918,8 +983,8 @@
@GuardedBy("mDeviceStateLock")
private void dispatchCommunicationDevice() {
- int portId = (mActiveCommunicationDevice == null) ? 0
- : mActiveCommunicationDevice.getId();
+ AudioDeviceInfo device = getCommunicationDevice();
+ int portId = device != null ? device.getId() : 0;
if (portId == mCurCommunicationPortId) {
return;
}
@@ -936,6 +1001,7 @@
mCommDevDispatchers.finishBroadcast();
}
+
//---------------------------------------------------------------------
// Communication with (to) AudioService
//TODO check whether the AudioService methods are candidates to move here
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1bd8f1e..d6ecbc3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5846,46 +5846,16 @@
restoreDeviceVolumeBehavior();
}
- private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = {
- AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
- AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
- AudioDeviceInfo.TYPE_WIRED_HEADSET,
- AudioDeviceInfo.TYPE_USB_HEADSET,
- AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
- AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
- AudioDeviceInfo.TYPE_HEARING_AID,
- AudioDeviceInfo.TYPE_BLE_HEADSET,
- AudioDeviceInfo.TYPE_USB_DEVICE,
- AudioDeviceInfo.TYPE_BLE_SPEAKER,
- AudioDeviceInfo.TYPE_LINE_ANALOG,
- AudioDeviceInfo.TYPE_HDMI,
- AudioDeviceInfo.TYPE_AUX_LINE
- };
-
- private boolean isValidCommunicationDevice(AudioDeviceInfo device) {
- for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
- if (device.getType() == type) {
- return true;
- }
- }
- return false;
- }
-
/** @see AudioManager#getAvailableCommunicationDevices(int) */
public int[] getAvailableCommunicationDeviceIds() {
- ArrayList<Integer> deviceIds = new ArrayList<>();
- AudioDeviceInfo[] devices = AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS);
- for (AudioDeviceInfo device : devices) {
- if (isValidCommunicationDevice(device)) {
- deviceIds.add(device.getId());
- }
- }
- return deviceIds.stream().mapToInt(Integer::intValue).toArray();
+ List<AudioDeviceInfo> commDevices = AudioDeviceBroker.getAvailableCommunicationDevices();
+ return commDevices.stream().mapToInt(AudioDeviceInfo::getId).toArray();
}
- /**
- * @see AudioManager#setCommunicationDevice(int)
- * @see AudioManager#clearCommunicationDevice()
- */
+
+ /**
+ * @see AudioManager#setCommunicationDevice(int)
+ * @see AudioManager#clearCommunicationDevice()
+ */
public boolean setCommunicationDevice(IBinder cb, int portId) {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -5894,9 +5864,10 @@
if (portId != 0) {
device = AudioManager.getDeviceForPortId(portId, AudioManager.GET_DEVICES_OUTPUTS);
if (device == null) {
- throw new IllegalArgumentException("invalid portID " + portId);
+ Log.w(TAG, "setCommunicationDevice: invalid portID " + portId);
+ return false;
}
- if (!isValidCommunicationDevice(device)) {
+ if (!AudioDeviceBroker.isValidCommunicationDevice(device)) {
throw new IllegalArgumentException("invalid device type " + device.getType());
}
}
@@ -5939,13 +5910,15 @@
/** @see AudioManager#getCommunicationDevice() */
public int getCommunicationDevice() {
+ int deviceId = 0;
final long ident = Binder.clearCallingIdentity();
- AudioDeviceInfo device = mDeviceBroker.getCommunicationDevice();
- Binder.restoreCallingIdentity(ident);
- if (device == null) {
- return 0;
+ try {
+ AudioDeviceInfo device = mDeviceBroker.getCommunicationDevice();
+ deviceId = device != null ? device.getId() : 0;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- return device.getId();
+ return deviceId;
}
/** @see AudioManager#addOnCommunicationDeviceChangedListener(
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 4d55d4e..39acaee 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -150,8 +150,9 @@
= new ArraySet<>();
// Just the packages from mEnabledServicesForCurrentProfiles
private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>();
- // List of enabled packages that have nevertheless asked not to be run
- private ArraySet<ComponentName> mSnoozingForCurrentProfiles = new ArraySet<>();
+ // Per user id, list of enabled packages that have nevertheless asked not to be run
+ private final android.util.SparseSetArray<ComponentName> mSnoozing =
+ new android.util.SparseSetArray<>();
// List of approved packages or components (by user, then by primary/secondary) that are
// allowed to be bound as managed services. A package or component appearing in this list does
@@ -386,10 +387,15 @@
}
}
- pw.println(" Snoozed " + getCaption() + "s (" +
- mSnoozingForCurrentProfiles.size() + "):");
- for (ComponentName name : mSnoozingForCurrentProfiles) {
- pw.println(" " + name.flattenToShortString());
+ synchronized (mSnoozing) {
+ pw.println(" Snoozed " + getCaption() + "s ("
+ + mSnoozing.size() + "):");
+ for (int i = 0; i < mSnoozing.size(); i++) {
+ pw.println(" User: " + mSnoozing.keyAt(i));
+ for (ComponentName name : mSnoozing.valuesAt(i)) {
+ pw.println(" " + name.flattenToShortString());
+ }
+ }
}
}
@@ -431,8 +437,16 @@
}
}
- for (ComponentName name : mSnoozingForCurrentProfiles) {
- name.dumpDebug(proto, ManagedServicesProto.SNOOZED);
+ synchronized (mSnoozing) {
+ for (int i = 0; i < mSnoozing.size(); i++) {
+ long token = proto.start(ManagedServicesProto.SNOOZED);
+ proto.write(ManagedServicesProto.SnoozedServices.USER_ID,
+ mSnoozing.keyAt(i));
+ for (ComponentName name : mSnoozing.valuesAt(i)) {
+ name.dumpDebug(proto, ManagedServicesProto.SnoozedServices.SNOOZED);
+ }
+ proto.end(token);
+ }
}
}
@@ -975,6 +989,9 @@
synchronized (mApproved) {
mApproved.remove(user);
}
+ synchronized (mSnoozing) {
+ mSnoozing.remove(user);
+ }
rebindServices(true, user);
}
@@ -1066,15 +1083,17 @@
}
protected void setComponentState(ComponentName component, int userId, boolean enabled) {
- boolean previous = !mSnoozingForCurrentProfiles.contains(component);
- if (previous == enabled) {
- return;
- }
+ synchronized (mSnoozing) {
+ boolean previous = !mSnoozing.contains(userId, component);
+ if (previous == enabled) {
+ return;
+ }
- if (enabled) {
- mSnoozingForCurrentProfiles.remove(component);
- } else {
- mSnoozingForCurrentProfiles.add(component);
+ if (enabled) {
+ mSnoozing.remove(userId, component);
+ } else {
+ mSnoozing.add(userId, component);
+ }
}
// State changed
@@ -1287,7 +1306,10 @@
}
final Set<ComponentName> add = new HashSet<>(userComponents);
- add.removeAll(mSnoozingForCurrentProfiles);
+ ArraySet<ComponentName> snoozed = mSnoozing.get(userId);
+ if (snoozed != null) {
+ add.removeAll(snoozed);
+ }
componentsToBind.put(userId, add);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b380d84..d249547 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -975,12 +975,7 @@
powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
} else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
Slog.d(TAG, "No behavior defined for power press count " + count);
- } else if (count == 1 && interactive) {
- if (beganFromNonInteractive) {
- // The screen off case, where we might want to start dreaming on power button press.
- attemptToDreamFromShortPowerButtonPress(false, () -> {});
- return;
- }
+ } else if (count == 1 && interactive && !beganFromNonInteractive) {
if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) {
Slog.i(TAG, "Suppressing power key because the user is interacting with the "
+ "fingerprint sensor");
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
index a6f8557..2e67399 100644
--- a/services/core/java/com/android/server/wm/DeviceStateController.java
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -16,80 +16,92 @@
package com.android.server.wm;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
import android.os.HandlerExecutor;
+import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
/**
- * Class that registers callbacks with the {@link DeviceStateManager} and
- * responds to fold state changes by forwarding such events to a delegate.
+ * Class that registers callbacks with the {@link DeviceStateManager} and responds to device
+ * changes.
*/
-final class DeviceStateController {
+final class DeviceStateController implements DeviceStateManager.DeviceStateCallback {
+
+ @NonNull
private final DeviceStateManager mDeviceStateManager;
- private final Context mContext;
+ @NonNull
+ private final int[] mOpenDeviceStates;
+ @NonNull
+ private final int[] mHalfFoldedDeviceStates;
+ @NonNull
+ private final int[] mFoldedDeviceStates;
+ @NonNull
+ private final int[] mRearDisplayDeviceStates;
+ @NonNull
+ private final List<Consumer<DeviceState>> mDeviceStateCallbacks = new ArrayList<>();
- private FoldStateListener mDeviceStateListener;
+ @Nullable
+ private DeviceState mLastDeviceState;
- public enum FoldState {
- UNKNOWN, OPEN, FOLDED, HALF_FOLDED
+ public enum DeviceState {
+ UNKNOWN, OPEN, FOLDED, HALF_FOLDED, REAR,
}
- DeviceStateController(Context context, Handler handler, Consumer<FoldState> delegate) {
- mContext = context;
- mDeviceStateManager = mContext.getSystemService(DeviceStateManager.class);
+ DeviceStateController(@NonNull Context context, @NonNull Handler handler) {
+ mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
+ mOpenDeviceStates = context.getResources()
+ .getIntArray(R.array.config_openDeviceStates);
+ mHalfFoldedDeviceStates = context.getResources()
+ .getIntArray(R.array.config_halfFoldedDeviceStates);
+ mFoldedDeviceStates = context.getResources()
+ .getIntArray(R.array.config_foldedDeviceStates);
+ mRearDisplayDeviceStates = context.getResources()
+ .getIntArray(R.array.config_rearDisplayDeviceStates);
+
if (mDeviceStateManager != null) {
- mDeviceStateListener = new FoldStateListener(mContext, delegate);
- mDeviceStateManager
- .registerCallback(new HandlerExecutor(handler),
- mDeviceStateListener);
+ mDeviceStateManager.registerCallback(new HandlerExecutor(handler), this);
}
}
void unregisterFromDeviceStateManager() {
- if (mDeviceStateListener != null) {
- mDeviceStateManager.unregisterCallback(mDeviceStateListener);
+ if (mDeviceStateManager != null) {
+ mDeviceStateManager.unregisterCallback(this);
}
}
- /**
- * A listener for half-fold device state events that dispatches state changes to a delegate.
- */
- static final class FoldStateListener implements DeviceStateManager.DeviceStateCallback {
+ void registerDeviceStateCallback(@NonNull Consumer<DeviceState> callback) {
+ mDeviceStateCallbacks.add(callback);
+ }
- private final int[] mHalfFoldedDeviceStates;
- private final int[] mFoldedDeviceStates;
-
- @Nullable
- private FoldState mLastResult;
- private final Consumer<FoldState> mDelegate;
-
- FoldStateListener(Context context, Consumer<FoldState> delegate) {
- mFoldedDeviceStates = context.getResources().getIntArray(
- com.android.internal.R.array.config_foldedDeviceStates);
- mHalfFoldedDeviceStates = context.getResources().getIntArray(
- com.android.internal.R.array.config_halfFoldedDeviceStates);
- mDelegate = delegate;
+ @Override
+ public void onStateChanged(int state) {
+ final DeviceState deviceState;
+ if (ArrayUtils.contains(mHalfFoldedDeviceStates, state)) {
+ deviceState = DeviceState.HALF_FOLDED;
+ } else if (ArrayUtils.contains(mFoldedDeviceStates, state)) {
+ deviceState = DeviceState.FOLDED;
+ } else if (ArrayUtils.contains(mRearDisplayDeviceStates, state)) {
+ deviceState = DeviceState.REAR;
+ } else if (ArrayUtils.contains(mOpenDeviceStates, state)) {
+ deviceState = DeviceState.OPEN;
+ } else {
+ deviceState = DeviceState.UNKNOWN;
}
- @Override
- public void onStateChanged(int state) {
- final boolean halfFolded = ArrayUtils.contains(mHalfFoldedDeviceStates, state);
- FoldState result;
- if (halfFolded) {
- result = FoldState.HALF_FOLDED;
- } else {
- final boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
- result = folded ? FoldState.FOLDED : FoldState.OPEN;
- }
- if (mLastResult == null || !mLastResult.equals(result)) {
- mLastResult = result;
- mDelegate.accept(result);
+ if (mLastDeviceState == null || !mLastDeviceState.equals(deviceState)) {
+ mLastDeviceState = deviceState;
+
+ for (Consumer<DeviceState> callback : mDeviceStateCallbacks) {
+ callback.accept(mLastDeviceState);
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9af1b2b..740fd584 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1125,14 +1125,17 @@
mWmService.mAtmService.getRecentTasks().getInputListener());
}
+ mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH);
+
mDisplayPolicy = new DisplayPolicy(mWmService, this);
mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address);
- mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH,
- newFoldState -> {
+ final Consumer<DeviceStateController.DeviceState> deviceStateConsumer =
+ (@NonNull DeviceStateController.DeviceState newFoldState) -> {
mDisplaySwitchTransitionLauncher.foldStateChanged(newFoldState);
mDisplayRotation.foldStateChanged(newFoldState);
- });
+ };
+ mDeviceStateController.registerDeviceStateCallback(deviceStateConsumer);
mCloseToSquareMaxAspectRatio = mWmService.mContext.getResources().getFloat(
R.dimen.config_closeToSquareDisplayMaxAspectRatio);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index e6d8b3d..a1e18cf 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1573,7 +1573,7 @@
proto.end(token);
}
- boolean isDeviceInPosture(DeviceStateController.FoldState state, boolean isTabletop) {
+ boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) {
if (mFoldController == null) return false;
return mFoldController.isDeviceInPosture(state, isTabletop);
}
@@ -1585,10 +1585,10 @@
/**
* Called by the DeviceStateManager callback when the device state changes.
*/
- void foldStateChanged(DeviceStateController.FoldState foldState) {
+ void foldStateChanged(DeviceStateController.DeviceState deviceState) {
if (mFoldController != null) {
synchronized (mLock) {
- mFoldController.foldStateChanged(foldState);
+ mFoldController.foldStateChanged(deviceState);
}
}
}
@@ -1596,8 +1596,8 @@
private class FoldController {
@Surface.Rotation
private int mHalfFoldSavedRotation = -1; // No saved rotation
- private DeviceStateController.FoldState mFoldState =
- DeviceStateController.FoldState.UNKNOWN;
+ private DeviceStateController.DeviceState mDeviceState =
+ DeviceStateController.DeviceState.UNKNOWN;
private boolean mInHalfFoldTransition = false;
private final boolean mIsDisplayAlwaysSeparatingHinge;
private final Set<Integer> mTabletopRotations;
@@ -1637,32 +1637,33 @@
R.bool.config_isDisplayHingeAlwaysSeparating);
}
- boolean isDeviceInPosture(DeviceStateController.FoldState state, boolean isTabletop) {
- if (state != mFoldState) {
+ boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) {
+ if (state != mDeviceState) {
return false;
}
- if (mFoldState == DeviceStateController.FoldState.HALF_FOLDED) {
+ if (mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) {
return !(isTabletop ^ mTabletopRotations.contains(mRotation));
}
return true;
}
- DeviceStateController.FoldState getFoldState() {
- return mFoldState;
+ DeviceStateController.DeviceState getFoldState() {
+ return mDeviceState;
}
boolean isSeparatingHinge() {
- return mFoldState == DeviceStateController.FoldState.HALF_FOLDED
- || (mFoldState == DeviceStateController.FoldState.OPEN
+ return mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED
+ || (mDeviceState == DeviceStateController.DeviceState.OPEN
&& mIsDisplayAlwaysSeparatingHinge);
}
boolean overrideFrozenRotation() {
- return mFoldState == DeviceStateController.FoldState.HALF_FOLDED;
+ return mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED;
}
boolean shouldRevertOverriddenRotation() {
- return mFoldState == DeviceStateController.FoldState.OPEN // When transitioning to open.
+ // When transitioning to open.
+ return mDeviceState == DeviceStateController.DeviceState.OPEN
&& mInHalfFoldTransition
&& mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
&& mUserRotationMode
@@ -1676,30 +1677,30 @@
return savedRotation;
}
- void foldStateChanged(DeviceStateController.FoldState newState) {
+ void foldStateChanged(DeviceStateController.DeviceState newState) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"foldStateChanged: displayId %d, halfFoldStateChanged %s, "
+ "saved rotation: %d, mUserRotation: %d, mLastSensorRotation: %d, "
+ "mLastOrientation: %d, mRotation: %d",
mDisplayContent.getDisplayId(), newState.name(), mHalfFoldSavedRotation,
mUserRotation, mLastSensorRotation, mLastOrientation, mRotation);
- if (mFoldState == DeviceStateController.FoldState.UNKNOWN) {
- mFoldState = newState;
+ if (mDeviceState == DeviceStateController.DeviceState.UNKNOWN) {
+ mDeviceState = newState;
return;
}
- if (newState == DeviceStateController.FoldState.HALF_FOLDED
- && mFoldState != DeviceStateController.FoldState.HALF_FOLDED) {
+ if (newState == DeviceStateController.DeviceState.HALF_FOLDED
+ && mDeviceState != DeviceStateController.DeviceState.HALF_FOLDED) {
// The device has transitioned to HALF_FOLDED state: save the current rotation and
// update the device rotation.
mHalfFoldSavedRotation = mRotation;
- mFoldState = newState;
+ mDeviceState = newState;
// Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will
// return true, so rotation is unlocked.
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
} else {
mInHalfFoldTransition = true;
- mFoldState = newState;
+ mDeviceState = newState;
// Tell the device to update its orientation.
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
@@ -1822,7 +1823,7 @@
final long mTimestamp = System.currentTimeMillis();
final int mHalfFoldSavedRotation;
final boolean mInHalfFoldTransition;
- final DeviceStateController.FoldState mFoldState;
+ final DeviceStateController.DeviceState mDeviceState;
@Nullable final String mDisplayRotationCompatPolicySummary;
Record(DisplayRotation dr, int fromRotation, int toRotation) {
@@ -1852,11 +1853,11 @@
if (dr.mFoldController != null) {
mHalfFoldSavedRotation = dr.mFoldController.mHalfFoldSavedRotation;
mInHalfFoldTransition = dr.mFoldController.mInHalfFoldTransition;
- mFoldState = dr.mFoldController.mFoldState;
+ mDeviceState = dr.mFoldController.mDeviceState;
} else {
mHalfFoldSavedRotation = NO_FOLD_CONTROLLER;
mInHalfFoldTransition = false;
- mFoldState = DeviceStateController.FoldState.UNKNOWN;
+ mDeviceState = DeviceStateController.DeviceState.UNKNOWN;
}
mDisplayRotationCompatPolicySummary = dc.mDisplayRotationCompatPolicy == null
? null
@@ -1882,7 +1883,7 @@
pw.println(prefix + " halfFoldSavedRotation="
+ mHalfFoldSavedRotation
+ " mInHalfFoldTransition=" + mInHalfFoldTransition
- + " mFoldState=" + mFoldState);
+ + " mFoldState=" + mDeviceState);
}
if (mDisplayRotationCompatPolicySummary != null) {
pw.println(prefix + mDisplayRotationCompatPolicySummary);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index d2f62da..b67b745 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -105,6 +105,12 @@
private final ActivityRecord mActivityRecord;
+ /**
+ * Taskbar expanded height. Used to determine when to crop an app window to display the
+ * rounded corners above the expanded taskbar.
+ */
+ private final float mExpandedTaskBarHeight;
+
/*
* WindowContainerListener responsible to make translucent activities inherit
* constraints from the first opaque activity beneath them. It's null for not
@@ -184,6 +190,9 @@
() -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
/* checkDeviceConfig */ true),
PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
+
+ mExpandedTaskBarHeight =
+ getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
}
/**
@@ -422,7 +431,7 @@
if (w == null) {
return;
}
- adjustBoundsForTaskbar(w, outBounds);
+ adjustBoundsIfNeeded(w, outBounds);
} else {
outBounds.setEmpty();
}
@@ -465,13 +474,13 @@
if (w == null || winHint != null && w != winHint) {
return;
}
- updateRoundedCorners(w);
+ updateRoundedCornersIfNeeded(w);
// If there is another main window that is not an application-starting window, we should
// update rounded corners for it as well, to avoid flickering rounded corners.
final WindowState nonStartingAppW = mActivityRecord.findMainWindow(
/* includeStartingApp= */ false);
if (nonStartingAppW != null && nonStartingAppW != w) {
- updateRoundedCorners(nonStartingAppW);
+ updateRoundedCornersIfNeeded(nonStartingAppW);
}
updateWallpaperForLetterbox(w);
@@ -533,7 +542,7 @@
// Note that we check the task rather than the parent as with ActivityEmbedding the parent might
// be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
// actually fullscreen.
- private boolean isDisplayFullScreenAndInPosture(DeviceStateController.FoldState state,
+ private boolean isDisplayFullScreenAndInPosture(DeviceStateController.DeviceState state,
boolean isTabletop) {
Task task = mActivityRecord.getTask();
return mActivityRecord.mDisplayContent != null
@@ -559,7 +568,7 @@
// Don't check resolved configuration because it may not be updated yet during
// configuration change.
boolean bookMode = isDisplayFullScreenAndInPosture(
- DeviceStateController.FoldState.HALF_FOLDED, false /* isTabletop */);
+ DeviceStateController.DeviceState.HALF_FOLDED, false /* isTabletop */);
return isHorizontalReachabilityEnabled(parentConfiguration)
// Using the last global dynamic position to avoid "jumps" when moving
// between apps or activities.
@@ -571,7 +580,7 @@
// Don't check resolved configuration because it may not be updated yet during
// configuration change.
boolean tabletopMode = isDisplayFullScreenAndInPosture(
- DeviceStateController.FoldState.HALF_FOLDED, true /* isTabletop */);
+ DeviceStateController.DeviceState.HALF_FOLDED, true /* isTabletop */);
return isVerticalReachabilityEnabled(parentConfiguration)
// Using the last global dynamic position to avoid "jumps" when moving
// between apps or activities.
@@ -775,8 +784,8 @@
return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
- // activity is using blurred wallpaper for letterbox backgroud.
- && (mainWindow.mAttrs.flags & FLAG_SHOW_WALLPAPER) == 0;
+ // activity is using blurred wallpaper for letterbox background.
+ && (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) == 0;
}
@VisibleForTesting
@@ -828,106 +837,107 @@
return mLetterboxConfiguration.getLetterboxBackgroundColor();
}
- private void updateRoundedCorners(WindowState mainWindow) {
+ private void updateRoundedCornersIfNeeded(final WindowState mainWindow) {
final SurfaceControl windowSurface = mainWindow.getSurfaceControl();
- if (windowSurface != null && windowSurface.isValid()) {
- final Transaction transaction = mActivityRecord.getSyncTransaction();
-
- if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
- // We don't want corner radius on the window.
- // In the case the ActivityRecord requires a letterboxed animation we never want
- // rounded corners on the window because rounded corners are applied at the
- // animation-bounds surface level and rounded corners on the window would interfere
- // with that leading to unexpected rounded corner positioning during the animation.
- transaction
- .setWindowCrop(windowSurface, null)
- .setCornerRadius(windowSurface, 0);
- return;
- }
-
- Rect cropBounds = null;
-
- if (hasVisibleTaskbar(mainWindow)) {
- cropBounds = new Rect(mActivityRecord.getBounds());
-
- // Rounded corners should be displayed above the taskbar.
- // It is important to call adjustBoundsForTaskbarUnchecked before offsetTo
- // because taskbar bounds are in screen coordinates
- adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
-
- // Activity bounds are in screen coordinates while (0,0) for activity's surface
- // control is at the top left corner of an app window so offsetting bounds
- // accordingly.
- cropBounds.offsetTo(0, 0);
- }
-
- transaction
- .setWindowCrop(windowSurface, cropBounds)
- .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow));
+ if (windowSurface == null || !windowSurface.isValid()) {
+ return;
}
+
+ // cropBounds must be non-null for the cornerRadius to be ever applied.
+ mActivityRecord.getSyncTransaction()
+ .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow))
+ .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow));
}
- private boolean requiresRoundedCorners(WindowState mainWindow) {
- final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
+ @VisibleForTesting
+ @Nullable
+ Rect getCropBoundsIfNeeded(final WindowState mainWindow) {
+ if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
+ // We don't want corner radius on the window.
+ // In the case the ActivityRecord requires a letterboxed animation we never want
+ // rounded corners on the window because rounded corners are applied at the
+ // animation-bounds surface level and rounded corners on the window would interfere
+ // with that leading to unexpected rounded corner positioning during the animation.
+ return null;
+ }
+ final Rect cropBounds = new Rect(mActivityRecord.getBounds());
+
+ // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo}
+ // because taskbar bounds used in {@link #adjustBoundsIfNeeded}
+ // are in screen coordinates
+ adjustBoundsIfNeeded(mainWindow, cropBounds);
+
+ // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface
+ // control is in the top left corner of an app window so offsetting bounds
+ // accordingly.
+ cropBounds.offsetTo(0, 0);
+ return cropBounds;
+ }
+
+ private boolean requiresRoundedCorners(final WindowState mainWindow) {
return isLetterboxedNotForDisplayCutout(mainWindow)
- && mLetterboxConfiguration.isLetterboxActivityCornersRounded()
- && taskbarInsetsSource != null;
+ && mLetterboxConfiguration.isLetterboxActivityCornersRounded();
}
// Returns rounded corners radius the letterboxed activity should have based on override in
// R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii.
- // Device corners can be different on the right and left sides but we use the same radius
+ // Device corners can be different on the right and left sides, but we use the same radius
// for all corners for consistency and pick a minimal bottom one for consistency with a
// taskbar rounded corners.
- int getRoundedCornersRadius(WindowState mainWindow) {
- if (!requiresRoundedCorners(mainWindow)) {
+ int getRoundedCornersRadius(final WindowState mainWindow) {
+ if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
return 0;
}
+ final int radius;
if (mLetterboxConfiguration.getLetterboxActivityCornersRadius() >= 0) {
- return mLetterboxConfiguration.getLetterboxActivityCornersRadius();
+ radius = mLetterboxConfiguration.getLetterboxActivityCornersRadius();
+ } else {
+ final InsetsState insetsState = mainWindow.getInsetsState();
+ radius = Math.min(
+ getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
+ getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
}
- final InsetsState insetsState = mainWindow.getInsetsState();
- return Math.min(
- getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
- getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
+ final float scale = mainWindow.mInvGlobalScale;
+ return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius;
}
/**
- * Returns whether the taskbar is visible. Returns false if the window is in immersive mode,
- * since the user can swipe to show/hide the taskbar as an overlay.
+ * Returns the taskbar in case it is visible and expanded in height, otherwise returns null.
*/
- private boolean hasVisibleTaskbar(WindowState mainWindow) {
- final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
-
- return taskbarInsetsSource != null
- && taskbarInsetsSource.isVisible();
+ @VisibleForTesting
+ @Nullable
+ InsetsSource getExpandedTaskbarOrNull(final WindowState mainWindow) {
+ final InsetsSource taskbar = mainWindow.getInsetsState().peekSource(
+ InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ if (taskbar != null && taskbar.isVisible()
+ && taskbar.getFrame().height() >= mExpandedTaskBarHeight) {
+ return taskbar;
+ }
+ return null;
}
- private InsetsSource getTaskbarInsetsSource(WindowState mainWindow) {
- final InsetsState insetsState = mainWindow.getInsetsState();
- return insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
- }
-
- private void adjustBoundsForTaskbar(WindowState mainWindow, Rect bounds) {
+ private void adjustBoundsIfNeeded(final WindowState mainWindow, final Rect bounds) {
// Rounded corners should be displayed above the taskbar. When taskbar is hidden,
// an insets frame is equal to a navigation bar which shouldn't affect position of
// rounded corners since apps are expected to handle navigation bar inset.
// This condition checks whether the taskbar is visible.
// Do not crop the taskbar inset if the window is in immersive mode - the user can
// swipe to show/hide the taskbar as an overlay.
- if (hasVisibleTaskbar(mainWindow)) {
- adjustBoundsForTaskbarUnchecked(mainWindow, bounds);
+ // Adjust the bounds only in case there is an expanded taskbar,
+ // otherwise the rounded corners will be shown behind the navbar.
+ final InsetsSource expandedTaskbarOrNull = getExpandedTaskbarOrNull(mainWindow);
+ if (expandedTaskbarOrNull != null) {
+ // Rounded corners should be displayed above the expanded taskbar.
+ bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top);
}
- }
- private void adjustBoundsForTaskbarUnchecked(WindowState mainWindow, Rect bounds) {
- // Rounded corners should be displayed above the taskbar.
- bounds.bottom =
- Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
- scaleIfNeeded(bounds);
+ final float scale = mainWindow.mInvGlobalScale;
+ if (scale != 1f && scale > 0f) {
+ bounds.scale(scale);
+ }
}
private int getInsetsStateCornerRadius(
@@ -1097,7 +1107,7 @@
int letterboxPositionForHorizontalReachability = getLetterboxConfiguration()
.getLetterboxPositionForHorizontalReachability(
isDisplayFullScreenAndInPosture(
- DeviceStateController.FoldState.HALF_FOLDED,
+ DeviceStateController.DeviceState.HALF_FOLDED,
false /* isTabletop */));
positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition(
letterboxPositionForHorizontalReachability);
@@ -1105,7 +1115,7 @@
int letterboxPositionForVerticalReachability = getLetterboxConfiguration()
.getLetterboxPositionForVerticalReachability(
isDisplayFullScreenAndInPosture(
- DeviceStateController.FoldState.HALF_FOLDED,
+ DeviceStateController.DeviceState.HALF_FOLDED,
true /* isTabletop */));
positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition(
letterboxPositionForVerticalReachability);
@@ -1269,20 +1279,4 @@
mInheritedSizeCompatScale = 1f;
mInheritedCompatDisplayInsets = null;
}
-
- private void scaleIfNeeded(Rect bounds) {
- if (boundsNeedToScale()) {
- bounds.scale(1.0f / mActivityRecord.getCompatScale());
- }
- }
-
- private boolean boundsNeedToScale() {
- if (hasInheritedLetterboxBehavior()) {
- return mIsInheritedInSizeCompatMode
- && mInheritedSizeCompatScale < 1.0f;
- } else {
- return mActivityRecord.inSizeCompatMode()
- && mActivityRecord.getCompatScale() < 1.0f;
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index 30bdc34..2edb082 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -51,10 +51,10 @@
/**
* Called by the DeviceStateManager callback when the state changes.
*/
- void foldStateChanged(DeviceStateController.FoldState newFoldState) {
+ void foldStateChanged(DeviceStateController.DeviceState newDeviceState) {
// Ignore transitions to/from half-folded.
- if (newFoldState == DeviceStateController.FoldState.HALF_FOLDED) return;
- mIsFolded = newFoldState == DeviceStateController.FoldState.FOLDED;
+ if (newDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) return;
+ mIsFolded = newDeviceState == DeviceStateController.DeviceState.FOLDED;
}
/**
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 7986043..8b1384e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1582,6 +1582,55 @@
}
@Test
+ public void testSetComponentState_differentUsers() throws Exception {
+ Context context = mock(Context.class);
+ PackageManager pm = mock(PackageManager.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+ when(context.getPackageName()).thenReturn(mContext.getPackageName());
+ when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageManager()).thenReturn(pm);
+ when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+ addExpectedServices(service, Arrays.asList("a"), mZero.id);
+ addExpectedServices(service, Arrays.asList("a"), mTen.id);
+ when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ ServiceConnection sc = (ServiceConnection) args[1];
+ sc.onServiceConnected(cn, mock(IBinder.class));
+ return true;
+ });
+ service.addApprovedList("a/a", 0, true);
+ service.addApprovedList("a/a", 10, false);
+
+ service.registerService(cn, mZero.id);
+ assertTrue(service.isBound(cn, mZero.id));
+
+ service.onUserSwitched(mTen.id);
+ assertFalse(service.isBound(cn, mZero.id));
+ service.registerService(cn, mTen.id);
+ assertTrue(service.isBound(cn, mTen.id));
+
+ service.setComponentState(cn, mTen.id, false);
+ assertFalse(service.isBound(cn, mZero.id));
+ assertFalse(service.isBound(cn, mTen.id));
+
+ // Service should be rebound on user 0, since it was only disabled for user 10.
+ service.onUserSwitched(mZero.id);
+ assertTrue(service.isBound(cn, mZero.id));
+ assertFalse(service.isBound(cn, mTen.id));
+
+ // Service should stay unbound on going back to user 10.
+ service.onUserSwitched(mTen.id);
+ assertFalse(service.isBound(cn, mZero.id));
+ assertFalse(service.isBound(cn, mTen.id));
+ }
+ @Test
public void testOnPackagesChanged_nullValuesPassed_noNullPointers() {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1847,7 +1896,7 @@
}
private void addExpectedServices(final ManagedServices service, final List<String> packages,
- int userId) {
+ int userId) throws Exception {
ManagedServices.Config config = service.getConfig();
when(mPm.queryIntentServicesAsUser(any(), anyInt(), eq(userId))).
thenAnswer(new Answer<List<ResolveInfo>>() {
@@ -1876,6 +1925,20 @@
return new ArrayList<>();
}
});
+
+ when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
+ (Answer<ServiceInfo>) invocation -> {
+ ComponentName invocationCn = invocation.getArgument(0);
+ if (invocationCn != null && packages.contains(invocationCn.getPackageName())) {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationCn.getPackageName();
+ serviceInfo.name = invocationCn.getClassName();
+ serviceInfo.permission = service.getConfig().bindPermission;
+ return serviceInfo;
+ }
+ return null;
+ }
+ );
}
private List<String> stringToList(String list) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
index 86732c9..2a28ae2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
@@ -16,13 +16,12 @@
package com.android.server.wm;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import android.content.Context;
import android.content.res.Resources;
@@ -32,9 +31,10 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
+
import org.junit.Before;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
import java.util.function.Consumer;
@@ -48,92 +48,76 @@
@Presubmit
public class DeviceStateControllerTests {
- private DeviceStateController.FoldStateListener mFoldStateListener;
private DeviceStateController mTarget;
private DeviceStateControllerBuilder mBuilder;
private Context mMockContext;
- private Handler mMockHandler;
- private Resources mMockRes;
private DeviceStateManager mMockDeviceStateManager;
-
- private Consumer<DeviceStateController.FoldState> mDelegate;
- private DeviceStateController.FoldState mCurrentState = DeviceStateController.FoldState.UNKNOWN;
+ private DeviceStateController.DeviceState mCurrentState =
+ DeviceStateController.DeviceState.UNKNOWN;
@Before
public void setUp() {
mBuilder = new DeviceStateControllerBuilder();
- mCurrentState = DeviceStateController.FoldState.UNKNOWN;
+ mCurrentState = DeviceStateController.DeviceState.UNKNOWN;
}
- private void initialize(boolean supportFold, boolean supportHalfFold) throws Exception {
+ private void initialize(boolean supportFold, boolean supportHalfFold) {
mBuilder.setSupportFold(supportFold, supportHalfFold);
- mDelegate = (newFoldState) -> {
+ Consumer<DeviceStateController.DeviceState> delegate = (newFoldState) -> {
mCurrentState = newFoldState;
};
- mBuilder.setDelegate(mDelegate);
+ mBuilder.setDelegate(delegate);
mBuilder.build();
- verifyFoldStateListenerRegistration(1);
+ verify(mMockDeviceStateManager).registerCallback(any(), any());
}
@Test
- public void testInitialization() throws Exception {
+ public void testInitialization() {
initialize(true /* supportFold */, true /* supportHalfFolded */);
- mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
- assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
+ mTarget.onStateChanged(mOpenDeviceStates[0]);
+ assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
}
@Test
- public void testInitializationWithNoFoldSupport() throws Exception {
+ public void testInitializationWithNoFoldSupport() {
initialize(false /* supportFold */, false /* supportHalfFolded */);
- mFoldStateListener.onStateChanged(mFoldedStates[0]);
+ mTarget.onStateChanged(mFoldedStates[0]);
// Note that the folded state is ignored.
- assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
+ assertEquals(DeviceStateController.DeviceState.UNKNOWN, mCurrentState);
}
@Test
- public void testWithFoldSupported() throws Exception {
+ public void testWithFoldSupported() {
initialize(true /* supportFold */, false /* supportHalfFolded */);
- mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
- assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
- mFoldStateListener.onStateChanged(mFoldedStates[0]);
- assertEquals(mCurrentState, DeviceStateController.FoldState.FOLDED);
- mFoldStateListener.onStateChanged(mHalfFoldedStates[0]);
- assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN); // Ignored
+ mTarget.onStateChanged(mOpenDeviceStates[0]);
+ assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
+ mTarget.onStateChanged(mFoldedStates[0]);
+ assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState);
+ mTarget.onStateChanged(mHalfFoldedStates[0]);
+ assertEquals(DeviceStateController.DeviceState.UNKNOWN, mCurrentState); // Ignored
}
@Test
- public void testWithHalfFoldSupported() throws Exception {
+ public void testWithHalfFoldSupported() {
initialize(true /* supportFold */, true /* supportHalfFolded */);
- mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
- assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
- mFoldStateListener.onStateChanged(mFoldedStates[0]);
- assertEquals(mCurrentState, DeviceStateController.FoldState.FOLDED);
- mFoldStateListener.onStateChanged(mHalfFoldedStates[0]);
- assertEquals(mCurrentState, DeviceStateController.FoldState.HALF_FOLDED);
+ mTarget.onStateChanged(mOpenDeviceStates[0]);
+ assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
+ mTarget.onStateChanged(mFoldedStates[0]);
+ assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState);
+ mTarget.onStateChanged(mHalfFoldedStates[0]);
+ assertEquals(DeviceStateController.DeviceState.HALF_FOLDED, mCurrentState);
}
-
private final int[] mFoldedStates = {0};
- private final int[] mUnfoldedStates = {1};
+ private final int[] mOpenDeviceStates = {1};
private final int[] mHalfFoldedStates = {2};
-
-
- private void verifyFoldStateListenerRegistration(int numOfInvocation) {
- final ArgumentCaptor<DeviceStateController.FoldStateListener> listenerCaptor =
- ArgumentCaptor.forClass(DeviceStateController.FoldStateListener.class);
- verify(mMockDeviceStateManager, times(numOfInvocation)).registerCallback(
- any(),
- listenerCaptor.capture());
- if (numOfInvocation > 0) {
- mFoldStateListener = listenerCaptor.getValue();
- }
- }
+ private final int[] mRearDisplayStates = {3};
private class DeviceStateControllerBuilder {
private boolean mSupportFold = false;
private boolean mSupportHalfFold = false;
- private Consumer<DeviceStateController.FoldState> mDelegate;
+ private Consumer<DeviceStateController.DeviceState> mDelegate;
DeviceStateControllerBuilder setSupportFold(
boolean supportFold, boolean supportHalfFold) {
@@ -143,34 +127,44 @@
}
DeviceStateControllerBuilder setDelegate(
- Consumer<DeviceStateController.FoldState> delegate) {
+ Consumer<DeviceStateController.DeviceState> delegate) {
mDelegate = delegate;
return this;
}
private void mockFold(boolean enableFold, boolean enableHalfFold) {
+ if (enableFold || enableHalfFold) {
+ when(mMockContext.getResources()
+ .getIntArray(R.array.config_openDeviceStates))
+ .thenReturn(mOpenDeviceStates);
+ when(mMockContext.getResources()
+ .getIntArray(R.array.config_rearDisplayDeviceStates))
+ .thenReturn(mRearDisplayStates);
+ }
+
if (enableFold) {
- when(mMockContext.getResources().getIntArray(
- com.android.internal.R.array.config_foldedDeviceStates))
+ when(mMockContext.getResources()
+ .getIntArray(R.array.config_foldedDeviceStates))
.thenReturn(mFoldedStates);
}
if (enableHalfFold) {
- when(mMockContext.getResources().getIntArray(
- com.android.internal.R.array.config_halfFoldedDeviceStates))
+ when(mMockContext.getResources()
+ .getIntArray(R.array.config_halfFoldedDeviceStates))
.thenReturn(mHalfFoldedStates);
}
}
- private void build() throws Exception {
+ private void build() {
mMockContext = mock(Context.class);
- mMockRes = mock(Resources.class);
- when(mMockContext.getResources()).thenReturn((mMockRes));
mMockDeviceStateManager = mock(DeviceStateManager.class);
when(mMockContext.getSystemService(DeviceStateManager.class))
.thenReturn(mMockDeviceStateManager);
+ Resources mockRes = mock(Resources.class);
+ when(mMockContext.getResources()).thenReturn((mockRes));
mockFold(mSupportFold, mSupportHalfFold);
- mMockHandler = mock(Handler.class);
- mTarget = new DeviceStateController(mMockContext, mMockHandler, mDelegate);
+ Handler mockHandler = mock(Handler.class);
+ mTarget = new DeviceStateController(mMockContext, mockHandler);
+ mTarget.registerDeviceStateCallback(mDelegate);
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 4ce43e1..f814608 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -705,7 +705,7 @@
enableOrientationSensor();
- mTarget.foldStateChanged(DeviceStateController.FoldState.OPEN);
+ mTarget.foldStateChanged(DeviceStateController.DeviceState.OPEN);
freezeRotation(Surface.ROTATION_270);
mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_0));
@@ -715,7 +715,7 @@
SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
// ... until half-fold
- mTarget.foldStateChanged(DeviceStateController.FoldState.HALF_FOLDED);
+ mTarget.foldStateChanged(DeviceStateController.DeviceState.HALF_FOLDED);
assertTrue(waitForUiHandler());
verify(sMockWm).updateRotation(false, false);
assertTrue(waitForUiHandler());
@@ -723,7 +723,7 @@
SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
// ... then transition back to flat
- mTarget.foldStateChanged(DeviceStateController.FoldState.OPEN);
+ mTarget.foldStateChanged(DeviceStateController.DeviceState.OPEN);
assertTrue(waitForUiHandler());
verify(sMockWm, atLeast(1)).updateRotation(false, false);
assertTrue(waitForUiHandler());
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 5e087f0..478bd85 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -28,21 +28,36 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+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.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
+import android.annotation.Nullable;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.Property;
+import android.content.res.Resources;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.RoundedCorner;
+import android.view.RoundedCorners;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
+
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.Before;
@@ -61,6 +76,14 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class LetterboxUiControllerTest extends WindowTestsBase {
+ private static final int TASKBAR_COLLAPSED_HEIGHT = 10;
+ private static final int TASKBAR_EXPANDED_HEIGHT = 20;
+ private static final int SCREEN_WIDTH = 200;
+ private static final int SCREEN_HEIGHT = 100;
+ private static final Rect TASKBAR_COLLAPSED_BOUNDS = new Rect(0,
+ SCREEN_HEIGHT - TASKBAR_COLLAPSED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
+ private static final Rect TASKBAR_EXPANDED_BOUNDS = new Rect(0,
+ SCREEN_HEIGHT - TASKBAR_EXPANDED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -69,6 +92,7 @@
private DisplayContent mDisplayContent;
private LetterboxUiController mController;
private LetterboxConfiguration mLetterboxConfiguration;
+ private final Rect mLetterboxedPortraitTaskBounds = new Rect();
@Before
public void setUp() throws Exception {
@@ -308,6 +332,162 @@
assertTrue(mController.shouldForceRotateForCameraCompat());
}
+ @Test
+ public void testGetCropBoundsIfNeeded_noCrop() {
+ final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
+
+ // Do not apply crop if taskbar is collapsed
+ taskbar.setFrame(TASKBAR_COLLAPSED_BOUNDS);
+ assertNull(mController.getExpandedTaskbarOrNull(mainWindow));
+
+ mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 4,
+ SCREEN_WIDTH - SCREEN_WIDTH / 4, SCREEN_HEIGHT - SCREEN_HEIGHT / 4);
+
+ final Rect noCrop = mController.getCropBoundsIfNeeded(mainWindow);
+ assertNotEquals(null, noCrop);
+ assertEquals(0, noCrop.left);
+ assertEquals(0, noCrop.top);
+ assertEquals(mLetterboxedPortraitTaskBounds.width(), noCrop.right);
+ assertEquals(mLetterboxedPortraitTaskBounds.height(), noCrop.bottom);
+ }
+
+ @Test
+ public void testGetCropBoundsIfNeeded_appliesCrop() {
+ final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
+
+ // Apply crop if taskbar is expanded
+ taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS);
+ assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow));
+
+ mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4,
+ SCREEN_HEIGHT);
+
+ final Rect crop = mController.getCropBoundsIfNeeded(mainWindow);
+ assertNotEquals(null, crop);
+ assertEquals(0, crop.left);
+ assertEquals(0, crop.top);
+ assertEquals(mLetterboxedPortraitTaskBounds.width(), crop.right);
+ assertEquals(mLetterboxedPortraitTaskBounds.height() - TASKBAR_EXPANDED_HEIGHT,
+ crop.bottom);
+ }
+
+ @Test
+ public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() {
+ final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
+ final float scaling = 2.0f;
+
+ // Apply crop if taskbar is expanded
+ taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS);
+ assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow));
+ // With SizeCompat scaling
+ doReturn(true).when(mActivity).inSizeCompatMode();
+ mainWindow.mInvGlobalScale = scaling;
+
+ mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4,
+ SCREEN_HEIGHT);
+
+ final int appWidth = mLetterboxedPortraitTaskBounds.width();
+ final int appHeight = mLetterboxedPortraitTaskBounds.height();
+
+ final Rect crop = mController.getCropBoundsIfNeeded(mainWindow);
+ assertNotEquals(null, crop);
+ assertEquals(0, crop.left);
+ assertEquals(0, crop.top);
+ assertEquals((int) (appWidth * scaling), crop.right);
+ assertEquals((int) ((appHeight - TASKBAR_EXPANDED_HEIGHT) * scaling), crop.bottom);
+ }
+
+ @Test
+ public void testGetRoundedCornersRadius_withRoundedCornersFromInsets() {
+ final float invGlobalScale = 0.5f;
+ final int expectedRadius = 7;
+ final int configurationRadius = 15;
+
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
+ mainWindow.mInvGlobalScale = invGlobalScale;
+ final InsetsState insets = mainWindow.getInsetsState();
+
+ RoundedCorners roundedCorners = new RoundedCorners(
+ /*topLeft=*/ null,
+ /*topRight=*/ null,
+ /*bottomRight=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT,
+ configurationRadius, /*centerX=*/ 1, /*centerY=*/ 1),
+ /*bottomLeft=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT,
+ configurationRadius * 2 /*2 is to test selection of the min radius*/,
+ /*centerX=*/ 1, /*centerY=*/ 1)
+ );
+ doReturn(roundedCorners).when(insets).getRoundedCorners();
+ mLetterboxConfiguration.setLetterboxActivityCornersRadius(-1);
+
+ assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
+ }
+
+ @Test
+ public void testGetRoundedCornersRadius_withLetterboxActivityCornersRadius() {
+ final float invGlobalScale = 0.5f;
+ final int expectedRadius = 7;
+ final int configurationRadius = 15;
+
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
+ mainWindow.mInvGlobalScale = invGlobalScale;
+ mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius);
+
+ assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
+
+ }
+
+ @Test
+ public void testGetRoundedCornersRadius_noScalingApplied() {
+ final int configurationRadius = 15;
+
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
+ mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius);
+
+ mainWindow.mInvGlobalScale = -1f;
+ assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow));
+
+ mainWindow.mInvGlobalScale = 0f;
+ assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow));
+
+ mainWindow.mInvGlobalScale = 1f;
+ assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow));
+ }
+
+ private WindowState mockForGetCropBoundsAndRoundedCorners(@Nullable InsetsSource taskbar) {
+ final WindowState mainWindow = mock(WindowState.class);
+ final InsetsState insets = mock(InsetsState.class);
+ final Resources resources = mWm.mContext.getResources();
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+
+ mainWindow.mInvGlobalScale = 1f;
+ spyOn(resources);
+ spyOn(mActivity);
+
+ if (taskbar != null) {
+ taskbar.setVisible(true);
+ doReturn(taskbar).when(insets).peekSource(taskbar.getType());
+ }
+ doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
+ doReturn(true).when(mActivity).isVisible();
+ doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
+ doReturn(insets).when(mainWindow).getInsetsState();
+ doReturn(attrs).when(mainWindow).getAttrs();
+ doReturn(true).when(mainWindow).isDrawn();
+ doReturn(false).when(mainWindow).isLetterboxedForDisplayCutout();
+ doReturn(true).when(mainWindow).areAppWindowBoundsLetterboxed();
+ doReturn(true).when(mLetterboxConfiguration).isLetterboxActivityCornersRounded();
+ doReturn(TASKBAR_EXPANDED_HEIGHT).when(resources).getDimensionPixelSize(
+ R.dimen.taskbar_frame_height);
+
+ // Need to reinitialise due to the change in resources getDimensionPixelSize output.
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ return mainWindow;
+ }
+
private void mockThatProperty(String propertyName, boolean value) throws Exception {
Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
/* className */ "");
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index de0999f..a09bb33 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -98,7 +98,7 @@
import com.android.internal.policy.SystemBarUtils;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.wm.DeviceStateController.FoldState;
+import com.android.server.wm.DeviceStateController.DeviceState;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -2821,7 +2821,7 @@
mActivity.mRootWindowContainer.performSurfacePlacement();
final ArgumentCaptor<Rect> cropCapturer = ArgumentCaptor.forClass(Rect.class);
- verify(mTransaction, times(2)).setWindowCrop(
+ verify(mTransaction, times(2)).setCrop(
eq(w1.getSurfaceControl()),
cropCapturer.capture()
);
@@ -3186,9 +3186,9 @@
private void setFoldablePosture(boolean isHalfFolded, boolean isTabletop) {
final DisplayRotation r = mActivity.mDisplayContent.getDisplayRotation();
doReturn(isHalfFolded).when(r).isDisplaySeparatingHinge();
- doReturn(false).when(r).isDeviceInPosture(any(FoldState.class), anyBoolean());
+ doReturn(false).when(r).isDeviceInPosture(any(DeviceState.class), anyBoolean());
if (isHalfFolded) {
- doReturn(true).when(r).isDeviceInPosture(FoldState.HALF_FOLDED, isTabletop);
+ doReturn(true).when(r).isDeviceInPosture(DeviceState.HALF_FOLDED, isTabletop);
}
mActivity.recomputeConfiguration();
}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 42a5af7..17c354a 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -36,6 +36,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
/**
@@ -106,6 +107,12 @@
return false;
}
+ /**
+ * List of connected MIDI devices
+ */
+ private final HashMap<String, UsbMidiDevice>
+ mMidiDevices = new HashMap<String, UsbMidiDevice>();
+
// UsbMidiDevice for USB peripheral mode (gadget) device
private UsbMidiDevice mPeripheralMidiDevice = null;
@@ -249,6 +256,8 @@
}
}
+ addMidiDevice(deviceAddress, usbDevice, parser, cardRec);
+
logDevices("deviceAdded()");
if (DEBUG) {
@@ -256,6 +265,54 @@
}
}
+ private void addMidiDevice(String deviceAddress, UsbDevice usbDevice,
+ UsbDescriptorParser parser, AlsaCardsParser.AlsaCardRecord cardRec) {
+ boolean hasMidi = parser.hasMIDIInterface();
+ // UsbHostManager will create UsbDirectMidiDevices instead if MIDI 2 is supported.
+ boolean hasMidi2 = parser.containsUniversalMidiDeviceEndpoint();
+ if (DEBUG) {
+ Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature);
+ Slog.d(TAG, "hasMidi2: " + hasMidi2);
+ }
+ if (mHasMidiFeature && hasMidi && !hasMidi2) {
+ Bundle properties = new Bundle();
+ String manufacturer = usbDevice.getManufacturerName();
+ String product = usbDevice.getProductName();
+ String version = usbDevice.getVersion();
+ String name;
+ if (manufacturer == null || manufacturer.isEmpty()) {
+ name = product;
+ } else if (product == null || product.isEmpty()) {
+ name = manufacturer;
+ } else {
+ name = manufacturer + " " + product;
+ }
+ properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
+ properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
+ properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
+ properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
+ properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
+ usbDevice.getSerialNumber());
+ properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, cardRec.getCardNum());
+ properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, 0 /*deviceNum*/);
+ properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
+
+ int numLegacyMidiInputs = parser.calculateNumLegacyMidiInputs();
+ int numLegacyMidiOutputs = parser.calculateNumLegacyMidiOutputs();
+ if (DEBUG) {
+ Slog.d(TAG, "numLegacyMidiInputs: " + numLegacyMidiInputs);
+ Slog.d(TAG, "numLegacyMidiOutputs:" + numLegacyMidiOutputs);
+ }
+
+ UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
+ cardRec.getCardNum(), 0 /*device*/, numLegacyMidiInputs,
+ numLegacyMidiOutputs);
+ if (usbMidiDevice != null) {
+ mMidiDevices.put(deviceAddress, usbMidiDevice);
+ }
+ }
+ }
+
/* package */ synchronized void usbDeviceRemoved(String deviceAddress/*UsbDevice usbDevice*/) {
if (DEBUG) {
Slog.d(TAG, "deviceRemoved(" + deviceAddress + ")");
@@ -269,6 +326,13 @@
selectDefaultDevice(); // if there any external devices left, select one of them
}
+ // MIDI
+ UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress);
+ if (usbMidiDevice != null) {
+ Slog.i(TAG, "USB MIDI Device Removed: " + deviceAddress);
+ IoUtils.closeQuietly(usbMidiDevice);
+ }
+
logDevices("usbDeviceRemoved()");
}
@@ -324,6 +388,12 @@
usbAlsaDevice.dump(dump, "alsa_devices", UsbAlsaManagerProto.ALSA_DEVICES);
}
+ for (String deviceAddr : mMidiDevices.keySet()) {
+ // A UsbMidiDevice does not have a handle to the UsbDevice anymore
+ mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices",
+ UsbAlsaManagerProto.MIDI_DEVICES);
+ }
+
dump.end(token);
}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index f389276..b3eb285 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -444,14 +444,19 @@
} else {
Slog.e(TAG, "Universal Midi Device is null.");
}
- }
- if (parser.containsLegacyMidiDeviceEndpoint()) {
- UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
- newDevice, parser, false, uniqueUsbDeviceIdentifier);
- if (midiDevice != null) {
- midiDevices.add(midiDevice);
- } else {
- Slog.e(TAG, "Legacy Midi Device is null.");
+
+ // Use UsbDirectMidiDevice only if this supports MIDI 2.0 as well.
+ // ALSA removes the audio sound card if MIDI interfaces are removed.
+ // This means that as long as ALSA is used for audio, MIDI 1.0 USB
+ // devices should use the ALSA path for MIDI.
+ if (parser.containsLegacyMidiDeviceEndpoint()) {
+ midiDevice = UsbDirectMidiDevice.create(mContext,
+ newDevice, parser, false, uniqueUsbDeviceIdentifier);
+ if (midiDevice != null) {
+ midiDevices.add(midiDevice);
+ } else {
+ Slog.e(TAG, "Legacy Midi Device is null.");
+ }
}
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
index 3f2d8c8..c6ea228 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -79,6 +79,10 @@
mInterfaceDescriptors.add(interfaceDesc);
}
+ ArrayList<UsbInterfaceDescriptor> getInterfaceDescriptors() {
+ return mInterfaceDescriptors;
+ }
+
private boolean isAudioInterface(UsbInterfaceDescriptor descriptor) {
return descriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO
&& descriptor.getUsbSubclass() == UsbDescriptor.AUDIO_AUDIOSTREAMING;
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index cd6ea68..626ce89 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -40,6 +40,7 @@
private UsbDeviceDescriptor mDeviceDescriptor;
private UsbConfigDescriptor mCurConfigDescriptor;
private UsbInterfaceDescriptor mCurInterfaceDescriptor;
+ private UsbEndpointDescriptor mCurEndpointDescriptor;
// The AudioClass spec implemented by the AudioClass Interfaces
// This may well be different than the overall USB Spec.
@@ -165,7 +166,7 @@
break;
case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT:
- descriptor = new UsbEndpointDescriptor(length, type);
+ descriptor = mCurEndpointDescriptor = new UsbEndpointDescriptor(length, type);
if (mCurInterfaceDescriptor != null) {
mCurInterfaceDescriptor.addEndpointDescriptor(
(UsbEndpointDescriptor) descriptor);
@@ -265,6 +266,9 @@
+ Integer.toHexString(subClass));
break;
}
+ if (mCurEndpointDescriptor != null && descriptor != null) {
+ mCurEndpointDescriptor.setClassSpecificEndpointDescriptor(descriptor);
+ }
}
break;
@@ -798,6 +802,84 @@
/**
* @hide
*/
+ private int calculateNumLegacyMidiPorts(boolean isOutput) {
+ // Only look at the first config.
+ UsbConfigDescriptor configDescriptor = null;
+ for (UsbDescriptor descriptor : mDescriptors) {
+ if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_CONFIG) {
+ if (descriptor instanceof UsbConfigDescriptor) {
+ configDescriptor = (UsbConfigDescriptor) descriptor;
+ break;
+ } else {
+ Log.w(TAG, "Unrecognized Config l: " + descriptor.getLength()
+ + " t:0x" + Integer.toHexString(descriptor.getType()));
+ }
+ }
+ }
+ if (configDescriptor == null) {
+ Log.w(TAG, "Config not found");
+ return 0;
+ }
+
+ ArrayList<UsbInterfaceDescriptor> legacyMidiInterfaceDescriptors =
+ new ArrayList<UsbInterfaceDescriptor>();
+ for (UsbInterfaceDescriptor interfaceDescriptor
+ : configDescriptor.getInterfaceDescriptors()) {
+ if (interfaceDescriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO) {
+ if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
+ UsbDescriptor midiHeaderDescriptor =
+ interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
+ if (midiHeaderDescriptor != null) {
+ if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
+ UsbMSMidiHeader midiHeader =
+ (UsbMSMidiHeader) midiHeaderDescriptor;
+ if (midiHeader.getMidiStreamingClass() == MS_MIDI_1_0) {
+ legacyMidiInterfaceDescriptors.add(interfaceDescriptor);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ int count = 0;
+ for (UsbInterfaceDescriptor interfaceDescriptor : legacyMidiInterfaceDescriptors) {
+ for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) {
+ UsbEndpointDescriptor endpoint =
+ interfaceDescriptor.getEndpointDescriptor(i);
+ // 0 is output, 1 << 7 is input.
+ if ((endpoint.getDirection() == 0) == isOutput) {
+ UsbDescriptor classSpecificEndpointDescriptor =
+ endpoint.getClassSpecificEndpointDescriptor();
+ if (classSpecificEndpointDescriptor != null
+ && (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) {
+ UsbACMidi10Endpoint midiEndpoint =
+ (UsbACMidi10Endpoint) classSpecificEndpointDescriptor;
+ count += midiEndpoint.getNumJacks();
+ }
+ }
+ }
+ }
+ return count;
+ }
+
+ /**
+ * @hide
+ */
+ public int calculateNumLegacyMidiInputs() {
+ return calculateNumLegacyMidiPorts(false /*isOutput*/);
+ }
+
+ /**
+ * @hide
+ */
+ public int calculateNumLegacyMidiOutputs() {
+ return calculateNumLegacyMidiPorts(true /*isOutput*/);
+ }
+
+ /**
+ * @hide
+ */
public float getInputHeadsetProbability() {
if (hasMIDIInterface()) {
return 0.0f;
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index ab07ce7..1f448ac 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -79,6 +79,8 @@
private byte mRefresh;
private byte mSyncAddress;
+ private UsbDescriptor mClassSpecificEndpointDescriptor;
+
public UsbEndpointDescriptor(int length, byte type) {
super(length, type);
mHierarchyLevel = 4;
@@ -112,6 +114,14 @@
return mEndpointAddress & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION;
}
+ void setClassSpecificEndpointDescriptor(UsbDescriptor descriptor) {
+ mClassSpecificEndpointDescriptor = descriptor;
+ }
+
+ UsbDescriptor getClassSpecificEndpointDescriptor() {
+ return mClassSpecificEndpointDescriptor;
+ }
+
/**
* Returns a UsbEndpoint that this UsbEndpointDescriptor is describing.
*/