Merge "Integrate isHardwareIgnoringTouches into OperationContext" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index edd9d3a..ce311d0 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -73,6 +73,7 @@
":android.provider.flags-aconfig-java{.generated_srcjars}",
":android.chre.flags-aconfig-java{.generated_srcjars}",
":android.speech.flags-aconfig-java{.generated_srcjars}",
+ ":power_flags_lib{.generated_srcjars}",
]
filegroup {
@@ -932,3 +933,10 @@
aconfig_declarations: "android.speech.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// Power
+java_aconfig_library {
+ name: "power_flags_lib",
+ aconfig_declarations: "power_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index ecfd86c..d59775f 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -138,14 +138,15 @@
}
],
"postsubmit-ravenwood": [
- {
- "name": "CtsUtilTestCasesRavenwood",
- "host": true,
- "file_patterns": [
- "*Ravenwood*",
- "*ravenwood*"
- ]
- }
+ // TODO(ravenwood) promote it to presubmit
+ // TODO: Enable it once the infra knows how to run it.
+// {
+// "name": "CtsUtilTestCasesRavenwood",
+// "file_patterns": [
+// "*Ravenwood*",
+// "*ravenwood*"
+// ]
+// }
],
"postsubmit-managedprofile-stress": [
{
diff --git a/core/api/current.txt b/core/api/current.txt
index 8a1a466..46c8f82 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9486,12 +9486,15 @@
method @NonNull public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForPackage(@NonNull String, @Nullable android.os.UserHandle);
method @NonNull public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(@Nullable android.os.UserHandle);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
+ method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int);
method public boolean isRequestPinAppWidgetSupported();
method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
+ method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
+ method @FlaggedApi("android.appwidget.flags.generated_previews") public void setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
method public void updateAppWidget(int[], android.widget.RemoteViews);
method public void updateAppWidget(int, android.widget.RemoteViews);
method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -9565,6 +9568,7 @@
field public int autoAdvanceViewId;
field public android.content.ComponentName configure;
field @IdRes public int descriptionRes;
+ field @FlaggedApi("android.appwidget.flags.generated_previews") public int generatedPreviewCategories;
field public int icon;
field public int initialKeyguardLayout;
field public int initialLayout;
@@ -18676,6 +18680,7 @@
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject, @NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
method @Nullable public int getAllowedAuthenticators();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView();
method @Nullable public CharSequence getDescription();
method @Nullable public CharSequence getNegativeButtonText();
method @Nullable public CharSequence getSubtitle();
@@ -18723,6 +18728,7 @@
method @NonNull public android.hardware.biometrics.BiometricPrompt build();
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setAllowedAuthenticators(int);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setConfirmationRequired(boolean);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
@@ -18746,6 +18752,43 @@
method @Nullable public java.security.Signature getSignature();
}
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentListItem {
+ }
+
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentListItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentListItem {
+ ctor public PromptContentListItemBulletedText(@NonNull CharSequence);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentListItemBulletedText> CREATOR;
+ }
+
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentListItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentListItem {
+ ctor public PromptContentListItemPlainText(@NonNull CharSequence);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentListItemPlainText> CREATOR;
+ }
+
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentView {
+ }
+
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
+ method public int describeContents();
+ method @Nullable public CharSequence getDescription();
+ method @NonNull public java.util.List<android.hardware.biometrics.PromptContentListItem> getListItems();
+ method public static int getMaxEachItemCharacterNumber();
+ method public static int getMaxItemCount();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptVerticalListContentView> CREATOR;
+ }
+
+ public static final class PromptVerticalListContentView.Builder {
+ ctor public PromptVerticalListContentView.Builder();
+ method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentListItem);
+ method @NonNull public android.hardware.biometrics.PromptVerticalListContentView build();
+ method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull CharSequence);
+ }
+
}
package android.hardware.camera2 {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ebd0544..bd4ecf2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -17023,6 +17023,7 @@
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteManager {
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getAllSatellitePlmnsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 4cf9fca..6204edc 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -19,6 +19,7 @@
import static android.appwidget.flags.Flags.remoteAdapterConversion;
import android.annotation.BroadcastBehavior;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
@@ -30,6 +31,7 @@
import android.annotation.UserIdInt;
import android.app.IServiceConnection;
import android.app.PendingIntent;
+import android.appwidget.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -1415,6 +1417,89 @@
}
}
+ /**
+ * Set a preview for this widget. This preview will be used instead of the provider's {@link
+ * AppWidgetProviderInfo#previewLayout previewLayout} or {@link
+ * AppWidgetProviderInfo#previewImage previewImage} for previewing the widget in the widget
+ * picker and pin app widget flow.
+ *
+ * @param provider The {@link ComponentName} for the {@link android.content.BroadcastReceiver
+ * BroadcastReceiver} provider for the AppWidget you intend to provide a preview for.
+ * @param widgetCategories The categories that this preview should be used for. This can be a
+ * single category or combination of categories. If multiple categories are specified,
+ * then this preview will be used for each of those categories. For example, if you
+ * set a preview for WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD, the preview will
+ * be used when picking widgets for the home screen and keyguard.
+ *
+ * <p>Note: You should only use the widget categories that the provider supports, as defined
+ * in {@link AppWidgetProviderInfo#widgetCategory}.
+ * @param preview This preview will be used for previewing the provider when picking widgets for
+ * the selected categories.
+ *
+ * @see AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN
+ * @see AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD
+ * @see AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX
+ */
+ @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
+ public void setWidgetPreview(@NonNull ComponentName provider,
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
+ @NonNull RemoteViews preview) {
+ try {
+ mService.setWidgetPreview(provider, widgetCategories, preview);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the RemoteViews previews for this widget.
+ *
+ * @param provider The {@link ComponentName} for the {@link android.content.BroadcastReceiver
+ * BroadcastReceiver} provider for the AppWidget you intend to get a preview for.
+ * @param profile The profile in which the provider resides. Passing null is equivalent
+ * to querying for only the calling user.
+ * @param widgetCategory The widget category for which you want to display previews. This should
+ * be a single category. If a combination of categories is provided, this function will
+ * return a preview that matches at least one of the categories.
+ *
+ * @return The widget preview for the selected category, if available.
+ * @see AppWidgetProviderInfo#generatedPreviewCategories
+ */
+ @Nullable
+ @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
+ public RemoteViews getWidgetPreview(@NonNull ComponentName provider,
+ @Nullable UserHandle profile, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
+ try {
+ if (profile == null) {
+ profile = mContext.getUser();
+ }
+ return mService.getWidgetPreview(mPackageName, provider, profile.getIdentifier(),
+ widgetCategory);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove this provider's preview for the specified widget categories. If the provider does not
+ * have a preview for the specified widget category, this is a no-op.
+ *
+ * @param provider The AppWidgetProvider to remove previews for.
+ * @param widgetCategories The categories of the preview to remove. For example, removing the
+ * preview for WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD will remove the
+ * previews for both categories.
+ */
+ @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
+ public void removeWidgetPreview(@NonNull ComponentName provider,
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
+ try {
+ mService.removeWidgetPreview(provider, widgetCategories);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
@UiThread
private static @NonNull Executor createUpdateExecutorIfNull() {
if (sUpdateExecutor == null) {
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index e56e53a..1a80cac2 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -16,6 +16,10 @@
package android.appwidget;
+import static android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS;
+import static android.appwidget.flags.Flags.generatedPreviews;
+
+import android.annotation.FlaggedApi;
import android.annotation.IdRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -358,6 +362,20 @@
/** @hide */
public boolean isExtendedFromAppWidgetProvider;
+ /**
+ * Flags indicating the widget categories for which generated previews are available.
+ * These correspond to the previews set by this provider with
+ * {@link AppWidgetManager#setWidgetPreview}.
+ *
+ * @see #WIDGET_CATEGORY_HOME_SCREEN
+ * @see #WIDGET_CATEGORY_KEYGUARD
+ * @see #WIDGET_CATEGORY_SEARCHBOX
+ * @see AppWidgetManager#getWidgetPreview
+ */
+ @FlaggedApi(FLAG_GENERATED_PREVIEWS)
+ @SuppressLint("MutableBareField")
+ public int generatedPreviewCategories;
+
public AppWidgetProviderInfo() {
}
@@ -391,6 +409,9 @@
this.widgetFeatures = in.readInt();
this.descriptionRes = in.readInt();
this.isExtendedFromAppWidgetProvider = in.readBoolean();
+ if (generatedPreviews()) {
+ generatedPreviewCategories = in.readInt();
+ }
}
/**
@@ -515,6 +536,9 @@
out.writeInt(this.widgetFeatures);
out.writeInt(this.descriptionRes);
out.writeBoolean(this.isExtendedFromAppWidgetProvider);
+ if (generatedPreviews()) {
+ out.writeInt(this.generatedPreviewCategories);
+ }
}
@Override
@@ -545,6 +569,9 @@
that.widgetFeatures = this.widgetFeatures;
that.descriptionRes = this.descriptionRes;
that.isExtendedFromAppWidgetProvider = this.isExtendedFromAppWidgetProvider;
+ if (generatedPreviews()) {
+ that.generatedPreviewCategories = this.generatedPreviewCategories;
+ }
return that;
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 8c1ea5f..a0f4d8d 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -22,6 +22,7 @@
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
@@ -208,6 +209,12 @@
/**
* Optional: Sets a description that will be shown on the prompt.
+ *
+ * <p> Note that the description set by {@link Builder#setDescription(CharSequence)} will be
+ * overridden by {@link Builder#setContentView(PromptContentView)}. The view provided to
+ * {@link Builder#setContentView(PromptContentView)} will be used if both methods are
+ * called.
+ *
* @param description The description to display.
* @return This builder.
*/
@@ -218,6 +225,24 @@
}
/**
+ * Optional: Sets application customized content view that will be shown on the prompt.
+ *
+ * <p> Note that the description set by {@link Builder#setDescription(CharSequence)} will be
+ * overridden by {@link Builder#setContentView(PromptContentView)}. The view provided to
+ * {@link Builder#setContentView(PromptContentView)} will be used if both methods are
+ * called.
+ *
+ * @param view The customized view information.
+ * @return This builder.re
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @NonNull
+ public BiometricPrompt.Builder setContentView(@NonNull PromptContentView view) {
+ mPromptInfo.setContentView(view);
+ return this;
+ }
+
+ /**
* @param service
* @return This builder.
* @hide
@@ -698,6 +723,18 @@
}
/**
+ * Gets the content view for the prompt, as set by
+ * {@link Builder#setContentView(PromptContentView)}.
+ *
+ * @return The content view for the prompt, or null if the prompt has no content view.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @Nullable
+ public PromptContentView getContentView() {
+ return mPromptInfo.getContentView();
+ }
+
+ /**
* Gets the negative button text for the prompt, as set by
* {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
* @return The negative button text for the prompt, or null if no negative button text was set.
diff --git a/core/java/android/hardware/biometrics/PromptContentListItem.java b/core/java/android/hardware/biometrics/PromptContentListItem.java
new file mode 100644
index 0000000..fa3783d
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentListItem.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+
+/**
+ * A list item shown on {@link PromptVerticalListContentView}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public interface PromptContentListItem {
+}
+
diff --git a/core/java/android/hardware/biometrics/PromptContentListItemBulletedText.java b/core/java/android/hardware/biometrics/PromptContentListItemBulletedText.java
new file mode 100644
index 0000000..c31f8a6
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentListItemBulletedText.java
@@ -0,0 +1,81 @@
+/*
+ * 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 android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A list item with bulleted text shown on {@link PromptVerticalListContentView}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public final class PromptContentListItemBulletedText implements PromptContentListItemParcelable {
+ private final CharSequence mText;
+
+ /**
+ * A list item with bulleted text shown on {@link PromptVerticalListContentView}.
+ *
+ * @param text The text of this list item.
+ */
+ public PromptContentListItemBulletedText(@NonNull CharSequence text) {
+ mText = text;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeCharSequence(mText);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<PromptContentListItemBulletedText> CREATOR = new Creator<>() {
+ @Override
+ public PromptContentListItemBulletedText createFromParcel(Parcel in) {
+ return new PromptContentListItemBulletedText(in.readCharSequence());
+ }
+
+ @Override
+ public PromptContentListItemBulletedText[] newArray(int size) {
+ return new PromptContentListItemBulletedText[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/biometrics/PromptContentListItemParcelable.java b/core/java/android/hardware/biometrics/PromptContentListItemParcelable.java
new file mode 100644
index 0000000..15271f0
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentListItemParcelable.java
@@ -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 android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcelable;
+
+/**
+ * A parcelable {@link PromptContentListItem}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+sealed interface PromptContentListItemParcelable extends PromptContentListItem, Parcelable
+ permits PromptContentListItemPlainText, PromptContentListItemBulletedText {
+}
diff --git a/core/java/android/hardware/biometrics/PromptContentListItemPlainText.java b/core/java/android/hardware/biometrics/PromptContentListItemPlainText.java
new file mode 100644
index 0000000..d72a758
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentListItemPlainText.java
@@ -0,0 +1,81 @@
+/*
+ * 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 android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A list item with plain text shown on {@link PromptVerticalListContentView}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public final class PromptContentListItemPlainText implements PromptContentListItemParcelable {
+ private final CharSequence mText;
+
+ /**
+ * A list item with plain text shown on {@link PromptVerticalListContentView}.
+ *
+ * @param text The text of this list item.
+ */
+ public PromptContentListItemPlainText(@NonNull CharSequence text) {
+ mText = text;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeCharSequence(mText);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<PromptContentListItemPlainText> CREATOR = new Creator<>() {
+ @Override
+ public PromptContentListItemPlainText createFromParcel(Parcel in) {
+ return new PromptContentListItemPlainText(in.readCharSequence());
+ }
+
+ @Override
+ public PromptContentListItemPlainText[] newArray(int size) {
+ return new PromptContentListItemPlainText[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/biometrics/PromptContentView.java b/core/java/android/hardware/biometrics/PromptContentView.java
new file mode 100644
index 0000000..ff9313e
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentView.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+
+/**
+ * Contains the information of the template of content view for Biometric Prompt.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public interface PromptContentView {
+}
diff --git a/core/java/android/hardware/biometrics/PromptContentViewParcelable.java b/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
new file mode 100644
index 0000000..43b965b
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
@@ -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 android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcelable;
+
+/**
+ * A parcelable {@link PromptContentView}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+sealed interface PromptContentViewParcelable extends PromptContentView, Parcelable
+ permits PromptVerticalListContentView {
+}
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 24cfd164..c73ebd4 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -35,6 +35,7 @@
@Nullable private CharSequence mSubtitle;
private boolean mUseDefaultSubtitle;
@Nullable private CharSequence mDescription;
+ @Nullable private PromptContentViewParcelable mContentView;
@Nullable private CharSequence mDeviceCredentialTitle;
@Nullable private CharSequence mDeviceCredentialSubtitle;
@Nullable private CharSequence mDeviceCredentialDescription;
@@ -60,6 +61,8 @@
mSubtitle = in.readCharSequence();
mUseDefaultSubtitle = in.readBoolean();
mDescription = in.readCharSequence();
+ mContentView = in.readParcelable(PromptContentViewParcelable.class.getClassLoader(),
+ PromptContentViewParcelable.class);
mDeviceCredentialTitle = in.readCharSequence();
mDeviceCredentialSubtitle = in.readCharSequence();
mDeviceCredentialDescription = in.readCharSequence();
@@ -100,6 +103,7 @@
dest.writeCharSequence(mSubtitle);
dest.writeBoolean(mUseDefaultSubtitle);
dest.writeCharSequence(mDescription);
+ dest.writeParcelable(mContentView, 0);
dest.writeCharSequence(mDeviceCredentialTitle);
dest.writeCharSequence(mDeviceCredentialSubtitle);
dest.writeCharSequence(mDeviceCredentialDescription);
@@ -176,6 +180,10 @@
mDescription = description;
}
+ public void setContentView(PromptContentView view) {
+ mContentView = (PromptContentViewParcelable) view;
+ }
+
public void setDeviceCredentialTitle(CharSequence deviceCredentialTitle) {
mDeviceCredentialTitle = deviceCredentialTitle;
}
@@ -257,6 +265,15 @@
return mDescription;
}
+ /**
+ * Gets the content view for the prompt.
+ *
+ * @return The content view for the prompt, or null if the prompt has no content view.
+ */
+ public PromptContentView getContentView() {
+ return mContentView;
+ }
+
public CharSequence getDeviceCredentialTitle() {
return mDeviceCredentialTitle;
}
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
new file mode 100644
index 0000000..f3cb189
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -0,0 +1,205 @@
+/*
+ * 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 android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Contains the information of the template of vertical list content view for Biometric Prompt. Note
+ * that there are limits on the item count and the number of characters allowed for each item's
+ * text.
+ * <p>
+ * Here's how you'd set a <code>PromptVerticalListContentView</code> on a Biometric Prompt:
+ * <pre class="prettyprint">
+ * BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(...)
+ * .setTitle(...)
+ * .setSubTitle(...)
+ * .setContentView(new PromptVerticalListContentView.Builder()
+ * .setDescription("test description")
+ * .addListItem(new PromptContentListItemPlainText("test item 1"))
+ * .addListItem(new PromptContentListItemPlainText("test item 2"))
+ * .addListItem(new PromptContentListItemBulletedText("test item 3"))
+ * .build())
+ * .build();
+ * </pre>
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public final class PromptVerticalListContentView implements PromptContentViewParcelable {
+ private static final int MAX_ITEM_NUMBER = 20;
+ private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
+ private final List<PromptContentListItemParcelable> mContentList;
+ private final CharSequence mDescription;
+
+ private PromptVerticalListContentView(
+ @NonNull List<PromptContentListItemParcelable> contentList,
+ @NonNull CharSequence description) {
+ mContentList = contentList;
+ mDescription = description;
+ }
+
+ private PromptVerticalListContentView(Parcel in) {
+ mContentList = in.readArrayList(
+ PromptContentListItemParcelable.class.getClassLoader(),
+ PromptContentListItemParcelable.class);
+ mDescription = in.readCharSequence();
+ }
+
+ /**
+ * Returns the maximum count of the list items.
+ */
+ public static int getMaxItemCount() {
+ return MAX_ITEM_NUMBER;
+ }
+
+ /**
+ * Returns the maximum number of characters allowed for each item's text.
+ */
+ public static int getMaxEachItemCharacterNumber() {
+ return MAX_EACH_ITEM_CHARACTER_NUMBER;
+ }
+
+ /**
+ * Gets the description for the content view, as set by
+ * {@link PromptVerticalListContentView.Builder#setDescription(CharSequence)}.
+ *
+ * @return The description for the content view, or null if the content view has no description.
+ */
+ @Nullable
+ public CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Gets the list of ListItem on the content view, as set by
+ * {@link PromptVerticalListContentView.Builder#addListItem(PromptContentListItem)}.
+ *
+ * @return The item list on the content view.
+ */
+ @NonNull
+ public List<PromptContentListItem> getListItems() {
+ return new ArrayList<>(mContentList);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+ dest.writeList(mContentList);
+ dest.writeCharSequence(mDescription);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<PromptVerticalListContentView> CREATOR = new Creator<>() {
+ @Override
+ public PromptVerticalListContentView createFromParcel(Parcel in) {
+ return new PromptVerticalListContentView(in);
+ }
+
+ @Override
+ public PromptVerticalListContentView[] newArray(int size) {
+ return new PromptVerticalListContentView[size];
+ }
+ };
+
+
+ /**
+ * A builder that collects arguments to be shown on the vertical list view.
+ */
+ public static final class Builder {
+ private final List<PromptContentListItemParcelable> mContentList = new ArrayList<>();
+ private CharSequence mDescription;
+
+ /**
+ * Optional: Sets a description that will be shown on the content view.
+ *
+ * @param description The description to display.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setDescription(@NonNull CharSequence description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in
+ * total.
+ *
+ * @param listItem The list item view to display
+ * @return This builder.
+ */
+ @NonNull
+ public Builder addListItem(@NonNull PromptContentListItem listItem) {
+ if (doesListItemExceedsCharLimit(listItem)) {
+ throw new IllegalStateException(
+ "The character number of list item exceeds "
+ + MAX_EACH_ITEM_CHARACTER_NUMBER);
+ }
+ mContentList.add((PromptContentListItemParcelable) listItem);
+ return this;
+ }
+
+ private boolean doesListItemExceedsCharLimit(PromptContentListItem listItem) {
+ if (listItem instanceof PromptContentListItemPlainText) {
+ return ((PromptContentListItemPlainText) listItem).getText().length()
+ > MAX_EACH_ITEM_CHARACTER_NUMBER;
+ } else if (listItem instanceof PromptContentListItemBulletedText) {
+ return ((PromptContentListItemBulletedText) listItem).getText().length()
+ > MAX_EACH_ITEM_CHARACTER_NUMBER;
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Creates a {@link PromptVerticalListContentView}.
+ *
+ * @return An instance of {@link PromptVerticalListContentView}.
+ */
+ @NonNull
+ public PromptVerticalListContentView build() {
+ if (mContentList.size() > MAX_ITEM_NUMBER) {
+ throw new IllegalStateException(
+ "The number of list items exceeds " + MAX_ITEM_NUMBER);
+ }
+ return new PromptVerticalListContentView(mContentList, mDescription);
+ }
+ }
+}
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 375fdb5..3ba8be4 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -21,3 +21,10 @@
bug: "307601768"
}
+flag {
+ name: "custom_biometric_prompt"
+ namespace: "biometrics_framework"
+ description: "Feature flag for adding a custom content view API to BiometricPrompt.Builder."
+ bug: "302735104"
+}
+
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 561db9c..83b7eda 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -80,8 +80,6 @@
* <p>The VCN will only migrate to a Carrier WiFi network that has a signal strength greater
* than, or equal to this threshold.
*
- * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup.
- *
* @hide
*/
@NonNull
@@ -94,8 +92,6 @@
* <p>If the VCN's selected Carrier WiFi network has a signal strength less than this threshold,
* the VCN will attempt to migrate away from the Carrier WiFi network.
*
- * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup.
- *
* @hide
*/
@NonNull
@@ -120,6 +116,15 @@
public static final String VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY =
"vcn_network_selection_ipsec_packet_loss_percent_threshold";
+ /**
+ * Key for the list of timeouts in minute to stop penalizing an underlying network candidate
+ *
+ * @hide
+ */
+ @NonNull
+ public static final String VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY =
+ "vcn_network_selection_penalty_timeout_minutes_list";
+
// TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz
/**
@@ -168,6 +173,7 @@
VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+ VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 47065e1..f974ef4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13476,6 +13476,16 @@
@Readable
public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
+
+ /**
+ * Whether to boot with 16K page size compatible kernel
+ * 1 = Boot with 16K kernel
+ * 0 = Boot with 4K kernel (default)
+ * @hide
+ */
+ @Readable
+ public static final String ENABLE_16K_PAGES = "enable_16k_pages";
+
/** Timeout for package verification.
* @hide */
@Readable
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index bb7677d6..ccc5dbb 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -47,3 +47,10 @@
is_fixed_read_only: true
}
+flag {
+ name: "use_zero_jank_proxy"
+ namespace: "input_method"
+ description: "Feature flag for using a proxy that uses async calls to achieve zero jank for IMMS calls."
+ bug: "293640003"
+ is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 4fe9aea..85bdbb9 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -80,5 +80,11 @@
in Bundle extras, in IntentSender resultIntent);
boolean isRequestPinAppWidgetSupported();
oneway void noteAppWidgetTapped(in String callingPackage, in int appWidgetId);
+ void setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
+ in RemoteViews preview);
+ @nullable RemoteViews getWidgetPreview(in String callingPackage,
+ in ComponentName providerComponent, in int profileId, in int widgetCategory);
+ void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories);
+
}
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index dcc9686..fbe1b8e 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -48,6 +48,7 @@
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.READ_SEARCH_INDEXABLES"/>
<permission name="android.permission.REBOOT"/>
+ <permission name="android.permission.RECOVERY"/>
<permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"/>
<permission name="android.permission.TETHER_PRIVILEGED"/>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 896bcaf..e23e15f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1249,6 +1249,7 @@
}
String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
+ Log.v(TAG, "showOrHideAppBubble, with key: " + appBubbleKey);
PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
if (!isResizableActivity(intent, packageManager, appBubbleKey)) return;
@@ -1258,18 +1259,22 @@
if (isStackExpanded()) {
if (selectedBubble != null && appBubbleKey.equals(selectedBubble.getKey())) {
// App bubble is expanded, lets collapse
+ Log.v(TAG, " showOrHideAppBubble, selected bubble is app bubble, collapsing");
collapseStack();
} else {
// App bubble is not selected, select it
+ Log.v(TAG, " showOrHideAppBubble, expanded, selecting existing app bubble");
mBubbleData.setSelectedBubble(existingAppBubble);
}
} else {
// App bubble is not selected, select it & expand
+ Log.v(TAG, " showOrHideAppBubble, expand and select existing app bubble");
mBubbleData.setSelectedBubble(existingAppBubble);
mBubbleData.setExpanded(true);
}
} else {
// App bubble does not exist, lets add and expand it
+ Log.v(TAG, " showOrHideAppBubble, creating and expanding app bubble");
Bubble b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index d7cb490..4aed7c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.unfold;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -28,11 +30,11 @@
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
+import dagger.Lazy;
+
import java.util.List;
import java.util.Optional;
-import dagger.Lazy;
-
/**
* Manages fold/unfold animations of tasks on foldable devices.
* When folding or unfolding a foldable device we play animations that
@@ -228,7 +230,8 @@
}
private void maybeResetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) {
- if (!mIsInStageChange) {
+ // TODO(b/311084698): the windowing mode check is added here as a work around.
+ if (!mIsInStageChange || taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
// No need to resetTask if there is no ongoing state change.
return;
}
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 41f8204..aabc1cf 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
@@ -421,9 +421,9 @@
}
moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
- if (!mHasLongClicked) {
+ if (!mHasLongClicked && id != R.id.maximize_window) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- decoration.closeMaximizeMenu();
+ decoration.closeMaximizeMenuIfNeeded(e);
}
final long eventDuration = e.getEventTime() - e.getDownTime();
@@ -643,7 +643,7 @@
handleCaptionThroughStatusBar(ev, relevantDecor);
}
}
- handleEventOutsideFocusedCaption(ev, relevantDecor);
+ handleEventOutsideCaption(ev, relevantDecor);
// Prevent status bar from reacting to a caption drag.
if (DesktopModeStatus.isEnabled()) {
if (mTransitionDragActive) {
@@ -652,11 +652,17 @@
}
}
- // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
- private void handleEventOutsideFocusedCaption(MotionEvent ev,
+ /**
+ * If an UP/CANCEL action is received outside of the caption bounds, close the handle and
+ * maximize the menu.
+ *
+ * @param relevantDecor the window decoration of the focused task's caption. This method only
+ * handles motion events outside this caption's bounds.
+ */
+ private void handleEventOutsideCaption(MotionEvent ev,
DesktopModeWindowDecoration relevantDecor) {
// Returns if event occurs within caption
- if (relevantDecor == null || relevantDecor.checkTouchEventInCaptionHandle(ev)) {
+ if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) {
return;
}
@@ -692,7 +698,7 @@
}
if (dragFromStatusBarAllowed
- && relevantDecor.checkTouchEventInCaptionHandle(ev)) {
+ && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) {
mTransitionDragActive = true;
}
}
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 5f77192..53f806c 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
@@ -612,8 +612,7 @@
void closeMaximizeMenuIfNeeded(MotionEvent ev) {
if (!isMaximizeMenuActive()) return;
- final PointF inputPoint = offsetCaptionLocation(ev);
- if (!mMaximizeMenu.isValidMenuInput(inputPoint)) {
+ if (!mMaximizeMenu.isValidMenuInput(ev)) {
closeMaximizeMenu();
}
}
@@ -639,20 +638,34 @@
}
/**
- * Checks if motion event occurs in the caption handle area. This should be used in cases where
+ * Checks if motion event occurs in the caption handle area of a focused caption (the caption on
+ * a task in fullscreen or in multi-windowing mode). This should be used in cases where
* onTouchListener will not work (i.e. when caption is in status bar area).
*
* @param ev the {@link MotionEvent} to check
- * @return {@code true} if event is inside the specified view, {@code false} if not
+ * @return {@code true} if event is inside caption handle view, {@code false} if not
*/
- boolean checkTouchEventInCaptionHandle(MotionEvent ev) {
+ boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
if (isHandleMenuActive() || !(mWindowDecorViewHolder
instanceof DesktopModeFocusedWindowDecorationViewHolder)) {
return false;
}
+
+ return checkTouchEventInCaption(ev);
+ }
+
+ /**
+ * Checks if touch event occurs in caption.
+ *
+ * @param ev the {@link MotionEvent} to check
+ * @return {@code true} if event is inside caption view, {@code false} if not
+ */
+ boolean checkTouchEventInCaption(MotionEvent ev) {
final PointF inputPoint = offsetCaptionLocation(ev);
- return ((DesktopModeFocusedWindowDecorationViewHolder) mWindowDecorViewHolder)
- .pointInCaption(inputPoint, mResult.mCaptionX);
+ return inputPoint.x >= mResult.mCaptionX
+ && inputPoint.x <= mResult.mCaptionX + mResult.mCaptionWidth
+ && inputPoint.y >= 0
+ && inputPoint.y <= mResult.mCaptionHeight;
}
/**
@@ -668,7 +681,7 @@
// Click if point in caption handle view
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
final View handle = caption.findViewById(R.id.caption_handle);
- if (checkTouchEventInCaptionHandle(ev)) {
+ if (checkTouchEventInFocusedCaptionHandle(ev)) {
mOnCaptionButtonClickListener.onClick(handle);
}
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 921708f..794b357 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -22,10 +22,10 @@
import android.graphics.PixelFormat
import android.graphics.PointF
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.SurfaceControlViewHost
-import android.view.View
import android.view.View.OnClickListener
import android.view.WindowManager
import android.view.WindowlessWindowManager
@@ -62,6 +62,8 @@
private val cornerRadius = loadDimensionPixelSize(
R.dimen.desktop_mode_maximize_menu_corner_radius
).toFloat()
+ private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
+ private val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
/** Position the menu relative to the caption's position. */
fun positionMenu(position: PointF, t: Transaction) {
@@ -95,8 +97,6 @@
.setName("Maximize Menu")
.setContainerLayer()
.build()
- val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
- val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
val lp = WindowManager.LayoutParams(
menuWidth,
menuHeight,
@@ -160,14 +160,11 @@
*
* @param inputPoint the input to compare against.
*/
- fun isValidMenuInput(inputPoint: PointF): Boolean {
- val menuView = maximizeMenu?.mWindowViewHost?.view ?: return true
- return !viewsLaidOut() || pointInView(menuView, inputPoint.x - menuPosition.x,
- inputPoint.y - menuPosition.y)
- }
-
- private fun pointInView(v: View, x: Float, y: Float): Boolean {
- return v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+ fun isValidMenuInput(ev: MotionEvent): Boolean {
+ val x = ev.rawX
+ val y = ev.rawY
+ return !viewsLaidOut() || (menuPosition.x <= x && menuPosition.x + menuWidth >= x &&
+ menuPosition.y <= y && menuPosition.y + menuHeight >= y)
}
/**
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 6a9258c..afe837e 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
@@ -279,11 +279,12 @@
}
outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
- final int captionWidth = params.mCaptionWidthId != Resources.ID_NULL
+ outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL
? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
- outResult.mCaptionX = (outResult.mWidth - captionWidth) / 2;
+ outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
- startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
+ startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth,
+ outResult.mCaptionHeight)
.setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */)
.setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
.show(mCaptionContainerSurface);
@@ -356,7 +357,7 @@
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
+ new WindowManager.LayoutParams(outResult.mCaptionWidth, outResult.mCaptionHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -578,6 +579,7 @@
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
int mCaptionHeight;
+ int mCaptionWidth;
int mCaptionX;
int mWidth;
int mHeight;
@@ -587,6 +589,7 @@
mWidth = 0;
mHeight = 0;
mCaptionHeight = 0;
+ mCaptionWidth = 0;
mCaptionX = 0;
mRootView = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 5f77022..6dcae27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -5,7 +5,6 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.res.ColorStateList
import android.graphics.Color
-import android.graphics.PointF
import android.view.View
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import android.widget.ImageButton
@@ -47,17 +46,6 @@
animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
}
- /**
- * Returns true if input point is in the caption's view.
- * @param inputPoint the input point relative to the task in full "focus" (i.e. fullscreen).
- */
- fun pointInCaption(inputPoint: PointF, captionX: Int): Boolean {
- return inputPoint.x >= captionX &&
- inputPoint.x <= captionX + captionView.width &&
- inputPoint.y >= 0 &&
- inputPoint.y <= captionView.height
- }
-
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
return if (shouldUseLightCaptionColors(taskInfo)) {
context.getColor(R.color.desktop_mode_caption_handle_bar_light)
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 991fe41..c292b502 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -7,19 +7,14 @@
default_applicable_licenses: ["frameworks_base_license"],
}
-android_app {
- name: "CredentialManager",
- defaults: ["platform_app_defaults"],
- certificate: "platform",
+android_library {
+ name: "CredentialManager-handheld",
+
+ platform_apis: true,
+
srcs: ["src/**/*.kt"],
resource_dirs: ["res"],
- dex_preopt: {
- profile_guided: true,
- //TODO: b/312357299 - Update baseline profile
- profile: "profile.txt.prof",
- },
-
static_libs: [
"CredentialManagerShared",
"PlatformComposeCore",
@@ -42,6 +37,23 @@
"androidx.recyclerview_recyclerview",
"kotlinx-coroutines-core",
],
+}
+
+android_app {
+ name: "CredentialManager",
+ defaults: ["platform_app_defaults"],
+ certificate: "platform",
+
+ dex_preopt: {
+ profile_guided: true,
+ //TODO: b/312357299 - Update baseline profile
+ profile: "profile.txt.prof",
+ },
+
+ // Do not add new dependencies here. Add to CredentialManager-handheld instead.
+ static_libs: [
+ "CredentialManager-handheld",
+ ],
platform_apis: true,
privileged: true,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index c409ba6..f8ffc9e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -34,6 +34,7 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.compose.theme.PlatformTheme
import com.android.credentialmanager.common.Constants
import com.android.credentialmanager.common.DialogState
import com.android.credentialmanager.common.ProviderActivityResult
@@ -43,7 +44,6 @@
import com.android.credentialmanager.createflow.hasContentToDisplay
import com.android.credentialmanager.getflow.GetCredentialScreen
import com.android.credentialmanager.getflow.hasContentToDisplay
-import com.android.credentialmanager.ui.theme.PlatformTheme
@ExperimentalMaterialApi
class CredentialSelectorActivity : ComponentActivity() {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index db69b8b..d319e4c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -24,11 +24,11 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.rememberSystemUiController
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
import com.android.credentialmanager.ui.theme.EntryShape
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import kotlinx.coroutines.launch
@@ -54,7 +54,7 @@
setBottomSheetSystemBarsColor(sysUiController)
}
ModalBottomSheetLayout(
- sheetBackgroundColor = LocalAndroidColorScheme.current.colorSurfaceBright,
+ sheetBackgroundColor = LocalAndroidColorScheme.current.surfaceBright,
modifier = Modifier.background(Color.Transparent),
sheetState = state,
sheetContent = sheetContent,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index 3976f9a..bdfe399 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -30,8 +30,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.ui.theme.Shapes
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
/**
* Container card for the whole sheet.
@@ -50,7 +50,7 @@
modifier = modifier.fillMaxWidth().wrapContentHeight(),
border = null,
colors = CardDefaults.cardColors(
- containerColor = LocalAndroidColorScheme.current.colorSurfaceBright,
+ containerColor = LocalAndroidColorScheme.current.surfaceBright,
),
) {
if (topAppBar != null) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 1c394ec..a6253b8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -56,9 +56,9 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.R
import com.android.credentialmanager.ui.theme.EntryShape
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import com.android.credentialmanager.ui.theme.Shapes
@Composable
@@ -168,7 +168,7 @@
// Decorative purpose only.
contentDescription = null,
modifier = Modifier.size(24.dp),
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
)
}
}
@@ -182,7 +182,7 @@
Icon(
modifier = iconSize,
bitmap = iconImageBitmap,
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -206,7 +206,7 @@
Icon(
modifier = iconSize,
imageVector = iconImageVector,
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -218,7 +218,7 @@
Icon(
modifier = iconSize,
painter = iconPainter,
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -229,9 +229,9 @@
},
border = null,
colors = SuggestionChipDefaults.suggestionChipColors(
- containerColor = LocalAndroidColorScheme.current.colorSurfaceContainerHigh,
- labelColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
- iconContentColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ containerColor = LocalAndroidColorScheme.current.surfaceContainerHigh,
+ labelColor = LocalAndroidColorScheme.current.onSurfaceVariant,
+ iconContentColor = LocalAndroidColorScheme.current.onSurfaceVariant,
),
)
}
@@ -294,7 +294,7 @@
Icon(
modifier = Modifier.size(24.dp),
painter = leadingIconPainter,
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -353,7 +353,7 @@
R.string.accessibility_back_arrow_button
),
modifier = Modifier.size(24.dp).autoMirrored(),
- tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
index 2df0c7a9..342af3b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
@@ -24,20 +24,20 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import com.android.compose.theme.LocalAndroidColorScheme
@Composable
fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) {
InternalSectionHeader(
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ color = LocalAndroidColorScheme.current.onSurfaceVariant,
applyTopPadding = !isFirstSection
)
}
@Composable
fun MoreAboutPasskeySectionHeader(text: String) {
- InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurface)
+ InternalSectionHeader(text, LocalAndroidColorScheme.current.onSurface)
}
@Composable
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
index a619523..b4075f1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
@@ -19,8 +19,8 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.android.compose.SystemUiController
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
@Composable
fun setTransparentSystemBarsColor(sysUiController: SystemUiController) {
@@ -34,7 +34,7 @@
darkIcons = false
)
sysUiController.setNavigationBarColor(
- color = LocalAndroidColorScheme.current.colorSurfaceBright,
+ color = LocalAndroidColorScheme.current.surfaceBright,
darkIcons = false
)
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 6b46636..9111e61 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -25,7 +25,7 @@
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import com.android.compose.theme.LocalAndroidColorScheme
/**
* The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X".
@@ -37,7 +37,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurface,
+ color = LocalAndroidColorScheme.current.onSurface,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.headlineSmall,
)
@@ -51,7 +51,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ color = LocalAndroidColorScheme.current.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
)
}
@@ -69,7 +69,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ color = LocalAndroidColorScheme.current.onSurfaceVariant,
style = MaterialTheme.typography.bodySmall,
overflow = TextOverflow.Ellipsis,
maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
@@ -85,7 +85,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurface,
+ color = LocalAndroidColorScheme.current.onSurface,
style = MaterialTheme.typography.titleLarge,
)
}
@@ -103,7 +103,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.colorOnSurface,
+ color = LocalAndroidColorScheme.current.onSurface,
style = MaterialTheme.typography.titleSmall,
overflow = TextOverflow.Ellipsis,
maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
@@ -159,7 +159,7 @@
modifier = modifier.wrapContentSize(),
text = text,
textAlign = TextAlign.Center,
- color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ color = LocalAndroidColorScheme.current.onSurfaceVariant,
style = MaterialTheme.typography.labelLarge,
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 14a9165..f261d1f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -46,6 +46,7 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
import com.android.credentialmanager.model.EntryInfo
@@ -70,7 +71,6 @@
import com.android.credentialmanager.logging.CreateCredentialEvent
import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.creation.RemoteInfo
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import com.android.internal.logging.UiEventLogger.UiEventEnum
@Composable
@@ -460,7 +460,7 @@
item {
Divider(
thickness = 1.dp,
- color = LocalAndroidColorScheme.current.colorOutlineVariant,
+ color = LocalAndroidColorScheme.current.outlineVariant,
modifier = Modifier.padding(vertical = 16.dp)
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index a291f59..458a99a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -26,7 +26,7 @@
import com.android.internal.util.Preconditions
data class GetCredentialUiState(
- val isRequestForAllOptions: Boolean,
+ val isRequestForAllOptions: Boolean,
val providerInfoList: List<ProviderInfo>,
val requestDisplayInfo: RequestDisplayInfo,
val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
@@ -165,7 +165,7 @@
)
}
-private fun toActiveEntry(
+fun toActiveEntry(
providerDisplayInfo: ProviderDisplayInfo,
): EntryInfo? {
val sortedUserNameToCredentialEntryList =
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
deleted file mode 100644
index a33904d..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme
-
-import android.annotation.ColorInt
-import android.content.Context
-import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.graphics.Color
-import com.android.internal.R
-
-/** File copied from PlatformComposeCore. */
-
-/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
-val LocalAndroidColorScheme =
- staticCompositionLocalOf<AndroidColorScheme> {
- throw IllegalStateException(
- "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
- "Composable surrounded by a PlatformTheme {}."
- )
- }
-
-/**
- * The Android color scheme.
- *
- * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
- * most of the colors in this class will be removed in favor of their M3 counterpart.
- */
-class AndroidColorScheme internal constructor(context: Context) {
- val colorSurfaceBright = getColor(context, R.attr.materialColorSurfaceBright)
- val colorSurfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh)
- val colorOutlineVariant = getColor(context, R.attr.materialColorOutlineVariant)
- val colorOnSurface = getColor(context, R.attr.materialColorOnSurface)
- val colorOnSurfaceVariant = getColor(context, R.attr.materialColorOnSurfaceVariant)
-
- companion object {
- fun getColor(context: Context, attr: Int): Color {
- val ta = context.obtainStyledAttributes(intArrayOf(attr))
- @ColorInt val color = ta.getColor(0, 0)
- ta.recycle()
- return Color(color)
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
deleted file mode 100644
index c923845..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
+++ /dev/null
@@ -1,30 +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.credentialmanager.ui.theme
-
-import android.annotation.AttrRes
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-
-/** Read the [Color] from the given [attribute]. */
-@Composable
-@ReadOnlyComposable
-fun colorAttr(@AttrRes attribute: Int): Color {
- return AndroidColorScheme.getColor(LocalContext.current, attribute)
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt
deleted file mode 100644
index 2f1ce68..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt
+++ /dev/null
@@ -1,67 +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.credentialmanager.ui.theme
-
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.dynamicDarkColorScheme
-import androidx.compose.material3.dynamicLightColorScheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import com.android.credentialmanager.ui.theme.typography.TypeScaleTokens
-import com.android.credentialmanager.ui.theme.typography.TypefaceNames
-import com.android.credentialmanager.ui.theme.typography.TypefaceTokens
-import com.android.credentialmanager.ui.theme.typography.TypographyTokens
-import com.android.credentialmanager.ui.theme.typography.platformTypography
-
-/** File copied from PlatformComposeCore. */
-
-/**
- * The Material 3 theme that should wrap all Platform Composables.
- *
- * TODO(b/280685309): Merge with the official SysUI platform theme.
- */
-@Composable
-fun PlatformTheme(
- isDarkTheme: Boolean = isSystemInDarkTheme(),
- content: @Composable () -> Unit,
-) {
- val context = LocalContext.current
-
- val colorScheme =
- if (isDarkTheme) {
- dynamicDarkColorScheme(context)
- } else {
- dynamicLightColorScheme(context)
- }
- val androidColorScheme = AndroidColorScheme(context)
- val typefaceNames = remember(context) { TypefaceNames.get(context) }
- val typography =
- remember(typefaceNames) {
- platformTypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames))))
- }
-
- MaterialTheme(colorScheme, typography = typography) {
- CompositionLocalProvider(
- LocalAndroidColorScheme provides androidColorScheme,
- ) {
- content()
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt
deleted file mode 100644
index 984e4f1..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt
+++ /dev/null
@@ -1,48 +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.credentialmanager.ui.theme.typography
-
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Typography
-
-/** File copied from PlatformComposeCore. */
-
-/**
- * The typography for Platform Compose code.
- *
- * Do not use directly and call [MaterialTheme.typography] instead to access the different text
- * styles.
- */
-internal fun platformTypography(typographyTokens: TypographyTokens): Typography {
- return Typography(
- displayLarge = typographyTokens.displayLarge,
- displayMedium = typographyTokens.displayMedium,
- displaySmall = typographyTokens.displaySmall,
- headlineLarge = typographyTokens.headlineLarge,
- headlineMedium = typographyTokens.headlineMedium,
- headlineSmall = typographyTokens.headlineSmall,
- titleLarge = typographyTokens.titleLarge,
- titleMedium = typographyTokens.titleMedium,
- titleSmall = typographyTokens.titleSmall,
- bodyLarge = typographyTokens.bodyLarge,
- bodyMedium = typographyTokens.bodyMedium,
- bodySmall = typographyTokens.bodySmall,
- labelLarge = typographyTokens.labelLarge,
- labelMedium = typographyTokens.labelMedium,
- labelSmall = typographyTokens.labelSmall,
- )
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt
deleted file mode 100644
index b2dd207..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt
+++ /dev/null
@@ -1,98 +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.credentialmanager.ui.theme.typography
-
-import androidx.compose.ui.unit.sp
-
-/** File copied from PlatformComposeCore. */
-internal class TypeScaleTokens(typefaceTokens: TypefaceTokens) {
- val bodyLargeFont = typefaceTokens.plain
- val bodyLargeLineHeight = 24.0.sp
- val bodyLargeSize = 16.sp
- val bodyLargeTracking = 0.0.sp
- val bodyLargeWeight = TypefaceTokens.WeightRegular
- val bodyMediumFont = typefaceTokens.plain
- val bodyMediumLineHeight = 20.0.sp
- val bodyMediumSize = 14.sp
- val bodyMediumTracking = 0.0.sp
- val bodyMediumWeight = TypefaceTokens.WeightRegular
- val bodySmallFont = typefaceTokens.plain
- val bodySmallLineHeight = 16.0.sp
- val bodySmallSize = 12.sp
- val bodySmallTracking = 0.1.sp
- val bodySmallWeight = TypefaceTokens.WeightRegular
- val displayLargeFont = typefaceTokens.brand
- val displayLargeLineHeight = 64.0.sp
- val displayLargeSize = 57.sp
- val displayLargeTracking = 0.0.sp
- val displayLargeWeight = TypefaceTokens.WeightRegular
- val displayMediumFont = typefaceTokens.brand
- val displayMediumLineHeight = 52.0.sp
- val displayMediumSize = 45.sp
- val displayMediumTracking = 0.0.sp
- val displayMediumWeight = TypefaceTokens.WeightRegular
- val displaySmallFont = typefaceTokens.brand
- val displaySmallLineHeight = 44.0.sp
- val displaySmallSize = 36.sp
- val displaySmallTracking = 0.0.sp
- val displaySmallWeight = TypefaceTokens.WeightRegular
- val headlineLargeFont = typefaceTokens.brand
- val headlineLargeLineHeight = 40.0.sp
- val headlineLargeSize = 32.sp
- val headlineLargeTracking = 0.0.sp
- val headlineLargeWeight = TypefaceTokens.WeightRegular
- val headlineMediumFont = typefaceTokens.brand
- val headlineMediumLineHeight = 36.0.sp
- val headlineMediumSize = 28.sp
- val headlineMediumTracking = 0.0.sp
- val headlineMediumWeight = TypefaceTokens.WeightRegular
- val headlineSmallFont = typefaceTokens.brand
- val headlineSmallLineHeight = 32.0.sp
- val headlineSmallSize = 24.sp
- val headlineSmallTracking = 0.0.sp
- val headlineSmallWeight = TypefaceTokens.WeightRegular
- val labelLargeFont = typefaceTokens.plain
- val labelLargeLineHeight = 20.0.sp
- val labelLargeSize = 14.sp
- val labelLargeTracking = 0.0.sp
- val labelLargeWeight = TypefaceTokens.WeightMedium
- val labelMediumFont = typefaceTokens.plain
- val labelMediumLineHeight = 16.0.sp
- val labelMediumSize = 12.sp
- val labelMediumTracking = 0.1.sp
- val labelMediumWeight = TypefaceTokens.WeightMedium
- val labelSmallFont = typefaceTokens.plain
- val labelSmallLineHeight = 16.0.sp
- val labelSmallSize = 11.sp
- val labelSmallTracking = 0.1.sp
- val labelSmallWeight = TypefaceTokens.WeightMedium
- val titleLargeFont = typefaceTokens.brand
- val titleLargeLineHeight = 28.0.sp
- val titleLargeSize = 22.sp
- val titleLargeTracking = 0.0.sp
- val titleLargeWeight = TypefaceTokens.WeightRegular
- val titleMediumFont = typefaceTokens.plain
- val titleMediumLineHeight = 24.0.sp
- val titleMediumSize = 16.sp
- val titleMediumTracking = 0.0.sp
- val titleMediumWeight = TypefaceTokens.WeightMedium
- val titleSmallFont = typefaceTokens.plain
- val titleSmallLineHeight = 20.0.sp
- val titleSmallSize = 14.sp
- val titleSmallTracking = 0.0.sp
- val titleSmallWeight = TypefaceTokens.WeightMedium
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt
deleted file mode 100644
index 3cc761f..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt
+++ /dev/null
@@ -1,75 +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.
- */
-
-@file:OptIn(ExperimentalTextApi::class)
-
-package com.android.credentialmanager.ui.theme.typography
-
-import android.content.Context
-import androidx.compose.ui.text.ExperimentalTextApi
-import androidx.compose.ui.text.font.DeviceFontFamilyName
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-
-/** File copied from PlatformComposeCore. */
-internal class TypefaceTokens(typefaceNames: TypefaceNames) {
- companion object {
- val WeightMedium = FontWeight.Medium
- val WeightRegular = FontWeight.Normal
- }
-
- private val brandFont = DeviceFontFamilyName(typefaceNames.brand)
- private val plainFont = DeviceFontFamilyName(typefaceNames.plain)
-
- val brand =
- FontFamily(
- Font(brandFont, weight = WeightMedium),
- Font(brandFont, weight = WeightRegular),
- )
- val plain =
- FontFamily(
- Font(plainFont, weight = WeightMedium),
- Font(plainFont, weight = WeightRegular),
- )
-}
-
-internal data class TypefaceNames
-private constructor(
- val brand: String,
- val plain: String,
-) {
- private enum class Config(val configName: String, val default: String) {
- Brand("config_headlineFontFamily", "sans-serif"),
- Plain("config_bodyFontFamily", "sans-serif"),
- }
-
- companion object {
- fun get(context: Context): TypefaceNames {
- return TypefaceNames(
- brand = getTypefaceName(context, Config.Brand),
- plain = getTypefaceName(context, Config.Plain),
- )
- }
-
- private fun getTypefaceName(context: Context, config: Config): String {
- return context
- .getString(context.resources.getIdentifier(config.configName, "string", "android"))
- .takeIf { it.isNotEmpty() }
- ?: config.default
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt
deleted file mode 100644
index aadab92..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt
+++ /dev/null
@@ -1,143 +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.credentialmanager.ui.theme.typography
-
-import androidx.compose.ui.text.TextStyle
-
-/** File copied from PlatformComposeCore. */
-internal class TypographyTokens(typeScaleTokens: TypeScaleTokens) {
- val bodyLarge =
- TextStyle(
- fontFamily = typeScaleTokens.bodyLargeFont,
- fontWeight = typeScaleTokens.bodyLargeWeight,
- fontSize = typeScaleTokens.bodyLargeSize,
- lineHeight = typeScaleTokens.bodyLargeLineHeight,
- letterSpacing = typeScaleTokens.bodyLargeTracking,
- )
- val bodyMedium =
- TextStyle(
- fontFamily = typeScaleTokens.bodyMediumFont,
- fontWeight = typeScaleTokens.bodyMediumWeight,
- fontSize = typeScaleTokens.bodyMediumSize,
- lineHeight = typeScaleTokens.bodyMediumLineHeight,
- letterSpacing = typeScaleTokens.bodyMediumTracking,
- )
- val bodySmall =
- TextStyle(
- fontFamily = typeScaleTokens.bodySmallFont,
- fontWeight = typeScaleTokens.bodySmallWeight,
- fontSize = typeScaleTokens.bodySmallSize,
- lineHeight = typeScaleTokens.bodySmallLineHeight,
- letterSpacing = typeScaleTokens.bodySmallTracking,
- )
- val displayLarge =
- TextStyle(
- fontFamily = typeScaleTokens.displayLargeFont,
- fontWeight = typeScaleTokens.displayLargeWeight,
- fontSize = typeScaleTokens.displayLargeSize,
- lineHeight = typeScaleTokens.displayLargeLineHeight,
- letterSpacing = typeScaleTokens.displayLargeTracking,
- )
- val displayMedium =
- TextStyle(
- fontFamily = typeScaleTokens.displayMediumFont,
- fontWeight = typeScaleTokens.displayMediumWeight,
- fontSize = typeScaleTokens.displayMediumSize,
- lineHeight = typeScaleTokens.displayMediumLineHeight,
- letterSpacing = typeScaleTokens.displayMediumTracking,
- )
- val displaySmall =
- TextStyle(
- fontFamily = typeScaleTokens.displaySmallFont,
- fontWeight = typeScaleTokens.displaySmallWeight,
- fontSize = typeScaleTokens.displaySmallSize,
- lineHeight = typeScaleTokens.displaySmallLineHeight,
- letterSpacing = typeScaleTokens.displaySmallTracking,
- )
- val headlineLarge =
- TextStyle(
- fontFamily = typeScaleTokens.headlineLargeFont,
- fontWeight = typeScaleTokens.headlineLargeWeight,
- fontSize = typeScaleTokens.headlineLargeSize,
- lineHeight = typeScaleTokens.headlineLargeLineHeight,
- letterSpacing = typeScaleTokens.headlineLargeTracking,
- )
- val headlineMedium =
- TextStyle(
- fontFamily = typeScaleTokens.headlineMediumFont,
- fontWeight = typeScaleTokens.headlineMediumWeight,
- fontSize = typeScaleTokens.headlineMediumSize,
- lineHeight = typeScaleTokens.headlineMediumLineHeight,
- letterSpacing = typeScaleTokens.headlineMediumTracking,
- )
- val headlineSmall =
- TextStyle(
- fontFamily = typeScaleTokens.headlineSmallFont,
- fontWeight = typeScaleTokens.headlineSmallWeight,
- fontSize = typeScaleTokens.headlineSmallSize,
- lineHeight = typeScaleTokens.headlineSmallLineHeight,
- letterSpacing = typeScaleTokens.headlineSmallTracking,
- )
- val labelLarge =
- TextStyle(
- fontFamily = typeScaleTokens.labelLargeFont,
- fontWeight = typeScaleTokens.labelLargeWeight,
- fontSize = typeScaleTokens.labelLargeSize,
- lineHeight = typeScaleTokens.labelLargeLineHeight,
- letterSpacing = typeScaleTokens.labelLargeTracking,
- )
- val labelMedium =
- TextStyle(
- fontFamily = typeScaleTokens.labelMediumFont,
- fontWeight = typeScaleTokens.labelMediumWeight,
- fontSize = typeScaleTokens.labelMediumSize,
- lineHeight = typeScaleTokens.labelMediumLineHeight,
- letterSpacing = typeScaleTokens.labelMediumTracking,
- )
- val labelSmall =
- TextStyle(
- fontFamily = typeScaleTokens.labelSmallFont,
- fontWeight = typeScaleTokens.labelSmallWeight,
- fontSize = typeScaleTokens.labelSmallSize,
- lineHeight = typeScaleTokens.labelSmallLineHeight,
- letterSpacing = typeScaleTokens.labelSmallTracking,
- )
- val titleLarge =
- TextStyle(
- fontFamily = typeScaleTokens.titleLargeFont,
- fontWeight = typeScaleTokens.titleLargeWeight,
- fontSize = typeScaleTokens.titleLargeSize,
- lineHeight = typeScaleTokens.titleLargeLineHeight,
- letterSpacing = typeScaleTokens.titleLargeTracking,
- )
- val titleMedium =
- TextStyle(
- fontFamily = typeScaleTokens.titleMediumFont,
- fontWeight = typeScaleTokens.titleMediumWeight,
- fontSize = typeScaleTokens.titleMediumSize,
- lineHeight = typeScaleTokens.titleMediumLineHeight,
- letterSpacing = typeScaleTokens.titleMediumTracking,
- )
- val titleSmall =
- TextStyle(
- fontFamily = typeScaleTokens.titleSmallFont,
- fontWeight = typeScaleTokens.titleSmallWeight,
- fontSize = typeScaleTokens.titleSmallSize,
- lineHeight = typeScaleTokens.titleSmallLineHeight,
- letterSpacing = typeScaleTokens.titleSmallTracking,
- )
-}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index d26c5ff..16de478 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -231,6 +231,7 @@
Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
Settings.Global.ENHANCED_4G_MODE_ENABLED,
+ Settings.Global.ENABLE_16K_PAGES, // Added for 16K developer option
Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
Settings.Global.ERROR_LOGCAT_PREFIX,
Settings.Global.EUICC_PROVISIONED,
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index 6c75b434..0df9bac 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -37,6 +37,7 @@
"androidx.preference_preference",
"androidx.viewpager_viewpager",
"SettingsLibDisplayUtils",
+ "SettingsLibSettingsTheme",
"com_android_a11y_menu_flags_lib",
],
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index ca84265..648cc3b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -40,7 +40,7 @@
android:exported="true"
android:label="@string/accessibility_menu_settings_name"
android:launchMode="singleTop"
- android:theme="@style/AccessibilityMenuSettings">
+ android:theme="@style/Theme.SettingsBase">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index eadcd7c..f5db6a4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -8,10 +8,3 @@
description: "Hides the AccessibilityMenuService UI before taking action instead of after."
bug: "292020123"
}
-
-flag {
- name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
- namespace: "accessibility"
- description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
- bug: "298467628"
-}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
index 1f57654..81b3152 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
@@ -16,10 +16,6 @@
-->
<resources>
- <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.DayNight">
- <item name="android:windowLightStatusBar">false</item>
- </style>
-
<!--Adds the theme to support SnackBar component and user configurable theme. -->
<style name="ServiceTheme" parent="android:Theme.DeviceDefault.DayNight">
<item name="android:colorControlNormal">@color/colorControlNormal</item>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
index a2508cd..4169155 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
@@ -16,11 +16,6 @@
-->
<resources>
- <!--The theme is for preference CollapsingToolbarBaseActivity settings-->
- <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.DayNight">
- <item name="android:windowLightStatusBar">true</item>
- </style>
-
<!--Adds the theme to support SnackBar component and user configurable theme. -->
<style name="ServiceTheme" parent="android:Theme.DeviceDefault.Light">
<item name="android:colorControlNormal">@color/colorControlNormal</item>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index bf51e23..ab8f97a 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -35,7 +35,6 @@
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
/**
@@ -56,28 +55,17 @@
ActionBar actionBar = getActionBar();
actionBar.setDisplayShowCustomEnabled(true);
-
- if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
- actionBar.setDisplayHomeAsUpEnabled(true);
- }
+ actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setCustomView(R.layout.preferences_action_bar);
((TextView) findViewById(R.id.action_bar_title)).setText(
getResources().getString(R.string.accessibility_menu_settings_name)
);
- actionBar.setDisplayOptions(
- ActionBar.DISPLAY_TITLE_MULTIPLE_LINES
- | ActionBar.DISPLAY_SHOW_TITLE
- | ActionBar.DISPLAY_HOME_AS_UP);
}
@Override
public boolean onNavigateUp() {
- if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
- mCallback.onBackInvoked();
- return true;
- } else {
- return false;
- }
+ mCallback.onBackInvoked();
+ return true;
}
/**
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index c4f372c..c2cf6e1 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -28,7 +28,6 @@
import android.widget.TextView;
import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
@@ -81,10 +80,8 @@
if (convertView == null) {
convertView = mInflater.inflate(R.layout.grid_item, parent, false);
- if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
- configureShortcutSize(convertView,
- A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService));
- }
+ configureShortcutSize(convertView,
+ A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService));
}
A11yMenuShortcut shortcutItem = (A11yMenuShortcut) getItem(position);
@@ -147,15 +144,6 @@
ImageButton shortcutIconButton = convertView.findViewById(R.id.shortcutIconBtn);
TextView shortcutLabel = convertView.findViewById(R.id.shortcutLabel);
- if (!Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
- if (A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService)) {
- ViewGroup.LayoutParams params = shortcutIconButton.getLayoutParams();
- params.width = (int) (params.width * LARGE_BUTTON_SCALE);
- params.height = (int) (params.height * LARGE_BUTTON_SCALE);
- shortcutLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, mLargeTextSize);
- }
- }
-
if (shortcutItem.getId() == A11yMenuShortcut.ShortcutId.UNSPECIFIED_ID_VALUE.ordinal()) {
// Sets empty shortcut icon and label when the shortcut is ADD_ITEM.
shortcutIconButton.setImageResource(android.R.color.transparent);
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5fbffbe..c23a49c 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -208,6 +208,13 @@
}
flag {
+ name: "compose_bouncer"
+ namespace: "systemui"
+ description: "Use the new compose bouncer in SystemUI"
+ bug: "310005730"
+}
+
+flag {
name: "media_in_scene_container"
namespace: "systemui"
description: "Enable media in the scene container framework"
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index fd04b5ee0..f4ffb3c 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -22,6 +22,8 @@
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -85,6 +87,12 @@
throwComposeUnavailableError()
}
+ override fun createBouncer(
+ context: Context,
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerDialogFactory,
+ ): View = throwComposeUnavailableError()
+
private fun throwComposeUnavailableError(): Nothing {
error(
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index d31547b..43745f9 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -28,6 +28,9 @@
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
import com.android.compose.ui.platform.DensityAwareComposeView
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.composable.BouncerContent
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
@@ -171,4 +174,14 @@
private fun Int.toDp(context: Context): Dp {
return (this.toFloat() / context.resources.displayMetrics.density).dp
}
+
+ override fun createBouncer(
+ context: Context,
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerDialogFactory,
+ ): View {
+ return ComposeView(context).apply {
+ setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
index 1860c9f..2b1268e 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
@@ -16,34 +16,14 @@
package com.android.systemui.scene
-import android.app.AlertDialog
-import android.content.Context
-import com.android.systemui.bouncer.ui.composable.BouncerDialogFactory
import com.android.systemui.bouncer.ui.composable.BouncerScene
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.Scene
-import com.android.systemui.statusbar.phone.SystemUIDialog
import dagger.Binds
import dagger.Module
-import dagger.Provides
import dagger.multibindings.IntoSet
@Module
interface BouncerSceneModule {
@Binds @IntoSet fun bouncerScene(scene: BouncerScene): Scene
-
- companion object {
-
- @Provides
- @SysUISingleton
- fun bouncerSceneDialogFactory(@Application context: Context): BouncerDialogFactory {
- return object : BouncerDialogFactory {
- override fun invoke(): AlertDialog {
- return SystemUIDialog(context)
- }
- }
- }
- }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 6591543..d949396 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -81,6 +81,7 @@
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
@@ -824,10 +825,6 @@
}
}
-interface BouncerDialogFactory {
- operator fun invoke(): AlertDialog
-}
-
/**
* Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of
* the two reaches a stopping point but `0` in the middle of the transition.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index d638ffe..428bc39 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -24,6 +24,7 @@
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Direction
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index d76f0ff..91a4d2e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -88,8 +88,6 @@
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHostState
import com.android.systemui.res.R
@Composable
@@ -576,10 +574,6 @@
AndroidView(
modifier = modifier,
factory = {
- viewModel.mediaHost.expansion = MediaHostState.EXPANDED
- viewModel.mediaHost.showsOnlyActiveMedia = false
- viewModel.mediaHost.falsingProtectionNeeded = false
- viewModel.mediaHost.init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
viewModel.mediaHost.hostView.layoutParams =
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index 74f50d8..27d1eb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -37,11 +37,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
- companion object {
- val TEST_USER_1 = UserHandle.of(1)!!
- val TEST_USER_2 = UserHandle.of(2)!!
- }
+ private val testUser1 = UserHandle.of(1)!!
+ private val testUser2 = UserHandle.of(2)!!
private val testDispatcher = StandardTestDispatcher()
private val scope = TestScope(testDispatcher)
private val settings: FakeSettings = FakeSettings()
@@ -63,7 +61,7 @@
settings.putIntForUser(
ColorCorrectionRepositoryImpl.SETTING_NAME,
1,
- TEST_USER_1.identifier
+ testUser1.identifier
)
underTest =
@@ -72,84 +70,84 @@
settings,
)
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+ underTest.isEnabled(testUser1).launchIn(backgroundScope)
runCurrent()
- val actualValue: Boolean = underTest.isEnabled(TEST_USER_1).first()
+ val actualValue: Boolean = underTest.isEnabled(testUser1).first()
Truth.assertThat(actualValue).isTrue()
}
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+ underTest.isEnabled(testUser1).launchIn(backgroundScope)
settings.putIntForUser(
ColorCorrectionRepositoryImpl.SETTING_NAME,
ColorCorrectionRepositoryImpl.DISABLED,
- TEST_USER_1.identifier
+ testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+ Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse()
settings.putIntForUser(
ColorCorrectionRepositoryImpl.SETTING_NAME,
ColorCorrectionRepositoryImpl.ENABLED,
- TEST_USER_1.identifier
+ testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
+ Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue()
settings.putIntForUser(
ColorCorrectionRepositoryImpl.SETTING_NAME,
ColorCorrectionRepositoryImpl.DISABLED,
- TEST_USER_1.identifier
+ testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+ Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse()
}
@Test
fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
scope.runTest {
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+ underTest.isEnabled(testUser1).launchIn(backgroundScope)
settings.putIntForUser(
ColorCorrectionRepositoryImpl.SETTING_NAME,
ColorCorrectionRepositoryImpl.DISABLED,
- TEST_USER_1.identifier
+ testUser1.identifier
)
- underTest.isEnabled(TEST_USER_2).launchIn(backgroundScope)
+ underTest.isEnabled(testUser2).launchIn(backgroundScope)
settings.putIntForUser(
ColorCorrectionRepositoryImpl.SETTING_NAME,
ColorCorrectionRepositoryImpl.DISABLED,
- TEST_USER_2.identifier
+ testUser2.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
- Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+ Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse()
+ Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse()
settings.putIntForUser(
ColorCorrectionRepositoryImpl.SETTING_NAME,
ColorCorrectionRepositoryImpl.ENABLED,
- TEST_USER_1.identifier
+ testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
- Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+ Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue()
+ Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse()
}
@Test
fun setEnabled() =
scope.runTest {
- val success = underTest.setIsEnabled(true, TEST_USER_1)
+ val success = underTest.setIsEnabled(true, testUser1)
runCurrent()
Truth.assertThat(success).isTrue()
val actualValue =
settings.getIntForUser(
ColorCorrectionRepositoryImpl.SETTING_NAME,
- TEST_USER_1.identifier
+ testUser1.identifier
)
Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.ENABLED)
}
@@ -157,14 +155,14 @@
@Test
fun setDisabled() =
scope.runTest {
- val success = underTest.setIsEnabled(false, TEST_USER_1)
+ val success = underTest.setIsEnabled(false, testUser1)
runCurrent()
Truth.assertThat(success).isTrue()
val actualValue =
settings.getIntForUser(
ColorCorrectionRepositoryImpl.SETTING_NAME,
- TEST_USER_1.identifier
+ testUser1.identifier
)
Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.DISABLED)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 3f05fef..423e124 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -39,6 +39,8 @@
@RunWith(AndroidJUnit4::class)
class ColorInversionRepositoryImplTest : SysuiTestCase() {
+ private val testUser1 = UserHandle.of(1)!!
+ private val testUser2 = UserHandle.of(2)!!
private val testDispatcher = StandardTestDispatcher()
private val scope = TestScope(testDispatcher)
private val settings: FakeSettings = FakeSettings()
@@ -57,7 +59,7 @@
@Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
- settings.putIntForUser(SETTING_NAME, 1, TEST_USER_1.identifier)
+ settings.putIntForUser(SETTING_NAME, 1, testUser1.identifier)
underTest =
ColorInversionRepositoryImpl(
@@ -65,68 +67,68 @@
settings,
)
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+ underTest.isEnabled(testUser1).launchIn(backgroundScope)
runCurrent()
- val actualValue: Boolean = underTest.isEnabled(TEST_USER_1).first()
+ val actualValue: Boolean = underTest.isEnabled(testUser1).first()
assertThat(actualValue).isTrue()
}
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+ underTest.isEnabled(testUser1).launchIn(backgroundScope)
- settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+ assertThat(underTest.isEnabled(testUser1).first()).isFalse()
- settings.putIntForUser(SETTING_NAME, ENABLED, TEST_USER_1.identifier)
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
+ assertThat(underTest.isEnabled(testUser1).first()).isTrue()
- settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+ assertThat(underTest.isEnabled(testUser1).first()).isFalse()
}
@Test
fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
scope.runTest {
- underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
- settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
- underTest.isEnabled(TEST_USER_2).launchIn(backgroundScope)
- settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_2.identifier)
+ underTest.isEnabled(testUser1).launchIn(backgroundScope)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+ underTest.isEnabled(testUser2).launchIn(backgroundScope)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
runCurrent()
- assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
- assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+ assertThat(underTest.isEnabled(testUser1).first()).isFalse()
+ assertThat(underTest.isEnabled(testUser2).first()).isFalse()
- settings.putIntForUser(SETTING_NAME, ENABLED, TEST_USER_1.identifier)
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
- assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+ assertThat(underTest.isEnabled(testUser1).first()).isTrue()
+ assertThat(underTest.isEnabled(testUser2).first()).isFalse()
}
@Test
fun setEnabled() =
scope.runTest {
- val success = underTest.setIsEnabled(true, TEST_USER_1)
+ val success = underTest.setIsEnabled(true, testUser1)
runCurrent()
assertThat(success).isTrue()
- val actualValue = settings.getIntForUser(SETTING_NAME, TEST_USER_1.identifier)
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
assertThat(actualValue).isEqualTo(ENABLED)
}
@Test
fun setDisabled() =
scope.runTest {
- val success = underTest.setIsEnabled(false, TEST_USER_1)
+ val success = underTest.setIsEnabled(false, testUser1)
runCurrent()
assertThat(success).isTrue()
- val actualValue = settings.getIntForUser(SETTING_NAME, TEST_USER_1.identifier)
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
assertThat(actualValue).isEqualTo(DISABLED)
}
@@ -134,7 +136,5 @@
private const val SETTING_NAME = ACCESSIBILITY_DISPLAY_INVERSION_ENABLED
private const val DISABLED = 0
private const val ENABLED = 1
- private val TEST_USER_1 = UserHandle.of(1)!!
- private val TEST_USER_2 = UserHandle.of(2)!!
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 15633d1..4a39799 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -18,6 +18,7 @@
import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.SensorProperties
import android.hardware.biometrics.SensorPropertiesInternal
@@ -119,6 +120,7 @@
title: String = "title",
subtitle: String = "sub",
description: String = "desc",
+ contentView: PromptContentView? = null,
credentialTitle: String? = "cred title",
credentialSubtitle: String? = "cred sub",
credentialDescription: String? = "cred desc",
@@ -128,6 +130,7 @@
info.title = title
info.subtitle = subtitle
info.description = description
+ info.contentView = contentView
credentialTitle?.let { info.deviceCredentialTitle = it }
credentialSubtitle?.let { info.deviceCredentialSubtitle = it }
credentialDescription?.let { info.deviceCredentialDescription = it }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
new file mode 100644
index 0000000..ccf119a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.hardware.biometrics.SensorLocationInternal
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FingerprintPropertyInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fingerprintPropertyInteractor
+ private val repository = kosmos.fingerprintPropertyRepository
+ private val configurationRepository = kosmos.fakeConfigurationRepository
+ private val displayRepository = kosmos.displayRepository
+
+ @Test
+ fun sensorLocation_resolution1f() =
+ testScope.runTest {
+ val currSensorLocation by collectLastValue(underTest.sensorLocation)
+
+ displayRepository.emitDisplayChangeEvent(0)
+ runCurrent()
+ repository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+ sensorLocations =
+ mapOf(
+ Pair("", SensorLocationInternal("", 4, 4, 2)),
+ Pair("otherDisplay", SensorLocationInternal("", 1, 1, 1))
+ )
+ )
+ runCurrent()
+ configurationRepository.setScaleForResolution(1f)
+ runCurrent()
+
+ assertThat(currSensorLocation?.centerX).isEqualTo(4)
+ assertThat(currSensorLocation?.centerY).isEqualTo(4)
+ assertThat(currSensorLocation?.radius).isEqualTo(2)
+ }
+
+ @Test
+ fun sensorLocation_resolution2f() =
+ testScope.runTest {
+ val currSensorLocation by collectLastValue(underTest.sensorLocation)
+
+ displayRepository.emitDisplayChangeEvent(0)
+ runCurrent()
+ repository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+ sensorLocations =
+ mapOf(
+ Pair("", SensorLocationInternal("", 4, 4, 2)),
+ Pair("otherDisplay", SensorLocationInternal("", 1, 1, 1))
+ )
+ )
+ runCurrent()
+ configurationRepository.setScaleForResolution(2f)
+ runCurrent()
+
+ assertThat(currSensorLocation?.centerX).isEqualTo(4 * 2)
+ assertThat(currSensorLocation?.centerY).isEqualTo(4 * 2)
+ assertThat(currSensorLocation?.radius).isEqualTo(2 * 2)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index ee46f76..63f6c20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -16,10 +16,10 @@
package com.android.systemui.bouncer.domain.interactor
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.testing.TestableResources
import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -60,7 +60,7 @@
@SmallTest
@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var repository: KeyguardBouncerRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 744b65f..cd83c07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -39,6 +40,9 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -69,9 +73,9 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- testScope = TestScope()
+ testScope = TestScope(StandardTestDispatcher())
- val withDeps = CommunalInteractorFactory.create()
+ val withDeps = CommunalInteractorFactory.create(testScope)
tutorialRepository = withDeps.tutorialRepository
communalRepository = withDeps.communalRepository
@@ -379,6 +383,131 @@
}
@Test
+ fun transitionProgress_onTargetScene_fullProgress() =
+ testScope.runTest {
+ val targetScene = CommunalSceneKey.Blank
+ val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
+ val transitionProgress by collectLastValue(transitionProgressFlow)
+
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(targetScene)
+ )
+ underTest.setTransitionState(transitionState)
+
+ // We're on the target scene.
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+ }
+
+ @Test
+ fun transitionProgress_notOnTargetScene_noProgress() =
+ testScope.runTest {
+ val targetScene = CommunalSceneKey.Blank
+ val currentScene = CommunalSceneKey.Communal
+ val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
+ val transitionProgress by collectLastValue(transitionProgressFlow)
+
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(currentScene)
+ )
+ underTest.setTransitionState(transitionState)
+
+ // Transition progress is still idle, but we're not on the target scene.
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+ }
+
+ @Test
+ fun transitionProgress_transitioningToTrackedScene() =
+ testScope.runTest {
+ val currentScene = CommunalSceneKey.Communal
+ val targetScene = CommunalSceneKey.Blank
+ val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
+ val transitionProgress by collectLastValue(transitionProgressFlow)
+
+ var transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(currentScene)
+ )
+ underTest.setTransitionState(transitionState)
+
+ // Progress starts at 0.
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+
+ val progress = MutableStateFlow(0f)
+ transitionState =
+ MutableStateFlow(
+ ObservableCommunalTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ underTest.setTransitionState(transitionState)
+
+ // Partially transition.
+ progress.value = .4f
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(.4f))
+
+ // Transition is at full progress.
+ progress.value = 1f
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(1f))
+
+ // Transition finishes.
+ transitionState = MutableStateFlow(ObservableCommunalTransitionState.Idle(targetScene))
+ underTest.setTransitionState(transitionState)
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+ }
+
+ @Test
+ fun transitionProgress_transitioningAwayFromTrackedScene() =
+ testScope.runTest {
+ val currentScene = CommunalSceneKey.Blank
+ val targetScene = CommunalSceneKey.Communal
+ val transitionProgressFlow = underTest.transitionProgressToScene(currentScene)
+ val transitionProgress by collectLastValue(transitionProgressFlow)
+
+ var transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(currentScene)
+ )
+ underTest.setTransitionState(transitionState)
+
+ // Progress starts at 0.
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+
+ val progress = MutableStateFlow(0f)
+ transitionState =
+ MutableStateFlow(
+ ObservableCommunalTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ underTest.setTransitionState(transitionState)
+
+ // Partially transition.
+ progress.value = .4f
+
+ // This is a transition we don't care about the progress of.
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition)
+
+ // Transition is at full progress.
+ progress.value = 1f
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition)
+
+ // Transition finishes.
+ transitionState = MutableStateFlow(ObservableCommunalTransitionState.Idle(targetScene))
+ underTest.setTransitionState(transitionState)
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+ }
+
+ @Test
fun isCommunalShowing() =
testScope.runTest {
var isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index a776062..804c052 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.util.mockito.mock
@@ -48,6 +49,7 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -92,6 +94,13 @@
}
@Test
+ fun init_initsMediaHost() =
+ testScope.runTest {
+ // MediaHost is initialized as soon as the class is created.
+ verify(mediaHost).init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
+ }
+
+ @Test
fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
testScope.runTest {
// Keyguard showing, and tutorial not started.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 5b88ebe6..fd2fd2f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -203,4 +203,38 @@
assertThat(isVisible?.isAnimating).isEqualTo(false)
}
+
+ @Test
+ fun alpha_glanceableHubOpen_isZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope,
+ )
+
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
+ fun alpha_glanceableHubClosed_isOne() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+
+ // Transition to the glanceable hub and back.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope,
+ )
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+
+ assertThat(alpha).isEqualTo(1.0f)
+ }
}
diff --git a/packages/SystemUI/res/layout/biometric_prompt_content_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_content_layout.xml
new file mode 100644
index 0000000..3908757
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_content_layout.xml
@@ -0,0 +1,32 @@
+<?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"
+ android:id="@+id/customized_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ style="@style/AuthCredentialContentLayoutStyle">
+
+ <TextView
+ android:id="@+id/customized_view_title"
+ style="@style/TextAppearance.AuthCredential.ContentViewTitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="1"
+ android:singleLine="true" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_content_row_item_text_view.xml b/packages/SystemUI/res/layout/biometric_prompt_content_row_item_text_view.xml
new file mode 100644
index 0000000..e39f60f
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_content_row_item_text_view.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/TextAppearance.AuthCredential.ContentViewListItem"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1.0" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/biometric_prompt_content_row_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_content_row_layout.xml
new file mode 100644
index 0000000..6c86736
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_content_row_layout.xml
@@ -0,0 +1,21 @@
+<?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"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/biometric_prompt_content_list_row_height"
+ android:gravity="center_vertical|start"
+ android:orientation="horizontal" />
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index bea0e13..23fbb12 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -48,6 +48,23 @@
android:importantForAccessibility="no"
style="@style/TextAppearance.AuthCredential.Description"/>
+ <Space
+ android:id="@+id/space_above_content"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/biometric_prompt_space_above_content"
+ android:visibility="gone" />
+
+ <ScrollView
+ android:id="@+id/customized_view_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fadeScrollbars="false"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
+ android:scrollbars="vertical"
+ android:visibility="gone" />
+
<Space android:id="@+id/space_above_icon"
android:layout_width="match_parent"
android:layout_height="48dp" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5f6a39a..8be1cc7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -135,6 +135,9 @@
<color name="biometric_dialog_gray">#ff757575</color>
<color name="biometric_dialog_accent">@color/material_dynamic_primary40</color>
<color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 -->
+ <!-- Color for biometric prompt content view -->
+ <color name="biometric_prompt_content_background_color">#8AB4F8</color>
+ <color name="biometric_prompt_content_list_item_bullet_color">#1d873b</color>
<!-- SFPS colors -->
<color name="sfps_chevron_fill">@color/material_dynamic_primary90</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 90d8cdb..798fc06b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1092,6 +1092,16 @@
<dimen name="biometric_dialog_width">240dp</dimen>
<dimen name="biometric_dialog_height">240dp</dimen>
+ <!-- Dimensions for biometric prompt content view. -->
+ <dimen name="biometric_prompt_space_above_content">48dp</dimen>
+ <dimen name="biometric_prompt_content_container_padding_horizontal">24dp</dimen>
+ <dimen name="biometric_prompt_content_padding_horizontal">10dp</dimen>
+ <dimen name="biometric_prompt_content_list_row_height">24dp</dimen>
+ <dimen name="biometric_prompt_content_list_item_padding_horizontal">10dp</dimen>
+ <dimen name="biometric_prompt_content_list_item_text_size">14sp</dimen>
+ <dimen name="biometric_prompt_content_list_item_bullet_gap_width">10dp</dimen>
+ <dimen name="biometric_prompt_content_list_item_bullet_radius">5dp</dimen>
+
<!-- Biometric Auth Credential values -->
<dimen name="biometric_auth_icon_size">48dp</dimen>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index c925b26..fad4d4f 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -16,6 +16,7 @@
-->
<resources>
<integer name="biometric_dialog_text_gravity">8388611</integer> <!-- gravity start -->
+ <integer name="biometric_prompt_content_list_item_max_lines_if_two_column">3</integer>
<integer name="qs_security_footer_maxLines">2</integer>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7d7c050..ab3cacb 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -195,6 +195,24 @@
<item name="android:textSize">14sp</item>
</style>
+ <style name="TextAppearance.AuthCredential.ContentViewTitle">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:paddingTop">8dp</item>
+ <item name="android:paddingHorizontal">24dp</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:gravity">start</item>
+ </style>
+
+ <style name="TextAppearance.AuthCredential.ContentViewListItem">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:paddingTop">8dp</item>
+ <item name="android:paddingHorizontal">
+ @dimen/biometric_prompt_content_list_item_padding_horizontal
+ </item>
+ <item name="android:textSize">@dimen/biometric_prompt_content_list_item_text_size</item>
+ <item name="android:gravity">start</item>
+ </style>
+
<style name="TextAppearance.AuthCredential.Error">
<item name="android:paddingTop">6dp</item>
<item name="android:paddingHorizontal">24dp</item>
@@ -294,6 +312,11 @@
<item name="android:textSize">16sp</item>
</style>
+ <style name="AuthCredentialContentLayoutStyle">
+ <item name="android:background">@color/biometric_prompt_content_background_color</item>
+ <item name="android:paddingHorizontal">@dimen/biometric_prompt_content_padding_horizontal</item>
+ </style>
+
<style name="DeviceManagementDialogTitle">
<item name="android:gravity">center</item>
<item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index abbfa01..86802a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -131,23 +131,21 @@
icon.measure(
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
- } else if (child.getId() == R.id.space_above_icon) {
+ } else if (child.getId() == R.id.space_above_icon
+ || child.getId() == R.id.space_above_content
+ || child.getId() == R.id.button_bar) {
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(
child.getLayoutParams().height, MeasureSpec.EXACTLY));
- } else if (child.getId() == R.id.button_bar) {
- child.measure(
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
- MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.space_below_icon) {
// Set the spacer height so the fingerprint icon is on the physical sensor area
final int clampedSpacerHeight = Math.max(mBottomSpacerHeight, 0);
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(clampedSpacerHeight, MeasureSpec.EXACTLY));
- } else if (child.getId() == R.id.description) {
+ } else if (child.getId() == R.id.description
+ || child.getId() == R.id.customized_view_container) {
//skip description view and compute later
continue;
} else {
@@ -161,27 +159,28 @@
}
}
- //re-calculate the height of description
+ //re-calculate the height of body content
View description = mView.findViewById(R.id.description);
+ View contentView = mView.findViewById(R.id.customized_view_container);
if (description != null && description.getVisibility() != View.GONE) {
totalHeight += measureDescription(description, displayHeight, width, totalHeight);
+ } else if (contentView != null && contentView.getVisibility() != View.GONE) {
+ totalHeight += measureDescription(contentView, displayHeight, width, totalHeight);
}
return new AuthDialog.LayoutParams(width, totalHeight);
}
- private int measureDescription(View description, int displayHeight, int currWidth,
+ private int measureDescription(View bodyContent, int displayHeight, int currWidth,
int currHeight) {
- //description view should be measured in AuthBiometricFingerprintView#onMeasureInternal
- //so we could getMeasuredHeight in onMeasureInternalPortrait directly.
- int newHeight = description.getMeasuredHeight() + currHeight;
+ int newHeight = bodyContent.getMeasuredHeight() + currHeight;
int limit = (int) (displayHeight * 0.75);
if (newHeight > limit) {
- description.measure(
+ bodyContent.measure(
MeasureSpec.makeMeasureSpec(currWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(limit - currHeight, MeasureSpec.EXACTLY));
}
- return description.getMeasuredHeight();
+ return bodyContent.getMeasuredHeight();
}
@NonNull
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
new file mode 100644
index 0000000..e6939f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.Context
+import android.hardware.biometrics.SensorLocationInternal
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.SensorLocation
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class FingerprintPropertyInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ repository: FingerprintPropertyRepository,
+ configurationInteractor: ConfigurationInteractor,
+ displayStateInteractor: DisplayStateInteractor,
+) {
+ /**
+ * Devices with multiple physical displays use unique display ids to determine which sensor is
+ * on the active physical display. This value represents a unique physical display id.
+ */
+ private val uniqueDisplayId: Flow<String> =
+ displayStateInteractor.displayChanges
+ .map { context.display?.uniqueId }
+ .filterNotNull()
+ .distinctUntilChanged()
+
+ /**
+ * Sensor location for the:
+ * - current physical display
+ * - device's natural screen resolution
+ * - device's natural orientation
+ */
+ private val unscaledSensorLocation: Flow<SensorLocationInternal> =
+ combine(
+ repository.sensorLocations,
+ uniqueDisplayId,
+ ) { locations, displayId ->
+ // Devices without multiple physical displays do not use the display id as the key;
+ // instead, the key is an empty string.
+ locations.getOrDefault(
+ displayId,
+ locations.getOrDefault("", SensorLocationInternal.DEFAULT)
+ )
+ }
+
+ /**
+ * Sensor location for the:
+ * - current physical display
+ * - current screen resolution
+ * - device's natural orientation
+ */
+ val sensorLocation: Flow<SensorLocation> =
+ combine(
+ unscaledSensorLocation,
+ configurationInteractor.scaleForResolution,
+ ) { unscaledSensorLocation, scale ->
+ val sensorLocation =
+ SensorLocation(
+ unscaledSensorLocation.sensorLocationX,
+ unscaledSensorLocation.sensorLocationY,
+ unscaledSensorLocation.sensorRadius,
+ )
+ sensorLocation.scale = scale
+ sensorLocation
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 8fbb250..4377937 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.biometrics.domain.model
+import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricUserInfo
@@ -34,6 +35,7 @@
operationInfo = operationInfo,
showEmergencyCallButton = info.isShowEmergencyCallButton
) {
+ val contentView: PromptContentView? = info.contentView
val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt
new file mode 100644
index 0000000..dddadbd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.shared.model
+
+/** Provides current sensor location information in the current screen resolution [scale]. */
+data class SensorLocation(
+ private val naturalCenterX: Int,
+ private val naturalCenterY: Int,
+ private val naturalRadius: Int
+) {
+ /**
+ * Scale to apply to the sensor location's natural parameters to support different screen
+ * resolutions.
+ */
+ var scale: Float = 1f
+
+ val centerX: Float
+ get() {
+ return naturalCenterX * scale
+ }
+ val centerY: Float
+ get() {
+ return naturalCenterY * scale
+ }
+ val radius: Float
+ get() {
+ return naturalRadius * scale
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index 0d72b9c..60b454e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -101,6 +101,7 @@
final View child = getChildAt(i);
if (child.getId() == R.id.space_above_icon
+ || child.getId() == R.id.space_above_content
|| child.getId() == R.id.space_below_icon
|| child.getId() == R.id.button_bar) {
child.measure(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
new file mode 100644
index 0000000..22b02da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -0,0 +1,220 @@
+/*
+ * 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.biometrics.ui.binder
+
+import android.content.Context
+import android.content.res.Resources
+import android.content.res.Resources.Theme
+import android.graphics.Paint
+import android.hardware.biometrics.PromptContentListItem
+import android.hardware.biometrics.PromptContentListItemBulletedText
+import android.hardware.biometrics.PromptContentListItemPlainText
+import android.hardware.biometrics.PromptContentView
+import android.hardware.biometrics.PromptVerticalListContentView
+import android.text.SpannableString
+import android.text.Spanned
+import android.text.style.BulletSpan
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import android.widget.ScrollView
+import android.widget.Space
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.biometrics.ui.BiometricPromptLayout
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlin.math.ceil
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/** Sub-binder for [BiometricPromptLayout.customized_view_container]. */
+object BiometricCustomizedViewBinder {
+ fun bind(customizedViewContainer: ScrollView, spaceAbove: Space, viewModel: PromptViewModel) {
+ customizedViewContainer.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ val contentView: PromptContentView? = viewModel.contentView.first()
+
+ if (contentView != null) {
+ val context = customizedViewContainer.context
+ customizedViewContainer.addView(contentView.toView(context))
+ customizedViewContainer.visibility = View.VISIBLE
+ spaceAbove.visibility = View.VISIBLE
+ } else {
+ customizedViewContainer.visibility = View.GONE
+ spaceAbove.visibility = View.GONE
+ }
+ }
+ }
+ }
+ }
+}
+
+private fun PromptContentView.toView(context: Context): View {
+ val resources = context.resources
+ val inflater = LayoutInflater.from(context)
+ when (this) {
+ is PromptVerticalListContentView -> {
+ val contentView =
+ inflater.inflate(R.layout.biometric_prompt_content_layout, null) as LinearLayout
+
+ val descriptionView = contentView.requireViewById<TextView>(R.id.customized_view_title)
+ if (!description.isNullOrEmpty()) {
+ descriptionView.text = description
+ } else {
+ descriptionView.visibility = View.GONE
+ }
+
+ // Show two column by default, once there is an item exceeding max lines, show single
+ // item instead.
+ val showTwoColumn = listItems.all { !it.doesExceedMaxLinesIfTwoColumn(resources) }
+ var currRowView = createNewRowLayout(inflater)
+ for (item in listItems) {
+ val itemView = item.toView(resources, inflater, context.theme)
+ currRowView.addView(itemView)
+
+ if (!showTwoColumn || currRowView.childCount == 2) {
+ contentView.addView(currRowView)
+ currRowView = createNewRowLayout(inflater)
+ }
+ }
+ if (currRowView.childCount > 0) {
+ contentView.addView(currRowView)
+ }
+
+ return contentView
+ }
+ else -> {
+ throw IllegalStateException("No such PromptContentView: $this")
+ }
+ }
+}
+
+private fun createNewRowLayout(inflater: LayoutInflater): LinearLayout {
+ return inflater.inflate(R.layout.biometric_prompt_content_row_layout, null) as LinearLayout
+}
+
+private fun PromptContentListItem.doesExceedMaxLinesIfTwoColumn(
+ resources: Resources,
+): Boolean {
+ val passedInText: CharSequence =
+ when (this) {
+ is PromptContentListItemPlainText -> text
+ is PromptContentListItemBulletedText -> text
+ else -> {
+ throw IllegalStateException("No such ListItem: $this")
+ }
+ }
+
+ when (this) {
+ is PromptContentListItemPlainText,
+ is PromptContentListItemBulletedText -> {
+ val dialogMargin =
+ resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
+ val halfDialogWidth =
+ Resources.getSystem().displayMetrics.widthPixels / 2 - dialogMargin
+ val containerPadding =
+ resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_content_container_padding_horizontal
+ )
+ val contentPadding =
+ resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_padding_horizontal)
+ val listItemPadding = getListItemPadding(resources)
+ val maxWidth = halfDialogWidth - containerPadding - contentPadding - listItemPadding
+
+ val text = "$passedInText"
+ val textSize =
+ resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_content_list_item_text_size
+ )
+ val paint = Paint()
+ paint.textSize = textSize.toFloat()
+
+ val maxLines =
+ resources.getInteger(
+ R.integer.biometric_prompt_content_list_item_max_lines_if_two_column
+ )
+ val numLines = ceil(paint.measureText(text).toDouble() / maxWidth).toInt()
+ return numLines > maxLines
+ }
+ else -> {
+ throw IllegalStateException("No such ListItem: $this")
+ }
+ }
+}
+
+private fun PromptContentListItem.toView(
+ resources: Resources,
+ inflater: LayoutInflater,
+ theme: Theme,
+): TextView {
+ val textView =
+ inflater.inflate(R.layout.biometric_prompt_content_row_item_text_view, null) as TextView
+ val lp = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
+ textView.layoutParams = lp
+
+ when (this) {
+ is PromptContentListItemPlainText -> {
+ textView.text = text
+ }
+ is PromptContentListItemBulletedText -> {
+ val bulletedText = SpannableString(text)
+ val span =
+ BulletSpan(
+ getListItemBulletGapWidth(resources),
+ getListItemBulletColor(resources, theme),
+ getListItemBulletRadius(resources)
+ )
+ bulletedText.setSpan(span, 0 /* start */, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ textView.text = bulletedText
+ }
+ else -> {
+ throw IllegalStateException("No such ListItem: $this")
+ }
+ }
+ return textView
+}
+
+private fun PromptContentListItem.getListItemPadding(resources: Resources): Int {
+ var listItemPadding =
+ resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_content_list_item_padding_horizontal
+ ) * 2
+ when (this) {
+ is PromptContentListItemPlainText -> {}
+ is PromptContentListItemBulletedText -> {
+ listItemPadding +=
+ getListItemBulletRadius(resources) * 2 + getListItemBulletGapWidth(resources)
+ }
+ else -> {
+ throw IllegalStateException("No such ListItem: $this")
+ }
+ }
+ return listItemPadding
+}
+
+private fun getListItemBulletRadius(resources: Resources): Int =
+ resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_list_item_bullet_radius)
+
+private fun getListItemBulletGapWidth(resources: Resources): Int =
+ resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_list_item_bullet_gap_width)
+
+private fun getListItemBulletColor(resources: Resources, theme: Theme): Int =
+ resources.getColor(R.color.biometric_prompt_content_list_item_bullet_color, theme)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 7b8cb82..04dc7a8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -31,6 +31,7 @@
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager
import android.widget.Button
+import android.widget.ScrollView
import android.widget.TextView
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
@@ -94,6 +95,8 @@
val titleView = view.requireViewById<TextView>(R.id.title)
val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
val descriptionView = view.requireViewById<TextView>(R.id.description)
+ val customizedViewContainer =
+ view.requireViewById<ScrollView>(R.id.customized_view_container)
// set selected to enable marquee unless a screen reader is enabled
titleView.isSelected =
@@ -150,8 +153,13 @@
}
titleView.text = viewModel.title.first()
- descriptionView.text = viewModel.description.first()
subtitleView.text = viewModel.subtitle.first()
+ descriptionView.text = viewModel.description.first()
+ BiometricCustomizedViewBinder.bind(
+ customizedViewContainer,
+ view.requireViewById(R.id.space_above_content),
+ viewModel
+ )
// set button listeners
negativeButton.setOnClickListener { legacyCallback.onButtonNegative() }
@@ -178,12 +186,14 @@
titleView,
subtitleView,
descriptionView,
+ customizedViewContainer,
),
viewsToFadeInOnSizeChange =
listOf(
titleView,
subtitleView,
descriptionView,
+ customizedViewContainer,
indicatorMessageView,
negativeButton,
cancelButton,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1a7b6c9..c3bbaed 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -55,7 +55,7 @@
fun bind(
view: BiometricPromptLayout,
viewModel: PromptViewModel,
- viewsToHideWhenSmall: List<TextView>,
+ viewsToHideWhenSmall: List<View>,
viewsToFadeInOnSizeChange: List<View>,
panelViewController: AuthPanelController,
jankListener: BiometricJankListener,
@@ -110,7 +110,7 @@
// prepare for animated size transitions
for (v in viewsToHideWhenSmall) {
- v.showTextOrHide(forceHide = size.isSmall)
+ v.showContentOrHide(forceHide = size.isSmall)
}
if (currentSize == null && size.isSmall) {
iconHolderView.alpha = 0f
@@ -119,6 +119,10 @@
viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
}
+ // TODO(b/302735104): Fix wrong height due to the delay of
+ // PromptContentView. addOnLayoutChangeListener() will cause crash when
+ // showing credential view, since |PromptIconViewModel| won't release the
+ // flow.
// propagate size changes to legacy panel controller and animate transitions
view.doOnLayout {
val width = view.measuredWidth
@@ -228,8 +232,9 @@
return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
}
-private fun TextView.showTextOrHide(forceHide: Boolean = false) {
- visibility = if (forceHide || text.isBlank()) View.GONE else View.VISIBLE
+private fun View.showContentOrHide(forceHide: Boolean = false) {
+ val isTextViewWithBlankText = this is TextView && this.text.isBlank()
+ visibility = if (forceHide || isTextViewWithBlankText) View.GONE else View.VISIBLE
}
private fun View.asVerticalAnimator(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index d899827e..1c78928 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.systemui.biometrics.ui.viewmodel
import android.content.Context
import android.graphics.Rect
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.PromptContentView
import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
@@ -239,9 +241,20 @@
val subtitle: Flow<String> =
promptSelectorInteractor.prompt.map { it?.subtitle ?: "" }.distinctUntilChanged()
- /** Description for the prompt. */
- val description: Flow<String> =
+ /** Custom content view for the prompt. */
+ val contentView: Flow<PromptContentView?> =
+ promptSelectorInteractor.prompt.map { it?.contentView }.distinctUntilChanged()
+
+ private val originalDescription =
promptSelectorInteractor.prompt.map { it?.description ?: "" }.distinctUntilChanged()
+ /**
+ * Description for the prompt. Description view and contentView is mutually exclusive. Pass
+ * description down only when contentView is null.
+ */
+ val description: Flow<String> =
+ combine(contentView, originalDescription) { contentView, description ->
+ if (contentView == null) description else ""
+ }
/** If the indicator (help, error) message should be shown. */
val isIndicatorMessageVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index c2a1d8f..d0ff185 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -20,6 +20,7 @@
import android.util.Log
import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel
import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -90,6 +91,9 @@
val alternateBouncerUIAvailable: StateFlow<Boolean>
val sideFpsShowing: StateFlow<Boolean>
+ /** Action that should be run right after the bouncer is dismissed. */
+ var bouncerDismissActionModel: BouncerDismissActionModel?
+
var lastAlternateBouncerVisibleTime: Long
fun setPrimaryScrimmed(isScrimmed: Boolean)
@@ -134,6 +138,8 @@
@Application private val applicationScope: CoroutineScope,
@BouncerTableLog private val buffer: TableLogBuffer,
) : KeyguardBouncerRepository {
+ override var bouncerDismissActionModel: BouncerDismissActionModel? = null
+
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
private val _primaryBouncerShow = MutableStateFlow(false)
override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 654fa22..8c87b0c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -28,10 +28,12 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.DejankUtils
+import com.android.systemui.Flags
import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel
import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
@@ -154,12 +156,12 @@
/** Show the bouncer if necessary and set the relevant states. */
@JvmOverloads
fun show(isScrimmed: Boolean) {
- if (primaryBouncerView.delegate == null) {
+ if (primaryBouncerView.delegate == null && !Flags.composeBouncer()) {
Log.d(
TAG,
"PrimaryBouncerInteractor#show is being called before the " +
- "primaryBouncerDelegate is set. Let's exit early so we don't set the wrong " +
- "primaryBouncer state."
+ "primaryBouncerDelegate is set. Let's exit early so we don't " +
+ "set the wrong primaryBouncer state."
)
return
}
@@ -272,15 +274,24 @@
repository.setShowMessage(BouncerShowMessageModel(message, colorStateList))
}
+ val bouncerDismissAction: BouncerDismissActionModel?
+ get() = repository.bouncerDismissActionModel
+
/**
* Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is
- * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we
- * call cancelAction.
+ * unlocked, we will run the onDismissAction. If the bouncer is exited before unlocking, we call
+ * cancelAction.
*/
fun setDismissAction(
onDismissAction: ActivityStarter.OnDismissAction?,
cancelAction: Runnable?
) {
+ repository.bouncerDismissActionModel =
+ if (onDismissAction != null && cancelAction != null) {
+ BouncerDismissActionModel(onDismissAction, cancelAction)
+ } else {
+ null
+ }
primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerDismissActionModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerDismissActionModel.kt
new file mode 100644
index 0000000..02b444f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerDismissActionModel.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.bouncer.shared.model
+
+import com.android.systemui.plugins.ActivityStarter
+
+/** Represents the action that needs to be performed after bouncer is dismissed. */
+data class BouncerDismissActionModel(
+ /** If the bouncer is unlocked, [onDismissAction] will be run. */
+ val onDismissAction: ActivityStarter.OnDismissAction?,
+ /** If the bouncer is exited before unlocking, [onCancel] will be invoked. */
+ val onCancel: Runnable?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerDialogFactory.kt
new file mode 100644
index 0000000..5defe475
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerDialogFactory.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui
+
+import android.app.AlertDialog
+
+/** Factory to create alert dialogs for use in bouncer component. */
+interface BouncerDialogFactory {
+ operator fun invoke(): AlertDialog
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
index 7f3b794..f3903de 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
@@ -16,9 +16,15 @@
package com.android.systemui.bouncer.ui
+import android.app.AlertDialog
+import android.content.Context
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModelModule
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.SystemUIDialog
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module(
includes =
@@ -29,4 +35,17 @@
interface BouncerViewModule {
/** Binds BouncerView to BouncerViewImpl and makes it injectable. */
@Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
+
+ companion object {
+
+ @Provides
+ @SysUISingleton
+ fun bouncerDialogFactory(@Application context: Context): BouncerDialogFactory {
+ return object : BouncerDialogFactory {
+ override fun invoke(): AlertDialog {
+ return SystemUIDialog(context)
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
new file mode 100644
index 0000000..dd253a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -0,0 +1,89 @@
+package com.android.systemui.bouncer.ui.binder
+
+import android.view.ViewGroup
+import com.android.keyguard.KeyguardMessageAreaController
+import com.android.keyguard.ViewMediatorCallback
+import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.Flags
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.Flags.COMPOSE_BOUNCER_ENABLED
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.log.BouncerLogger
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import dagger.Lazy
+import javax.inject.Inject
+
+/** Helper data class that allows to lazy load all the dependencies of the legacy bouncer. */
+@SysUISingleton
+data class LegacyBouncerDependencies
+@Inject
+constructor(
+ val viewModel: KeyguardBouncerViewModel,
+ val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+ val componentFactory: KeyguardBouncerComponent.Factory,
+ val messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
+ val bouncerMessageInteractor: BouncerMessageInteractor,
+ val bouncerLogger: BouncerLogger,
+ val selectedUserInteractor: SelectedUserInteractor,
+)
+
+/** Helper data class that allows to lazy load all the dependencies of the compose based bouncer. */
+@SysUISingleton
+data class ComposeBouncerDependencies
+@Inject
+constructor(
+ val legacyInteractor: PrimaryBouncerInteractor,
+ val viewModel: BouncerViewModel,
+ val dialogFactory: BouncerDialogFactory,
+ val authenticationInteractor: AuthenticationInteractor,
+ val viewMediatorCallback: ViewMediatorCallback?,
+ val selectedUserInteractor: SelectedUserInteractor,
+)
+
+/**
+ * Toggles between the compose and non compose version of the bouncer, instantiating only the
+ * dependencies required for each.
+ */
+@SysUISingleton
+class BouncerViewBinder
+@Inject
+constructor(
+ private val legacyBouncerDependencies: Lazy<LegacyBouncerDependencies>,
+ private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
+) {
+ fun bind(view: ViewGroup) {
+ if (
+ ComposeFacade.isComposeAvailable() && Flags.composeBouncer() && COMPOSE_BOUNCER_ENABLED
+ ) {
+ val deps = composeBouncerDependencies.get()
+ ComposeBouncerViewBinder.bind(
+ view,
+ deps.legacyInteractor,
+ deps.viewModel,
+ deps.dialogFactory,
+ deps.authenticationInteractor,
+ deps.selectedUserInteractor,
+ deps.viewMediatorCallback,
+ )
+ } else {
+ val deps = legacyBouncerDependencies.get()
+ KeyguardBouncerViewBinder.bind(
+ view,
+ deps.viewModel,
+ deps.primaryBouncerToGoneTransitionViewModel,
+ deps.componentFactory,
+ deps.messageAreaControllerFactory,
+ deps.bouncerMessageInteractor,
+ deps.bouncerLogger,
+ deps.selectedUserInteractor,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
new file mode 100644
index 0000000..7b05395
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -0,0 +1,75 @@
+package com.android.systemui.bouncer.ui.binder
+
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.ViewMediatorCallback
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/** View binder responsible for binding the compose version of the bouncer. */
+object ComposeBouncerViewBinder {
+ fun bind(
+ view: ViewGroup,
+ legacyInteractor: PrimaryBouncerInteractor,
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerDialogFactory,
+ authenticationInteractor: AuthenticationInteractor,
+ selectedUserInteractor: SelectedUserInteractor,
+ viewMediatorCallback: ViewMediatorCallback?,
+ ) {
+ view.addView(
+ ComposeFacade.createBouncer(
+ view.context,
+ viewModel,
+ dialogFactory,
+ )
+ )
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ legacyInteractor.isShowing.collectLatest { bouncerShowing ->
+ view.isVisible = bouncerShowing
+ }
+ }
+
+ launch {
+ authenticationInteractor.onAuthenticationResult.collectLatest {
+ authenticationSucceeded ->
+ if (authenticationSucceeded) {
+ // Some dismiss actions require that keyguard be dismissed right away or
+ // deferred until something else later on dismisses keyguard (eg. end of
+ // a hide animation).
+ val deferKeyguardDone =
+ legacyInteractor.bouncerDismissAction?.onDismissAction?.onDismiss()
+ legacyInteractor.setDismissAction(null, null)
+
+ viewMediatorCallback?.let {
+ val selectedUserId = selectedUserInteractor.getSelectedUserId()
+ if (deferKeyguardDone == true) {
+ it.keyguardDonePending(selectedUserId)
+ } else {
+ it.keyguardDone(selectedUserId)
+ }
+ }
+ }
+ }
+ }
+ launch {
+ legacyInteractor.startingDisappearAnimation.collectLatest {
+ it.run()
+ legacyInteractor.hide()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 24d4c6c..9fa4cd6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -37,6 +37,8 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -77,6 +79,29 @@
communalRepository.setTransitionState(transitionState)
}
+ /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */
+ fun transitionProgressToScene(targetScene: CommunalSceneKey) =
+ transitionState
+ .flatMapLatest { state ->
+ when (state) {
+ is ObservableCommunalTransitionState.Idle ->
+ flowOf(CommunalTransitionProgress.Idle(state.scene))
+ is ObservableCommunalTransitionState.Transition ->
+ if (state.toScene == targetScene) {
+ state.progress.map {
+ CommunalTransitionProgress.Transition(
+ // Clamp the progress values between 0 and 1 as actual progress
+ // values can be higher than 0 or lower than 1 due to a fling.
+ progress = it.coerceIn(0.0f, 1.0f)
+ )
+ }
+ } else {
+ flowOf(CommunalTransitionProgress.OtherTransition)
+ }
+ }
+ }
+ .distinctUntilChanged()
+
/**
* Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
* [CommunalSceneKey.Communal].
@@ -232,3 +257,17 @@
}
}
}
+
+/** Simplified transition progress data class for tracking a single transition between scenes. */
+sealed class CommunalTransitionProgress {
+ /** No transition/animation is currently running. */
+ data class Idle(val scene: CommunalSceneKey) : CommunalTransitionProgress()
+
+ /** There is a transition animating to the expected scene. */
+ data class Transition(
+ val progress: Float,
+ ) : CommunalTransitionProgress()
+
+ /** There is a transition animating to a scene other than the expected scene. */
+ data object OtherTransition : CommunalTransitionProgress()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 7a96fab..09c18ed 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -23,7 +23,9 @@
import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
import com.android.systemui.media.dagger.MediaModule
import javax.inject.Inject
import javax.inject.Named
@@ -70,6 +72,17 @@
override val isPopupOnDismissCtaShowing: Flow<Boolean> =
_isPopupOnDismissCtaShowing.asStateFlow()
+ init {
+ // Initialize our media host for the UMO. This only needs to happen once and must be done
+ // before the MediaHierarchyManager attempts to move the UMO to the hub.
+ with(mediaHost) {
+ expansion = MediaHostState.EXPANDED
+ showsOnlyActiveMedia = false
+ falsingProtectionNeeded = false
+ init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
+ }
+ }
+
override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
override fun onDismissCtaTile() {
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 3a92739..acbdecc 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -22,6 +22,8 @@
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -88,6 +90,13 @@
viewModel: BaseCommunalViewModel,
): View
+ /** Create a [View] to represent the [BouncerViewModel]. */
+ fun createBouncer(
+ context: Context,
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerDialogFactory,
+ ): View
+
/** Creates a container that hosts the communal UI and handles gesture transitions. */
fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 7876a6f..846736c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -476,6 +476,13 @@
// TODO(b/283300105): Tracking Bug
@JvmField val SCENE_CONTAINER_ENABLED = false
+ /**
+ * Whether the compose bouncer is enabled. This ensures ProGuard can
+ * remove unused code from our APK at compile time.
+ */
+ // TODO(b/280877228): Tracking Bug
+ @JvmField val COMPOSE_BOUNCER_ENABLED = false
+
// 1900
@JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 19fd7f9..48b3d9a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -19,6 +19,7 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.systemui.Flags
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -32,6 +33,7 @@
class FromGlanceableHubTransitionInteractor
@Inject
constructor(
+ private val glanceableHubTransitions: GlanceableHubTransitions,
override val transitionRepository: KeyguardTransitionRepository,
transitionInteractor: KeyguardTransitionInteractor,
@Main mainDispatcher: CoroutineDispatcher,
@@ -47,6 +49,7 @@
if (!Flags.communalHub()) {
return
}
+ listenForHubToLockscreen()
}
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -56,6 +59,18 @@
}
}
+ /**
+ * Listens for the glanceable hub transition to lock screen and directly drives the keyguard
+ * transition.
+ */
+ private fun listenForHubToLockscreen() {
+ glanceableHubTransitions.listenForLockscreenAndHubTransition(
+ transitionName = "listenForHubToLockscreen",
+ transitionOwnerName = TAG,
+ toScene = CommunalSceneKey.Blank
+ )
+ }
+
companion object {
const val TAG = "FromGlanceableHubTransitionInteractor"
val DEFAULT_DURATION = 500.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 2d0baa8..8b2b45f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -18,6 +18,7 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -62,6 +63,7 @@
private val flags: FeatureFlags,
private val shadeRepository: ShadeRepository,
private val powerInteractor: PowerInteractor,
+ private val glanceableHubTransitions: GlanceableHubTransitions,
inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
) :
TransitionInteractor(
@@ -81,6 +83,7 @@
listenForLockscreenToPrimaryBouncerDragging()
listenForLockscreenToAlternateBouncer()
listenForLockscreenTransitionToCamera()
+ listenForLockscreenToGlanceableHub()
}
/**
@@ -381,6 +384,22 @@
}
}
+ /**
+ * Listens for transition from glanceable hub back to lock screen and directly drives the
+ * keyguard transition.
+ */
+ private fun listenForLockscreenToGlanceableHub() {
+ if (!com.android.systemui.Flags.communalHub()) {
+ return
+ }
+
+ glanceableHubTransitions.listenForLockscreenAndHubTransition(
+ transitionName = "listenForLockscreenToGlanceableHub",
+ transitionOwnerName = TAG,
+ toScene = CommunalSceneKey.Communal
+ )
+ }
+
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
@@ -406,5 +425,6 @@
val TO_AOD_DURATION = 500.milliseconds
val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
val TO_GONE_DURATION = DEFAULT_DURATION
+ val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
new file mode 100644
index 0000000..cb50839
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+class GlanceableHubTransitions
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val transitionInteractor: KeyguardTransitionInteractor,
+ private val transitionRepository: KeyguardTransitionRepository,
+ private val communalInteractor: CommunalInteractor,
+) {
+ /**
+ * Listens for the glanceable hub transition to the specified scene and directly drives the
+ * keyguard transition between the lockscreen and the hub.
+ *
+ * The glanceable hub transition progress is used as the source of truth as it cannot be driven
+ * externally. The progress is used for both transitions caused by user touch input or by
+ * programmatic changes.
+ */
+ fun listenForLockscreenAndHubTransition(
+ transitionName: String,
+ transitionOwnerName: String,
+ toScene: CommunalSceneKey
+ ) {
+ val fromState: KeyguardState
+ val toState: KeyguardState
+ if (toScene == CommunalSceneKey.Blank) {
+ fromState = KeyguardState.GLANCEABLE_HUB
+ toState = KeyguardState.LOCKSCREEN
+ } else {
+ fromState = KeyguardState.LOCKSCREEN
+ toState = KeyguardState.GLANCEABLE_HUB
+ }
+ var transitionId: UUID? = null
+ scope.launch("$transitionOwnerName#$transitionName") {
+ communalInteractor
+ .transitionProgressToScene(toScene)
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { pair ->
+ val (transitionProgress, lastStartedStep) = pair
+
+ val id = transitionId
+ if (id == null) {
+ // No transition started.
+ if (
+ transitionProgress is CommunalTransitionProgress.Transition &&
+ lastStartedStep.to == fromState
+ ) {
+ transitionId =
+ transitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = transitionOwnerName,
+ from = fromState,
+ to = toState,
+ animator = null, // transition will be manually controlled
+ )
+ )
+ }
+ } else {
+ if (lastStartedStep.to != toState) {
+ return@collect
+ }
+ // An existing `id` means a transition is started, and calls to
+ // `updateTransition` will control it until FINISHED or CANCELED
+ val nextState: TransitionState
+ val progressFraction: Float
+ when (transitionProgress) {
+ is CommunalTransitionProgress.Idle -> {
+ if (transitionProgress.scene == toScene) {
+ nextState = TransitionState.FINISHED
+ progressFraction = 1f
+ } else {
+ nextState = TransitionState.CANCELED
+ progressFraction = 0f
+ }
+ }
+ is CommunalTransitionProgress.Transition -> {
+ nextState = TransitionState.RUNNING
+ progressFraction = transitionProgress.progress
+ }
+ is CommunalTransitionProgress.OtherTransition -> {
+ // Shouldn't happen but if another transition starts during the
+ // current one, mark the current one as canceled.
+ nextState = TransitionState.CANCELED
+ progressFraction = 0f
+ }
+ }
+ transitionRepository.updateTransition(
+ id,
+ progressFraction,
+ nextState,
+ )
+
+ if (
+ nextState == TransitionState.CANCELED ||
+ nextState == TransitionState.FINISHED
+ ) {
+ transitionId = null
+ }
+
+ // If canceled, just put the state back.
+ if (nextState == TransitionState.CANCELED) {
+ transitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = transitionOwnerName,
+ from = toState,
+ to = fromState,
+ animator =
+ ValueAnimator().apply {
+ interpolator = Interpolators.LINEAR
+ duration = 0
+ }
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 5b0c562..a8223ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
@@ -135,10 +136,18 @@
val lockscreenToDreamingLockscreenHostedTransition: Flow<TransitionStep> =
repository.transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED)
+ /** LOCKSCREEN->GLANCEABLE_HUB transition information. */
+ val lockscreenToGlanceableHubTransition: Flow<TransitionStep> =
+ repository.transition(LOCKSCREEN, GLANCEABLE_HUB)
+
/** LOCKSCREEN->OCCLUDED transition information. */
val lockscreenToOccludedTransition: Flow<TransitionStep> =
repository.transition(LOCKSCREEN, OCCLUDED)
+ /** GLANCEABLE_HUB->LOCKSCREEN transition information. */
+ val glanceableHubToLockscreenTransition: Flow<TransitionStep> =
+ repository.transition(GLANCEABLE_HUB, LOCKSCREEN)
+
/** OCCLUDED->LOCKSCREEN transition information. */
val occludedToLockscreenTransition: Flow<TransitionStep> =
repository.transition(OCCLUDED, LOCKSCREEN)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index fa4de04..ce45112 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -17,14 +17,12 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
-import android.hardware.biometrics.SensorLocationInternal
import com.android.settingslib.Utils
-import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
-import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -44,21 +42,24 @@
configurationInteractor: ConfigurationInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel,
- fingerprintPropertyRepository: FingerprintPropertyRepository,
+ fingerprintPropertyInteractor: FingerprintPropertyInteractor,
udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
+
+ /**
+ * UDFPS icon location in pixels for the current display and screen resolution, in natural
+ * orientation.
+ */
val iconLocation: Flow<IconLocation> =
isSupported.flatMapLatest { supportsUI ->
if (supportsUI) {
- fingerprintPropertyRepository.sensorLocations.map { sensorLocations ->
- val sensorLocation =
- sensorLocations.getOrDefault("", SensorLocationInternal.DEFAULT)
+ fingerprintPropertyInteractor.sensorLocation.map { sensorLocation ->
IconLocation(
- left = sensorLocation.sensorLocationX - sensorLocation.sensorRadius,
- top = sensorLocation.sensorLocationY - sensorLocation.sensorRadius,
- right = sensorLocation.sensorLocationX + sensorLocation.sensorRadius,
- bottom = sensorLocation.sensorLocationY + sensorLocation.sensorRadius,
+ left = (sensorLocation.centerX - sensorLocation.radius).toInt(),
+ top = (sensorLocation.centerY - sensorLocation.radius).toInt(),
+ right = (sensorLocation.centerX + sensorLocation.radius).toInt(),
+ bottom = (sensorLocation.centerY + sensorLocation.radius).toInt(),
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
new file mode 100644
index 0000000..bc51821
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down GLANCEABLE_HUB->LOCKSCREEN transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class GlanceableHubToLockscreenTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+) {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ )
+
+ // TODO(b/315205222): implement full animation spec instead of just a simple fade.
+ val keyguardAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ onStep = { it },
+ onFinish = { 1f },
+ onCancel = { 0f },
+ name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardAlpha",
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 5059e6b..5d36da9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -46,6 +46,7 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -58,6 +59,8 @@
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
screenOffAnimationController: ScreenOffAnimationController,
private val aodBurnInViewModel: AodBurnInViewModel,
aodAlphaViewModel: AodAlphaViewModel,
@@ -78,7 +81,13 @@
keyguardInteractor.notificationContainerBounds
/** An observable for the alpha level for the entire keyguard root view. */
- val alpha: Flow<Float> = aodAlphaViewModel.alpha
+ val alpha: Flow<Float> =
+ merge(
+ aodAlphaViewModel.alpha,
+ lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
+ glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+ )
+ .distinctUntilChanged()
/** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 0000000..3ea83ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down LOCKSCREEN->GLANCEABLE_HUB transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class LockscreenToGlanceableHubTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+) {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+
+ // TODO(b/315205222): implement full animation spec instead of just a simple fade.
+ val keyguardAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ onStep = { 1f - it },
+ onFinish = { 0f },
+ onCancel = { 1f },
+ name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardAlpha",
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index cde2a62..8c852cd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -31,16 +31,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AuthKeyguardMessageArea;
-import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.LockIconViewController;
-import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
-import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
-import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
+import com.android.systemui.bouncer.ui.binder.BouncerViewBinder;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
@@ -56,8 +52,6 @@
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies;
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
-import com.android.systemui.log.BouncerLogger;
import com.android.systemui.res.R;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
import com.android.systemui.statusbar.DragDownHelper;
@@ -77,7 +71,6 @@
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.SystemClock;
@@ -183,24 +176,18 @@
DumpManager dumpManager,
PulsingGestureListener pulsingGestureListener,
LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
- KeyguardBouncerViewModel keyguardBouncerViewModel,
- KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
- KeyguardMessageAreaController.Factory messageAreaControllerFactory,
KeyguardTransitionInteractor keyguardTransitionInteractor,
- PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
GlanceableHubContainerController glanceableHubContainerController,
NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
FeatureFlagsClassic featureFlagsClassic,
SystemClock clock,
- BouncerMessageInteractor bouncerMessageInteractor,
- BouncerLogger bouncerLogger,
SysUIKeyEventHandler sysUIKeyEventHandler,
QuickSettingsController quickSettingsController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
- SelectedUserInteractor selectedUserInteractor,
Lazy<JavaAdapter> javaAdapter,
- Lazy<AlternateBouncerDependencies> alternateBouncerDependencies) {
+ Lazy<AlternateBouncerDependencies> alternateBouncerDependencies,
+ BouncerViewBinder bouncerViewBinder) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -234,15 +221,7 @@
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
mDisableSubpixelTextTransitionListener = new DisableSubpixelTextTransitionListener(mView);
- KeyguardBouncerViewBinder.bind(
- mView.findViewById(R.id.keyguard_bouncer_container),
- keyguardBouncerViewModel,
- primaryBouncerToGoneTransitionViewModel,
- keyguardBouncerComponentFactory,
- messageAreaControllerFactory,
- bouncerMessageInteractor,
- bouncerLogger,
- selectedUserInteractor);
+ bouncerViewBinder.bind(mView.findViewById(R.id.keyguard_bouncer_container));
if (DeviceEntryUdfpsRefactor.isEnabled()) {
AlternateBouncerViewBinder.bind(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 6528cef3..a1718b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -37,6 +37,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -45,8 +46,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.TransformableView;
@@ -898,6 +899,7 @@
// forceUpdateVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
+ notifySubtreeForAccessibilityContentChange();
}
private void fireExpandedVisibleListenerIfVisible() {
@@ -980,6 +982,7 @@
// updateViewVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
+ notifySubtreeForAccessibilityContentChange();
}
private void updateViewVisibility(int visibleType, int type, View view,
@@ -1029,6 +1032,7 @@
hiddenView.setVisible(false);
}
mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
+ notifySubtreeForAccessibilityContentChange();
}
});
fireExpandedVisibleListenerIfVisible();
@@ -1049,6 +1053,22 @@
}
}
+ @Override
+ public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
+ if (isAnimatingVisibleType()) {
+ // Don't send A11y events while animating to reduce Jank.
+ return;
+ }
+ super.notifySubtreeAccessibilityStateChanged(child, source, changeType);
+ }
+
+ private void notifySubtreeForAccessibilityContentChange() {
+ if (mParent != null) {
+ mParent.notifySubtreeAccessibilityStateChanged(this, this,
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+ }
+ }
+
/**
* @param visibleType one of the static enum types in this view
* @return the corresponding transformable view according to the given visible type
@@ -2277,6 +2297,11 @@
mHeadsUpWrapper = headsUpWrapper;
}
+ @VisibleForTesting
+ protected void setAnimationStartVisibleType(int animationStartVisibleType) {
+ mAnimationStartVisibleType = animationStartVisibleType;
+ }
+
@Override
protected void dispatchDraw(Canvas canvas) {
try {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index 9e3c576..bd4973d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -1,5 +1,7 @@
package com.android.systemui.biometrics.domain.model
+import android.hardware.biometrics.PromptContentListItemBulletedText
+import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
@@ -23,11 +25,21 @@
val title = "what"
val subtitle = "a"
val description = "request"
+ val contentView =
+ PromptVerticalListContentView.Builder()
+ .setDescription("content description")
+ .addListItem(PromptContentListItemBulletedText("content text"))
+ .build()
val fpPros = fingerprintSensorPropertiesInternal().first()
val request =
BiometricPromptRequest.Biometric(
- promptInfo(title = title, subtitle = subtitle, description = description),
+ promptInfo(
+ title = title,
+ subtitle = subtitle,
+ description = description,
+ contentView = contentView
+ ),
BiometricUserInfo(USER_ID),
BiometricOperationInfo(OPERATION_ID),
BiometricModalities(fingerprintProperties = fpPros),
@@ -36,6 +48,7 @@
assertThat(request.title).isEqualTo(title)
assertThat(request.subtitle).isEqualTo(subtitle)
assertThat(request.description).isEqualTo(description)
+ assertThat(request.contentView).isEqualTo(contentView)
assertThat(request.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
assertThat(request.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
assertThat(request.modalities)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 6170e0c..bf61c2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -18,7 +18,10 @@
import android.content.res.Configuration
import android.graphics.Point
+import android.hardware.biometrics.PromptContentListItemBulletedText
+import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.PromptVerticalListContentView
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
@@ -60,7 +63,6 @@
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -69,7 +71,6 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mock
-import org.mockito.Mockito.times
import org.mockito.junit.MockitoJUnit
private const val USER_ID = 4
@@ -101,6 +102,7 @@
private lateinit var selector: PromptSelectorInteractor
private lateinit var viewModel: PromptViewModel
private lateinit var iconViewModel: PromptIconViewModel
+ private lateinit var promptContentView: PromptContentView
@Before
fun setup() {
@@ -136,6 +138,10 @@
selector =
PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
selector.resetPrompt()
+ promptContentView =
+ PromptVerticalListContentView.Builder()
+ .addListItem(PromptContentListItemBulletedText("test"))
+ .build()
viewModel =
PromptViewModel(
@@ -1200,6 +1206,26 @@
}
}
+ @Test
+ fun descriptionOverriddenByContentView() =
+ runGenericTest(contentView = promptContentView, description = "test description") {
+ val contentView by collectLastValue(viewModel.contentView)
+ val description by collectLastValue(viewModel.description)
+
+ assertThat(description).isEqualTo("")
+ assertThat(contentView).isEqualTo(promptContentView)
+ }
+
+ @Test
+ fun descriptionWithoutContentView() =
+ runGenericTest(description = "test description") {
+ val contentView by collectLastValue(viewModel.contentView)
+ val description by collectLastValue(viewModel.description)
+
+ assertThat(description).isEqualTo("test description")
+ assertThat(contentView).isNull()
+ }
+
/** Asserts that the selected buttons are visible now. */
private suspend fun TestScope.assertButtonsVisible(
tryAgain: Boolean = false,
@@ -1219,6 +1245,8 @@
private fun runGenericTest(
doNotStart: Boolean = false,
allowCredentialFallback: Boolean = false,
+ description: String? = null,
+ contentView: PromptContentView? = null,
block: suspend TestScope.() -> Unit
) {
selector.initializePrompt(
@@ -1226,6 +1254,8 @@
allowCredentialFallback = allowCredentialFallback,
fingerprint = testCase.fingerprint,
face = testCase.face,
+ descriptionFromApp = description,
+ contentViewFromApp = contentView,
)
// put the view model in the initial authenticating state, unless explicitly skipped
@@ -1401,11 +1431,15 @@
face: FaceSensorPropertiesInternal? = null,
requireConfirmation: Boolean = false,
allowCredentialFallback: Boolean = false,
+ descriptionFromApp: String? = null,
+ contentViewFromApp: PromptContentView? = null,
) {
val info =
PromptInfo().apply {
title = "t"
subtitle = "s"
+ description = descriptionFromApp
+ contentView = contentViewFromApp
authenticators = listOf(face, fingerprint).extractAuthenticatorTypes()
isDeviceCredentialAllowed = allowCredentialFallback
isConfirmationRequested = requireConfirmation
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 e531d44..0ea4e9f 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
@@ -20,8 +20,13 @@
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -50,6 +55,8 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
@@ -102,9 +109,12 @@
FromPrimaryBouncerTransitionInteractor
private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
FromDreamingLockscreenHostedTransitionInteractor
+ private lateinit var fromGlanceableHubTransitionInteractor:
+ FromGlanceableHubTransitionInteractor
private lateinit var powerInteractor: PowerInteractor
private lateinit var keyguardInteractor: KeyguardInteractor
+ private lateinit var communalInteractor: CommunalInteractor
@Before
fun setUp() {
@@ -118,10 +128,13 @@
shadeRepository = FakeShadeRepository()
transitionRepository = spy(FakeKeyguardTransitionRepository())
powerInteractor = PowerInteractorFactory.create().powerInteractor
+ communalInteractor =
+ CommunalInteractorFactory.create(testScope = testScope).communalInteractor
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
featureFlags = FakeFeatureFlags().apply { set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) }
+ mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
keyguardInteractor = createKeyguardInteractor()
@@ -137,6 +150,13 @@
)
.keyguardTransitionInteractor
+ val glanceableHubTransitions =
+ GlanceableHubTransitions(
+ testScope,
+ transitionInteractor,
+ transitionRepository,
+ communalInteractor
+ )
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
scope = testScope,
@@ -148,6 +168,7 @@
flags = featureFlags,
shadeRepository = shadeRepository,
powerInteractor = powerInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
inWindowLauncherUnlockAnimationInteractor = {
InWindowLauncherUnlockAnimationInteractor(
InWindowLauncherUnlockAnimationRepository(),
@@ -255,6 +276,16 @@
powerInteractor = powerInteractor,
)
.apply { start() }
+
+ fromGlanceableHubTransitionInteractor =
+ FromGlanceableHubTransitionInteractor(
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ glanceableHubTransitions = glanceableHubTransitions,
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ )
+ .apply { start() }
}
@Test
@@ -1410,6 +1441,124 @@
coroutineContext.cancelChildren()
}
+ @Test
+ fun lockscreenToGlanceableHub() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to LOCKSCREEN
+ runTransitionAndSetWakefulness(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
+ runCurrent()
+
+ // WHEN a glanceable hub transition starts
+ val currentScene = CommunalSceneKey.Blank
+ val targetScene = CommunalSceneKey.Communal
+
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalInteractor.setTransitionState(transitionState)
+ progress.value = .1f
+ runCurrent()
+
+ // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ assertThat(info.ownerName)
+ .isEqualTo(FromLockscreenTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.animator).isNull() // transition should be manually animated
+
+ // WHEN the user stops dragging and the glanceable hub opening is cancelled
+ clearInvocations(transitionRepository)
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(currentScene)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
+ val info2 =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ assertThat(info2.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNull() // transition should be manually animated
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun glanceableHubToLockscreen() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+ runCurrent()
+
+ // WHEN a transition away from glanceable hub starts
+ val currentScene = CommunalSceneKey.Communal
+ val targetScene = CommunalSceneKey.Blank
+
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalInteractor.setTransitionState(transitionState)
+ progress.value = .1f
+ runCurrent()
+
+ // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ assertThat(info.ownerName)
+ .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNull() // transition should be manually animated
+
+ // WHEN the user stops dragging and the glanceable hub closing is cancelled
+ clearInvocations(transitionRepository)
+ runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN)
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(currentScene)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+ val info2 =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ assertThat(info2.from).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info2.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.animator).isNull() // transition should be manually animated
+
+ coroutineContext.cancelChildren()
+ }
+
private fun createKeyguardInteractor(): KeyguardInteractor {
return KeyguardInteractorFactory.create(
featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 233cb3d..791c080 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -56,6 +56,8 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -67,6 +69,7 @@
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -200,6 +203,8 @@
new ConfigurationInteractor(configurationRepository),
shadeRepository,
() -> sceneInteractor);
+ CommunalInteractor communalInteractor =
+ CommunalInteractorFactory.create().getCommunalInteractor();
FakeKeyguardTransitionRepository keyguardTransitionRepository =
new FakeKeyguardTransitionRepository();
@@ -222,6 +227,12 @@
featureFlags,
shadeRepository,
powerInteractor,
+ new GlanceableHubTransitions(
+ mTestScope,
+ keyguardTransitionInteractor,
+ keyguardTransitionRepository,
+ communalInteractor
+ ),
() ->
new InWindowLauncherUnlockAnimationInteractor(
new InWindowLauncherUnlockAnimationRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index ee7c6c8..a11839c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade
import android.content.Context
-import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.KeyEvent
@@ -25,50 +24,29 @@
import android.view.View
import android.view.ViewGroup
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardMessageAreaController
import com.android.keyguard.KeyguardSecurityContainerController
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
-import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
-import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.bouncer.ui.binder.BouncerViewBinder
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.compose.ComposeFacade.isComposeAvailable
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
-import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
-import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
-import com.android.systemui.log.BouncerLogger
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.DragDownHelper
@@ -86,16 +64,15 @@
import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
-import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
@@ -110,9 +87,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.Optional
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -133,7 +109,6 @@
@Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var ambientState: AmbientState
- @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
@@ -156,8 +131,6 @@
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var dragDownHelper: DragDownHelper
@Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
- @Mock
- lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
@Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@@ -224,55 +197,18 @@
dumpManager,
pulsingGestureListener,
mLockscreenHostedDreamGestureListener,
- keyguardBouncerViewModel,
- keyguardBouncerComponentFactory,
- mock(KeyguardMessageAreaController.Factory::class.java),
keyguardTransitionInteractor,
- primaryBouncerToGoneTransitionViewModel,
mGlanceableHubContainerController,
notificationLaunchAnimationInteractor,
featureFlagsClassic,
fakeClock,
- BouncerMessageInteractor(
- repository = BouncerMessageRepositoryImpl(),
- userRepository = FakeUserRepository(),
- countDownTimerUtil = mock(CountDownTimerUtil::class.java),
- updateMonitor = mock(KeyguardUpdateMonitor::class.java),
- biometricSettingsRepository = FakeBiometricSettingsRepository(),
- applicationScope = testScope.backgroundScope,
- trustRepository = FakeTrustRepository(),
- systemPropertiesHelper = mock(SystemPropertiesHelper::class.java),
- primaryBouncerInteractor =
- PrimaryBouncerInteractor(
- FakeKeyguardBouncerRepository(),
- mock(BouncerView::class.java),
- mock(Handler::class.java),
- mock(KeyguardStateController::class.java),
- mock(KeyguardSecurityModel::class.java),
- mock(PrimaryBouncerCallbackInteractor::class.java),
- mock(FalsingCollector::class.java),
- mock(DismissCallbackRegistry::class.java),
- context,
- mock(KeyguardUpdateMonitor::class.java),
- FakeTrustRepository(),
- testScope.backgroundScope,
- mSelectedUserInteractor,
- mock(DeviceEntryFaceAuthInteractor::class.java)
- ),
- facePropertyRepository = FakeFacePropertyRepository(),
- deviceEntryFingerprintAuthRepository =
- FakeDeviceEntryFingerprintAuthRepository(),
- faceAuthRepository = FakeDeviceEntryFaceAuthRepository(),
- securityModel = mock(KeyguardSecurityModel::class.java),
- ),
- BouncerLogger(logcatLogBuffer("BouncerLog")),
sysUIKeyEventHandler,
quickSettingsController,
primaryBouncerInteractor,
alternateBouncerInteractor,
- mSelectedUserInteractor,
- { mock (JavaAdapter::class.java )},
+ { mock(JavaAdapter::class.java) },
{ mock(AlternateBouncerDependencies::class.java) },
+ mock(BouncerViewBinder::class.java)
)
underTest.setupExpandedStatusBar()
underTest.setDragDownHelper(dragDownHelper)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 33d60ea..0c4bf81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -15,51 +15,28 @@
*/
package com.android.systemui.shade
-import android.os.Handler
import android.os.SystemClock
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.MotionEvent
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardMessageAreaController
import com.android.keyguard.KeyguardSecurityContainerController
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
-import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
-import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
-import com.android.systemui.log.BouncerLogger
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.DragDownHelper
@@ -77,11 +54,8 @@
import com.android.systemui.statusbar.phone.DozeScrimController
import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -137,7 +111,6 @@
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock
private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
- @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock
@@ -150,10 +123,6 @@
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
- @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
- @Mock
- private lateinit var primaryBouncerToGoneTransitionViewModel:
- PrimaryBouncerToGoneTransitionViewModel
@Captor
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -217,55 +186,18 @@
dumpManager,
pulsingGestureListener,
mLockscreenHostedDreamGestureListener,
- keyguardBouncerViewModel,
- keyguardBouncerComponentFactory,
- Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
keyguardTransitionInteractor,
- primaryBouncerToGoneTransitionViewModel,
mGlanceableHubContainerController,
NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository()),
featureFlags,
FakeSystemClock(),
- BouncerMessageInteractor(
- repository = BouncerMessageRepositoryImpl(),
- userRepository = FakeUserRepository(),
- countDownTimerUtil = Mockito.mock(CountDownTimerUtil::class.java),
- updateMonitor = Mockito.mock(KeyguardUpdateMonitor::class.java),
- biometricSettingsRepository = FakeBiometricSettingsRepository(),
- applicationScope = testScope.backgroundScope,
- trustRepository = FakeTrustRepository(),
- systemPropertiesHelper = Mockito.mock(SystemPropertiesHelper::class.java),
- primaryBouncerInteractor =
- PrimaryBouncerInteractor(
- FakeKeyguardBouncerRepository(),
- Mockito.mock(BouncerView::class.java),
- Mockito.mock(Handler::class.java),
- Mockito.mock(KeyguardStateController::class.java),
- Mockito.mock(KeyguardSecurityModel::class.java),
- Mockito.mock(PrimaryBouncerCallbackInteractor::class.java),
- Mockito.mock(FalsingCollector::class.java),
- Mockito.mock(DismissCallbackRegistry::class.java),
- context,
- Mockito.mock(KeyguardUpdateMonitor::class.java),
- FakeTrustRepository(),
- testScope.backgroundScope,
- mSelectedUserInteractor,
- mock(),
- ),
- facePropertyRepository = FakeFacePropertyRepository(),
- deviceEntryFingerprintAuthRepository =
- FakeDeviceEntryFingerprintAuthRepository(),
- faceAuthRepository = FakeDeviceEntryFaceAuthRepository(),
- securityModel = Mockito.mock(KeyguardSecurityModel::class.java),
- ),
- BouncerLogger(logcatLogBuffer("BouncerLog")),
Mockito.mock(SysUIKeyEventHandler::class.java),
quickSettingsController,
primaryBouncerInteractor,
alternateBouncerInteractor,
- mSelectedUserInteractor,
{ Mockito.mock(JavaAdapter::class.java) },
{ Mockito.mock(AlternateBouncerDependencies::class.java) },
+ mock()
)
controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index f0a2303..a369f82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -42,6 +42,8 @@
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
@@ -55,6 +57,7 @@
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -235,6 +238,8 @@
new ConfigurationInteractor(configurationRepository),
mShadeRepository,
() -> sceneInteractor);
+ CommunalInteractor communalInteractor =
+ CommunalInteractorFactory.create().getCommunalInteractor();
FakeKeyguardTransitionRepository keyguardTransitionRepository =
new FakeKeyguardTransitionRepository();
@@ -257,6 +262,12 @@
featureFlags,
mShadeRepository,
powerInteractor,
+ new GlanceableHubTransitions(
+ mTestScope,
+ keyguardTransitionInteractor,
+ keyguardTransitionRepository,
+ communalInteractor
+ ),
() ->
new InWindowLauncherUnlockAnimationInteractor(
new InWindowLauncherUnlockAnimationRepository(),
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 1a6a067..4a365b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -36,6 +37,7 @@
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -139,6 +141,7 @@
{ fromLockscreenTransitionInteractor },
{ fromPrimaryBouncerTransitionInteractor }
)
+ val communalInteractor = CommunalInteractorFactory.create().communalInteractor
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
keyguardTransitionRepository,
@@ -150,6 +153,12 @@
featureFlags,
shadeRepository,
powerInteractor,
+ GlanceableHubTransitions(
+ testScope,
+ keyguardTransitionInteractor,
+ keyguardTransitionRepository,
+ communalInteractor
+ ),
{
InWindowLauncherUnlockAnimationInteractor(
InWindowLauncherUnlockAnimationRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 5549fee..91e4666 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.notification.FeedbackIcon
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
@@ -49,6 +50,8 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
@@ -74,7 +77,8 @@
@Before
fun setup() {
initMocks(this)
- fakeParent = FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE }
+ fakeParent =
+ spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE })
row =
spy(
ExpandableNotificationRow(mContext, /* attrs= */ null).apply {
@@ -558,6 +562,35 @@
verify(view.headsUpWrapper, never()).setAnimationsRunning(false)
}
+ @Test
+ fun notifySubtreeAccessibilityStateChanged_notifiesParent() {
+ // Given: a contentView is created
+ val view = createContentView()
+ clearInvocations(fakeParent)
+
+ // When: the contentView is notified for an A11y change
+ view.notifySubtreeAccessibilityStateChanged(view.contractedChild, view.contractedChild, 0)
+
+ // Then: the contentView propagates the event to its parent
+ verify(fakeParent).notifySubtreeAccessibilityStateChanged(any(), any(), anyInt())
+ }
+
+ @Test
+ fun notifySubtreeAccessibilityStateChanged_animatingContentView_dontNotifyParent() {
+ // Given: a collapsed contentView is created
+ val view = createContentView()
+ clearInvocations(fakeParent)
+
+ // And: it is animating to expanded
+ view.setAnimationStartVisibleType(NotificationContentView.VISIBLE_TYPE_EXPANDED)
+
+ // When: the contentView is notified for an A11y change
+ view.notifySubtreeAccessibilityStateChanged(view.contractedChild, view.contractedChild, 0)
+
+ // Then: the contentView DOESN'T propagates the event to its parent
+ verify(fakeParent, never()).notifySubtreeAccessibilityStateChanged(any(), any(), anyInt())
+ }
+
private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
mock<ExpandableNotificationRow>().apply {
whenever(this.entry).thenReturn(notificationEntry)
@@ -597,7 +630,7 @@
}
private fun createContentView(
- isSystemExpanded: Boolean,
+ isSystemExpanded: Boolean = false,
contractedView: View = createViewWithHeight(contractedHeight),
expandedView: View = createViewWithHeight(expandedHeight),
headsUpView: View = createViewWithHeight(contractedHeight),
@@ -647,5 +680,5 @@
}
private fun NotificationContentView.clearInvocations() {
- Mockito.clearInvocations(contractedWrapper, expandedWrapper, headsUpWrapper)
+ clearInvocations(contractedWrapper, expandedWrapper, headsUpWrapper)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 89842d6..f63f79f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.collectLastValue
import com.android.systemui.collectValues
+import com.android.systemui.communal.dagger.CommunalModule
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -58,6 +59,7 @@
modules =
[
SysUITestModule::class,
+ CommunalModule::class,
]
)
interface TestComponent : SysUITestComponent<CollapsedStatusBarViewModelImpl> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 30434c8..8920d4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -99,6 +99,8 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -111,6 +113,7 @@
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -439,6 +442,8 @@
() -> keyguardInteractor,
() -> mFromLockscreenTransitionInteractor,
() -> mFromPrimaryBouncerTransitionInteractor);
+ CommunalInteractor communalInteractor =
+ CommunalInteractorFactory.create().getCommunalInteractor();
mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor(
keyguardTransitionRepository,
@@ -450,6 +455,12 @@
featureFlags,
shadeRepository,
powerInteractor,
+ new GlanceableHubTransitions(
+ mTestScope,
+ keyguardTransitionInteractor,
+ keyguardTransitionRepository,
+ communalInteractor
+ ),
() ->
new InWindowLauncherUnlockAnimationInteractor(
new InWindowLauncherUnlockAnimationRepository(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index b28af46..fdb9b30 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -25,6 +25,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardViewController
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.ScreenLifecycle
@@ -111,6 +112,7 @@
@get:Provides val zenModeController: ZenModeController = mock(),
@get:Provides val systemUIDialogManager: SystemUIDialogManager = mock(),
@get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
+ @get:Provides val communalInteractor: CommunalInteractor = mock(),
// log buffers
@get:[Provides BroadcastDispatcherLog]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorKosmos.kt
new file mode 100644
index 0000000..4a089d3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayStateRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.mockito.mock
+import java.util.concurrent.Executor
+
+val Kosmos.displayStateInteractor by Fixture {
+ DisplayStateInteractorImpl(
+ applicationScope = applicationCoroutineScope,
+ context = applicationContext,
+ mainExecutor = mock<Executor>(),
+ displayStateRepository = displayStateRepository,
+ displayRepository = displayRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
new file mode 100644
index 0000000..e262066
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.fingerprintPropertyInteractor by Fixture {
+ FingerprintPropertyInteractor(
+ context = applicationContext,
+ repository = fingerprintPropertyRepository,
+ configurationInteractor = configurationInteractor,
+ displayStateInteractor = displayStateInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
index ff5179a..8010261 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
@@ -2,6 +2,7 @@
import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel
import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
import com.android.systemui.dagger.SysUISingleton
import dagger.Binds
@@ -53,6 +54,7 @@
override val alternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
private val _sideFpsShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
+ override var bouncerDismissActionModel: BouncerDismissActionModel? = null
override fun setPrimaryScrimmed(isScrimmed: Boolean) {
_primaryBouncerScrimmed.value = isScrimmed
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt
new file mode 100644
index 0000000..7946446
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.communalMediaRepository: CommunalMediaRepository by
+ Kosmos.Fixture { fakeCommunalMediaRepository }
+val Kosmos.fakeCommunalMediaRepository by Kosmos.Fixture { FakeCommunalMediaRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
new file mode 100644
index 0000000..be56d2b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.communalRepository: CommunalRepository by Kosmos.Fixture { fakeCommunalRepository }
+val Kosmos.fakeCommunalRepository by Kosmos.Fixture { FakeCommunalRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt
new file mode 100644
index 0000000..5a17f2f8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+var Kosmos.communalWidgetRepository: CommunalWidgetRepository by
+ Kosmos.Fixture { fakeCommunalWidgetRepository }
+val Kosmos.fakeCommunalWidgetRepository by
+ Kosmos.Fixture { FakeCommunalWidgetRepository(applicationCoroutineScope) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayRepositoryKosmos.kt
new file mode 100644
index 0000000..048ea3c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.displayRepository by Fixture { FakeDisplayRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayStateRepositoryKosmos.kt
new file mode 100644
index 0000000..4a71a09
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayStateRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.displayStateRepository by Fixture { FakeDisplayStateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
new file mode 100644
index 0000000..59f0ec3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.appwidget.AppWidgetHost
+import com.android.systemui.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.data.repository.communalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.smartspace.data.repository.smartspaceRepository
+import org.mockito.Mockito.mock
+
+val Kosmos.communalInteractor by
+ Kosmos.Fixture {
+ CommunalInteractor(
+ communalRepository = communalRepository,
+ widgetRepository = communalWidgetRepository,
+ mediaRepository = communalMediaRepository,
+ smartspaceRepository = smartspaceRepository,
+ keyguardInteractor = keyguardInteractor,
+ appWidgetHost = mock(AppWidgetHost::class.java),
+ editWidgetsActivityStarter = mock(EditWidgetsActivityStarter::class.java),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index b1a0b67..3b38342 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -37,6 +37,7 @@
flags = featureFlagsClassic,
shadeRepository = shadeRepository,
powerInteractor = powerInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
inWindowLauncherUnlockAnimationInteractor =
Lazy { inWindowLauncherUnlockAnimationInteractor },
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
new file mode 100644
index 0000000..294b5ba
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.glanceableHubTransitions by
+ Kosmos.Fixture {
+ GlanceableHubTransitions(
+ scope = applicationCoroutineScope,
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ communalInteractor = communalInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..28fce77
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.glanceableHubToLockscreenTransitionViewModel by Fixture {
+ GlanceableHubToLockscreenTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 933f50c..5564767 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -39,5 +39,7 @@
screenOffAnimationController = screenOffAnimationController,
aodBurnInViewModel = aodBurnInViewModel,
aodAlphaViewModel = aodAlphaViewModel,
+ lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..9fe4ea3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.lockscreenToGlanceableHubTransitionViewModel by Fixture {
+ LockscreenToGlanceableHubTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
new file mode 100644
index 0000000..e671d45
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.smartspace.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.smartspaceRepository: SmartspaceRepository by Kosmos.Fixture { fakeSmartspaceRepository }
+val Kosmos.fakeSmartspaceRepository by Kosmos.Fixture { FakeSmartspaceRepository() }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index cab2d74..5407af7 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -362,6 +362,7 @@
packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
packageFilter.addDataScheme("package");
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
packageFilter, null, mCallbackHandler);
@@ -402,6 +403,7 @@
boolean added = false;
boolean changed = false;
boolean componentsModified = false;
+ int clearedUid = -1;
final String pkgList[];
switch (action) {
@@ -416,6 +418,10 @@
case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
break;
+ case Intent.ACTION_PACKAGE_DATA_CLEARED:
+ pkgList = null;
+ clearedUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ break;
default: {
Uri uri = intent.getData();
if (uri == null) {
@@ -430,7 +436,7 @@
changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
}
}
- if (pkgList == null || pkgList.length == 0) {
+ if ((pkgList == null || pkgList.length == 0) && clearedUid == -1) {
return;
}
@@ -461,6 +467,8 @@
}
}
}
+ } else if (clearedUid != -1) {
+ componentsModified |= clearPreviewsForUidLocked(clearedUid);
} else {
// If the package is being updated, we'll receive a PACKAGE_ADDED
// shortly, otherwise it is removed permanently.
@@ -486,6 +494,19 @@
}
}
+ @GuardedBy("mLock")
+ private boolean clearPreviewsForUidLocked(int clearedUid) {
+ boolean changed = false;
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ if (provider.id.uid == clearedUid) {
+ changed |= provider.clearGeneratedPreviewsLocked();
+ }
+ }
+ return changed;
+ }
+
/**
* Reload all widgets' masked state for the given user and its associated profiles, including
* due to user not being available and package suspension.
@@ -3904,6 +3925,124 @@
}
}
+ @Override
+ @Nullable
+ public RemoteViews getWidgetPreview(@NonNull String callingPackage,
+ @NonNull ComponentName providerComponent, int profileId,
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
+ final int callingUserId = UserHandle.getCallingUserId();
+ if (DEBUG) {
+ Slog.i(TAG, "getWidgetPreview() " + callingUserId);
+ }
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+ ensureWidgetCategoryCombinationIsValid(widgetCategory);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(profileId);
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ final ComponentName componentName = provider.id.componentName;
+ if (provider.zombie || !providerComponent.equals(componentName)) {
+ continue;
+ }
+
+ final AppWidgetProviderInfo info = provider.getInfoLocked(mContext);
+ final int providerProfileId = info.getProfile().getIdentifier();
+ if (providerProfileId != profileId) {
+ continue;
+ }
+
+ // Allow access to this provider if it is from the calling package or the caller has
+ // BIND_APPWIDGET permission.
+ final int callingUid = Binder.getCallingUid();
+ final String providerPackageName = componentName.getPackageName();
+ final boolean providerIsInCallerProfile =
+ mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+ providerPackageName, providerProfileId);
+ final boolean shouldFilterAppAccess = mPackageManagerInternal.filterAppAccess(
+ providerPackageName, callingUid, providerProfileId);
+ final boolean providerIsInCallerPackage =
+ mSecurityPolicy.isProviderInPackageForUid(provider, callingUid,
+ callingPackage);
+ final boolean hasBindAppWidgetPermission =
+ mSecurityPolicy.hasCallerBindPermissionOrBindWhiteListedLocked(
+ callingPackage);
+ if (providerIsInCallerProfile && !shouldFilterAppAccess
+ && (providerIsInCallerPackage || hasBindAppWidgetPermission)) {
+ return provider.getGeneratedPreviewLocked(widgetCategory);
+ }
+ }
+ }
+ throw new IllegalArgumentException(
+ providerComponent + " is not a valid AppWidget provider");
+ }
+
+ @Override
+ public void setWidgetPreview(@NonNull ComponentName providerComponent,
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
+ @NonNull RemoteViews preview) {
+ final int userId = UserHandle.getCallingUserId();
+ if (DEBUG) {
+ Slog.i(TAG, "setWidgetPreview() " + userId);
+ }
+
+ // Make sure callers only set previews for their own package.
+ mSecurityPolicy.enforceCallFromPackage(providerComponent.getPackageName());
+
+ ensureWidgetCategoryCombinationIsValid(widgetCategories);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ final ProviderId providerId = new ProviderId(Binder.getCallingUid(), providerComponent);
+ final Provider provider = lookupProviderLocked(providerId);
+ if (provider == null) {
+ throw new IllegalArgumentException(
+ providerComponent + " is not a valid AppWidget provider");
+ }
+ provider.setGeneratedPreviewLocked(widgetCategories, preview);
+ scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ }
+ }
+
+ @Override
+ public void removeWidgetPreview(@NonNull ComponentName providerComponent,
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
+ final int userId = UserHandle.getCallingUserId();
+ if (DEBUG) {
+ Slog.i(TAG, "removeWidgetPreview() " + userId);
+ }
+
+ // Make sure callers only remove previews for their own package.
+ mSecurityPolicy.enforceCallFromPackage(providerComponent.getPackageName());
+
+ ensureWidgetCategoryCombinationIsValid(widgetCategories);
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ final ProviderId providerId = new ProviderId(Binder.getCallingUid(), providerComponent);
+ final Provider provider = lookupProviderLocked(providerId);
+ if (provider == null) {
+ throw new IllegalArgumentException(
+ providerComponent + " is not a valid AppWidget provider");
+ }
+ final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
+ if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ }
+ }
+
+ private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) {
+ int validCategories = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
+ | AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
+ | AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX;
+ int invalid = ~validCategories;
+ if ((widgetCategories & invalid) != 0) {
+ throw new IllegalArgumentException(widgetCategories
+ + " is not a valid widget category combination");
+ }
+ }
+
private final class CallbackHandler extends Handler {
public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
@@ -4201,6 +4340,12 @@
ArrayList<Widget> widgets = new ArrayList<>();
PendingIntent broadcast;
String infoTag;
+ SparseArray<RemoteViews> generatedPreviews = new SparseArray<>(3);
+ private static final int[] WIDGET_CATEGORY_FLAGS = new int[]{
+ AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX,
+ };
boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
@@ -4234,7 +4379,7 @@
return false;
}
- @GuardedBy("AppWidgetServiceImpl.mLock")
+ @GuardedBy("this.mLock")
public AppWidgetProviderInfo getInfoLocked(Context context) {
if (!mInfoParsed) {
// parse
@@ -4250,6 +4395,7 @@
}
if (newInfo != null) {
info = newInfo;
+ updateGeneratedPreviewCategoriesLocked();
}
}
mInfoParsed = true;
@@ -4279,6 +4425,62 @@
mInfoParsed = true;
}
+ @GuardedBy("this.mLock")
+ @Nullable
+ public RemoteViews getGeneratedPreviewLocked(
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
+ for (int i = 0; i < generatedPreviews.size(); i++) {
+ if ((widgetCategories & generatedPreviews.keyAt(i)) != 0) {
+ return generatedPreviews.valueAt(i);
+ }
+ }
+ return null;
+ }
+
+ @GuardedBy("this.mLock")
+ public void setGeneratedPreviewLocked(
+ @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
+ @NonNull RemoteViews preview) {
+ for (int flag : WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ generatedPreviews.put(flag, preview);
+ }
+ }
+ updateGeneratedPreviewCategoriesLocked();
+ }
+
+ @GuardedBy("this.mLock")
+ public boolean removeGeneratedPreviewLocked(int widgetCategories) {
+ boolean changed = false;
+ for (int flag : WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ changed |= generatedPreviews.removeReturnOld(flag) != null;
+ }
+ }
+ if (changed) {
+ updateGeneratedPreviewCategoriesLocked();
+ }
+ return changed;
+ }
+
+ @GuardedBy("this.mLock")
+ public boolean clearGeneratedPreviewsLocked() {
+ if (generatedPreviews.size() > 0) {
+ generatedPreviews.clear();
+ updateGeneratedPreviewCategoriesLocked();
+ return true;
+ }
+ return false;
+ }
+
+ @GuardedBy("this.mLock")
+ private void updateGeneratedPreviewCategoriesLocked() {
+ info.generatedPreviewCategories = 0;
+ for (int i = 0; i < generatedPreviews.size(); i++) {
+ info.generatedPreviewCategories |= generatedPreviews.keyAt(i);
+ }
+ }
+
@Override
public String toString() {
return "Provider{" + id + (zombie ? " Z" : "") + '}';
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 6a81425..a856f42 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -4271,13 +4271,19 @@
if (value != null) {
viewState.setCurrentValue(value);
}
-
+ boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
if (shouldRequestSecondaryProvider(flags)) {
if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
id, viewState, flags)) {
Slog.v(TAG, "Started a new fill request for secondary provider.");
return;
}
+
+ FillResponse response = viewState.getSecondaryResponse();
+ if (response != null) {
+ logPresentationStatsOnViewEntered(response, isCredmanRequested);
+ }
+
// If the ViewState is ready to be displayed, onReady() will be called.
viewState.update(value, virtualBounds, flags);
@@ -4363,15 +4369,9 @@
return;
}
- if (viewState.getResponse() != null) {
- boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
- FillResponse response = viewState.getResponse();
- mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
- mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
- mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
- mFieldClassificationIdSnapshot);
- mPresentationStatsEventLogger.maybeSetAvailableCount(
- response.getDatasets(), mCurrentViewId);
+ FillResponse response = viewState.getResponse();
+ if (response != null) {
+ logPresentationStatsOnViewEntered(response, isCredmanRequested);
}
if (isSameViewEntered) {
@@ -4412,6 +4412,17 @@
}
@GuardedBy("mLock")
+ private void logPresentationStatsOnViewEntered(FillResponse response,
+ boolean isCredmanRequested) {
+ mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
+ mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
+ mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
+ mFieldClassificationIdSnapshot);
+ mPresentationStatsEventLogger.maybeSetAvailableCount(
+ response.getDatasets(), mCurrentViewId);
+ }
+
+ @GuardedBy("mLock")
private void hideAugmentedAutofillLocked(@NonNull ViewState viewState) {
if ((viewState.getState()
& ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 25ec683..4db9ead 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4869,8 +4869,8 @@
final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
.getSortedInputMethodAndSubtypeList(
- showAuxSubtypes, isScreenLocked, false, mContext,
- mMethodMap, mSettings.getCurrentUserId());
+ showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
+ mContext, mMethodMap, mSettings.getCurrentUserId());
mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
lastInputMethodId, lastInputMethodSubtypeId, imList);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index fb57c09..58a68f2a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -255,14 +255,6 @@
return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId);
}
- private void putBoolean(String key, boolean value) {
- SecureSettingsWrapper.putBoolean(key, value, mCurrentUserId);
- }
-
- private boolean getBoolean(String key, boolean defaultValue) {
- return SecureSettingsWrapper.getBoolean(key, defaultValue, mCurrentUserId);
- }
-
ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
return getEnabledInputMethodListWithFilterLocked(null /* matchingCondition */);
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 2128c991..e226953 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -139,6 +139,7 @@
import com.android.server.power.batterysaver.BatterySaverPolicy;
import com.android.server.power.batterysaver.BatterySaverStateMachine;
import com.android.server.power.batterysaver.BatterySavingStats;
+import com.android.server.power.feature.PowerManagerFlags;
import dalvik.annotation.optimization.NeverCompile;
@@ -329,6 +330,8 @@
// True if battery saver is supported on this device.
private final boolean mBatterySaverSupported;
+ private final PowerManagerFlags mFeatureFlags;
+
private boolean mDisableScreenWakeLocksWhileCached;
private LightsManager mLightsManager;
@@ -1079,6 +1082,10 @@
DeviceConfigParameterProvider createDeviceConfigParameterProvider() {
return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
}
+
+ PowerManagerFlags getFlags() {
+ return new PowerManagerFlags();
+ }
}
/** Interface for checking an app op permission */
@@ -1145,6 +1152,7 @@
mNativeWrapper = injector.createNativeWrapper();
mSystemProperties = injector.createSystemPropertiesWrapper();
mClock = injector.createClock();
+ mFeatureFlags = injector.getFlags();
mInjector = injector;
mHandlerThread = new ServiceThread(TAG,
@@ -4802,6 +4810,7 @@
mAmbientDisplaySuppressionController.dump(pw);
mLowPowerStandbyController.dump(pw);
+ mFeatureFlags.dump(pw);
}
private void dumpProto(FileDescriptor fd) {
diff --git a/services/core/java/com/android/server/power/feature/Android.bp b/services/core/java/com/android/server/power/feature/Android.bp
new file mode 100644
index 0000000..2295b41
--- /dev/null
+++ b/services/core/java/com/android/server/power/feature/Android.bp
@@ -0,0 +1,7 @@
+aconfig_declarations {
+ name: "power_flags",
+ package: "com.android.server.power.feature.flags",
+ srcs: [
+ "*.aconfig",
+ ],
+}
diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
new file mode 100644
index 0000000..a5a7069
--- /dev/null
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.feature;
+
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.server.power.feature.flags.Flags;
+
+import java.io.PrintWriter;
+import java.util.function.Supplier;
+
+/**
+ * Utility class to read the flags used in the power manager server.
+ */
+public class PowerManagerFlags {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "PowerManagerFlags";
+
+ private final FlagState mEarlyScreenTimeoutDetectorFlagState = new FlagState(
+ Flags.FLAG_ENABLE_EARLY_SCREEN_TIMEOUT_DETECTOR,
+ Flags::enableEarlyScreenTimeoutDetector);
+
+ /** Returns whether early-screen-timeout-detector is enabled on not. */
+ public boolean isEarlyScreenTimeoutDetectorEnabled() {
+ return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
+ }
+
+ /**
+ * dumps all flagstates
+ * @param pw printWriter
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("PowerManagerFlags:");
+ pw.println(" " + mEarlyScreenTimeoutDetectorFlagState);
+ }
+
+ private static class FlagState {
+
+ private final String mName;
+
+ private final Supplier<Boolean> mFlagFunction;
+ private boolean mEnabledSet;
+ private boolean mEnabled;
+
+ private FlagState(String name, Supplier<Boolean> flagFunction) {
+ mName = name;
+ mFlagFunction = flagFunction;
+ }
+
+ private boolean isEnabled() {
+ if (mEnabledSet) {
+ if (DEBUG) {
+ Slog.d(TAG, mName + ": mEnabled. Recall = " + mEnabled);
+ }
+ return mEnabled;
+ }
+ mEnabled = mFlagFunction.get();
+ if (DEBUG) {
+ Slog.d(TAG, mName + ": mEnabled. Flag value = " + mEnabled);
+ }
+ mEnabledSet = true;
+ return mEnabled;
+ }
+
+ @Override
+ public String toString() {
+ // remove com.android.server.power.feature.flags. from the beginning of the name.
+ // align all isEnabled() values.
+ // Adjust lengths if we end up with longer names
+ final int nameLength = mName.length();
+ return TextUtils.substring(mName, 39, nameLength) + ": "
+ + TextUtils.formatSimple("%" + (91 - nameLength) + "s%s", " " , isEnabled())
+ + " (def:" + mFlagFunction.get() + ")";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
new file mode 100644
index 0000000..c8c16db
--- /dev/null
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -0,0 +1,11 @@
+package: "com.android.server.power.feature.flags"
+
+# Important: Flags must be accessed through PowerManagerFlags.
+
+flag {
+ name: "enable_early_screen_timeout_detector"
+ namespace: "power_manager"
+ description: "Feature flag for Early Screen Timeout detector"
+ bug: "309861917"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index fcc0de1..3094b18 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1910,6 +1910,12 @@
// Transforms do not need to be persisted; the IkeSession will keep them alive
mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
+ if (direction == IpSecManager.DIRECTION_IN
+ && mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform);
+ }
+
// For inbound transforms, additionally allow forwarded traffic to bridge to DUN (as
// needed)
final Set<Integer> exposedCaps = mConnectionConfig.getAllExposedCapabilities();
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 48df44b..3f8d39e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -30,6 +30,7 @@
import android.annotation.Nullable;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpSecTransform;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -52,6 +53,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
import com.android.server.vcn.util.LogUtils;
import java.util.ArrayList;
@@ -201,6 +203,14 @@
NetworkCallback oldWifiExitRssiThresholdCallback = mWifiExitRssiThresholdCallback;
List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
mCellBringupCallbacks.clear();
+
+ if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+ evaluator.close();
+ }
+ }
+
mUnderlyingNetworkRecords.clear();
// Register new callbacks. Make-before-break; always register new callbacks before removal
@@ -417,11 +427,42 @@
if (oldSnapshot
.getAllSubIdsInGroup(mSubscriptionGroup)
.equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
+
+ if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ reevaluateNetworks();
+ }
return;
}
registerOrUpdateNetworkRequests();
}
+ /**
+ * Pass the IpSecTransform of the VCN to UnderlyingNetworkController for metric monitoring
+ *
+ * <p>Caller MUST call it when IpSecTransforms have been created for VCN creation or migration
+ */
+ public void updateInboundTransform(
+ @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) {
+ if (!mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ || !mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ logWtf("#updateInboundTransform: unexpected call; flags missing");
+ return;
+ }
+
+ Objects.requireNonNull(currentNetwork, "currentNetwork is null");
+ Objects.requireNonNull(transform, "transform is null");
+
+ if (mCurrentRecord == null
+ || mRouteSelectionCallback == null
+ || !Objects.equals(currentNetwork.network, mCurrentRecord.network)) {
+ // The caller (VcnGatewayConnection) is out-of-dated. Ignore this call.
+ return;
+ }
+
+ mUnderlyingNetworkRecords.get(mCurrentRecord.network).setInboundTransform(transform);
+ }
+
/** Tears down this Tracker, and releases all underlying network requests. */
public void teardown() {
mVcnContext.ensureRunningOnLooperThread();
@@ -438,7 +479,7 @@
private TreeSet<UnderlyingNetworkEvaluator> getSortedUnderlyingNetworks() {
TreeSet<UnderlyingNetworkEvaluator> sorted =
- new TreeSet<>(UnderlyingNetworkEvaluator.getComparator());
+ new TreeSet<>(UnderlyingNetworkEvaluator.getComparator(mVcnContext));
for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
if (evaluator.getPriorityClass() != NetworkPriorityClassifier.PRIORITY_INVALID) {
@@ -525,11 +566,17 @@
mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
mSubscriptionGroup,
mLastSnapshot,
- mCarrierConfig));
+ mCarrierConfig,
+ new NetworkEvaluatorCallbackImpl()));
}
@Override
public void onLost(@NonNull Network network) {
+ if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ mUnderlyingNetworkRecords.get(network).close();
+ }
+
mUnderlyingNetworkRecords.remove(network);
reevaluateNetworks();
@@ -598,6 +645,21 @@
}
}
+ @VisibleForTesting
+ class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback {
+ @Override
+ public void onEvaluationResultChanged() {
+ if (!mVcnContext.isFlagNetworkMetricMonitorEnabled()
+ || !mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ logWtf("#onEvaluationResultChanged: unexpected call; flags missing");
+ return;
+ }
+
+ mVcnContext.ensureRunningOnLooperThread();
+ reevaluateNetworks();
+ }
+ }
+
private String getLogPrefix() {
return "("
+ LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
@@ -690,21 +752,22 @@
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static class Dependencies {
- /** Construct a new UnderlyingNetworkEvaluator */
public UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator(
@NonNull VcnContext vcnContext,
@NonNull Network network,
@NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
@NonNull ParcelUuid subscriptionGroup,
@NonNull TelephonySubscriptionSnapshot lastSnapshot,
- @Nullable PersistableBundleWrapper carrierConfig) {
+ @Nullable PersistableBundleWrapper carrierConfig,
+ @NonNull NetworkEvaluatorCallback evaluatorCallback) {
return new UnderlyingNetworkEvaluator(
vcnContext,
network,
underlyingNetworkTemplates,
subscriptionGroup,
lastSnapshot,
- carrierConfig);
+ carrierConfig,
+ evaluatorCallback);
}
}
}
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
index c124a19..2f4cf5e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -16,23 +16,32 @@
package com.android.server.vcn.routeselection;
+import static com.android.server.VcnManagementService.LOCAL_LOG;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.IpSecTransform;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.vcn.VcnManager;
import android.net.vcn.VcnUnderlyingNetworkTemplate;
+import android.os.Handler;
import android.os.ParcelUuid;
+import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.VcnContext;
+import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
/**
* UnderlyingNetworkEvaluator evaluates the quality and priority class of a network candidate for
@@ -43,20 +52,41 @@
public class UnderlyingNetworkEvaluator {
private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName();
+ private static final int[] PENALTY_TIMEOUT_MINUTES_DEFAULT = new int[] {5};
+
@NonNull private final VcnContext mVcnContext;
+ @NonNull private final Handler mHandler;
+ @NonNull private final Object mCancellationToken = new Object();
+
@NonNull private final UnderlyingNetworkRecord.Builder mNetworkRecordBuilder;
+ @NonNull private final NetworkEvaluatorCallback mEvaluatorCallback;
+ @NonNull private final List<NetworkMetricMonitor> mMetricMonitors = new ArrayList<>();
+
+ @NonNull private final Dependencies mDependencies;
+
+ // TODO: Support back-off timeouts
+ private long mPenalizedTimeoutMs;
+
private boolean mIsSelected;
+ private boolean mIsPenalized;
private int mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
public UnderlyingNetworkEvaluator(
@NonNull VcnContext vcnContext,
@NonNull Network network,
@NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
@NonNull ParcelUuid subscriptionGroup,
@NonNull TelephonySubscriptionSnapshot lastSnapshot,
- @Nullable PersistableBundleWrapper carrierConfig) {
+ @Nullable PersistableBundleWrapper carrierConfig,
+ @NonNull NetworkEvaluatorCallback evaluatorCallback,
+ @NonNull Dependencies dependencies) {
mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+ mHandler = new Handler(mVcnContext.getLooper());
+
+ mDependencies = Objects.requireNonNull(dependencies, "Missing dependencies");
+ mEvaluatorCallback = Objects.requireNonNull(evaluatorCallback, "Missing deps");
Objects.requireNonNull(underlyingNetworkTemplates, "Missing underlyingNetworkTemplates");
Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
@@ -66,9 +96,76 @@
new UnderlyingNetworkRecord.Builder(
Objects.requireNonNull(network, "Missing network"));
mIsSelected = false;
+ mIsPenalized = false;
+ mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
updatePriorityClass(
underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+ if (isIpSecPacketLossDetectorEnabled()) {
+ try {
+ mMetricMonitors.add(
+ mDependencies.newIpSecPacketLossDetector(
+ mVcnContext,
+ mNetworkRecordBuilder.getNetwork(),
+ carrierConfig,
+ new MetricMonitorCallbackImpl()));
+ } catch (IllegalAccessException e) {
+ // No action. Do not add anything to mMetricMonitors
+ }
+ }
+ }
+
+ public UnderlyingNetworkEvaluator(
+ @NonNull VcnContext vcnContext,
+ @NonNull Network network,
+ @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+ @Nullable PersistableBundleWrapper carrierConfig,
+ @NonNull NetworkEvaluatorCallback evaluatorCallback) {
+ this(
+ vcnContext,
+ network,
+ underlyingNetworkTemplates,
+ subscriptionGroup,
+ lastSnapshot,
+ carrierConfig,
+ evaluatorCallback,
+ new Dependencies());
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class Dependencies {
+ /** Get an IpSecPacketLossDetector instance */
+ public IpSecPacketLossDetector newIpSecPacketLossDetector(
+ @NonNull VcnContext vcnContext,
+ @NonNull Network network,
+ @Nullable PersistableBundleWrapper carrierConfig,
+ @NonNull NetworkMetricMonitor.NetworkMetricMonitorCallback callback)
+ throws IllegalAccessException {
+ return new IpSecPacketLossDetector(vcnContext, network, carrierConfig, callback);
+ }
+ }
+
+ /** Callback to notify caller to reevaluate network selection */
+ public interface NetworkEvaluatorCallback {
+ /**
+ * Called when mIsPenalized changed
+ *
+ * <p>When receiving this call, UnderlyingNetworkController should reevaluate all network
+ * candidates for VCN underlying network selection
+ */
+ void onEvaluationResultChanged();
+ }
+
+ private class MetricMonitorCallbackImpl
+ implements NetworkMetricMonitor.NetworkMetricMonitorCallback {
+ public void onValidationResultReceived() {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ handleValidationResult();
+ }
}
private void updatePriorityClass(
@@ -91,8 +188,25 @@
}
}
- public static Comparator<UnderlyingNetworkEvaluator> getComparator() {
+ private boolean isIpSecPacketLossDetectorEnabled() {
+ return isIpSecPacketLossDetectorEnabled(mVcnContext);
+ }
+
+ private static boolean isIpSecPacketLossDetectorEnabled(VcnContext vcnContext) {
+ return vcnContext.isFlagIpSecTransformStateEnabled()
+ && vcnContext.isFlagNetworkMetricMonitorEnabled();
+ }
+
+ /** Get the comparator for UnderlyingNetworkEvaluator */
+ public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) {
return (left, right) -> {
+ if (isIpSecPacketLossDetectorEnabled(vcnContext)) {
+ if (left.mIsPenalized != right.mIsPenalized) {
+ // A penalized network should have lower priority which means a larger index
+ return left.mIsPenalized ? 1 : -1;
+ }
+ }
+
final int leftIndex = left.mPriorityClass;
final int rightIndex = right.mPriorityClass;
@@ -112,6 +226,64 @@
};
}
+ private static long getPenaltyTimeoutMs(@Nullable PersistableBundleWrapper carrierConfig) {
+ final int[] timeoutMinuteList;
+
+ if (carrierConfig != null) {
+ timeoutMinuteList =
+ carrierConfig.getIntArray(
+ VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
+ PENALTY_TIMEOUT_MINUTES_DEFAULT);
+ } else {
+ timeoutMinuteList = PENALTY_TIMEOUT_MINUTES_DEFAULT;
+ }
+
+ // TODO: Add the support of back-off timeouts and return the full list
+ return TimeUnit.MINUTES.toMillis(timeoutMinuteList[0]);
+ }
+
+ private void handleValidationResult() {
+ final boolean wasPenalized = mIsPenalized;
+ mIsPenalized = false;
+ for (NetworkMetricMonitor monitor : mMetricMonitors) {
+ mIsPenalized |= monitor.isValidationFailed();
+ }
+
+ if (wasPenalized == mIsPenalized) {
+ return;
+ }
+
+ logInfo(
+ "#handleValidationResult: wasPenalized "
+ + wasPenalized
+ + " mIsPenalized "
+ + mIsPenalized);
+
+ if (mIsPenalized) {
+ mHandler.postDelayed(
+ new ExitPenaltyBoxRunnable(), mCancellationToken, mPenalizedTimeoutMs);
+ } else {
+ // Exit the penalty box
+ mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+ }
+ mEvaluatorCallback.onEvaluationResultChanged();
+ }
+
+ public class ExitPenaltyBoxRunnable implements Runnable {
+ @Override
+ public void run() {
+ if (!mIsPenalized) {
+ logWtf("Evaluator not being penalized but ExitPenaltyBoxRunnable was scheduled");
+ return;
+ }
+
+ // TODO: There might be a future metric monitor (e.g. ping) that will require the
+ // validation to pass before exiting the penalty box.
+ mIsPenalized = false;
+ mEvaluatorCallback.onEvaluationResultChanged();
+ }
+ }
+
/** Set the NetworkCapabilities */
public void setNetworkCapabilities(
@NonNull NetworkCapabilities nc,
@@ -162,6 +334,10 @@
updatePriorityClass(
underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+ for (NetworkMetricMonitor monitor : mMetricMonitors) {
+ monitor.setIsSelectedUnderlyingNetwork(isSelected);
+ }
}
/**
@@ -174,6 +350,35 @@
@Nullable PersistableBundleWrapper carrierConfig) {
updatePriorityClass(
underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+ // The already scheduled event will not be affected. The followup events will be scheduled
+ // with the new timeout
+ mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
+
+ for (NetworkMetricMonitor monitor : mMetricMonitors) {
+ monitor.setCarrierConfig(carrierConfig);
+ }
+ }
+
+ /** Update the inbound IpSecTransform applied to the network */
+ public void setInboundTransform(@NonNull IpSecTransform transform) {
+ if (!mIsSelected) {
+ logWtf("setInboundTransform on an unselected evaluator");
+ return;
+ }
+
+ for (NetworkMetricMonitor monitor : mMetricMonitors) {
+ monitor.setInboundTransform(transform);
+ }
+ }
+
+ /** Close the evaluator and stop all the underlying network metric monitors */
+ public void close() {
+ mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+
+ for (NetworkMetricMonitor monitor : mMetricMonitors) {
+ monitor.close();
+ }
}
/** Return whether this network evaluator is valid */
@@ -196,6 +401,11 @@
return mPriorityClass;
}
+ /** Return whether the network is being penalized */
+ public boolean isPenalized() {
+ return mIsPenalized;
+ }
+
/** Dump the information of this instance */
public void dump(IndentingPrintWriter pw) {
pw.println("UnderlyingNetworkEvaluator:");
@@ -211,7 +421,22 @@
pw.println("mIsSelected: " + mIsSelected);
pw.println("mPriorityClass: " + mPriorityClass);
+ pw.println("mIsPenalized: " + mIsPenalized);
pw.decreaseIndent();
}
+
+ private String getLogPrefix() {
+ return "[Network " + mNetworkRecordBuilder.getNetwork() + "] ";
+ }
+
+ private void logInfo(String msg) {
+ Slog.i(TAG, getLogPrefix() + msg);
+ LOCAL_LOG.log("[INFO ] " + TAG + getLogPrefix() + msg);
+ }
+
+ private void logWtf(String msg) {
+ Slog.wtf(TAG, getLogPrefix() + msg);
+ LOCAL_LOG.log("[WTF ] " + TAG + getLogPrefix() + msg);
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 59d0210..0da0bb4 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1034,8 +1034,14 @@
launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
final SafeActivityOptions safeOptions =
SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);
+ if (transition != null) {
+ transition.deferTransitionReady();
+ }
waitAsyncStart(() -> mService.mTaskSupervisor.startActivityFromRecents(
caller.mPid, caller.mUid, taskId, safeOptions));
+ if (transition != null) {
+ transition.continueTransitionReady();
+ }
break;
}
case HIERARCHY_OP_TYPE_REORDER:
@@ -1113,11 +1119,17 @@
activityOptions.setCallerDisplayId(DEFAULT_DISPLAY);
}
final Bundle options = activityOptions != null ? activityOptions.toBundle() : null;
+ if (transition != null) {
+ transition.deferTransitionReady();
+ }
int res = waitAsyncStart(() -> mService.mAmInternal.sendIntentSender(
hop.getPendingIntent().getTarget(),
hop.getPendingIntent().getWhitelistToken(), 0 /* code */,
hop.getActivityIntent(), resolvedType, null /* finishReceiver */,
null /* requiredPermission */, options));
+ if (transition != null) {
+ transition.continueTransitionReady();
+ }
if (ActivityManager.isStartResultSuccessful(res)) {
effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 6b85a32..95a9610 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -25,25 +25,15 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.IContentProvider;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import android.os.Parcel;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IntArray;
@@ -1243,63 +1233,6 @@
imeId, createSubtypeHashCodeArrayFromStr(enabledSubtypeHashCodesStr)));
}
- private static TestContext createMockContext(int userId) {
- return new TestContext(InstrumentationRegistry.getInstrumentation()
- .getTargetContext(), userId);
- }
-
- private static class TestContext extends ContextWrapper {
- private int mUserId;
- private ContentResolver mResolver;
- private Resources mResources;
-
- private static TestContext sSecondaryUserContext;
-
- TestContext(@NonNull Context context, int userId) {
- super(context);
- mUserId = userId;
- mResolver = mock(MockContentResolver.class);
- when(mResolver.acquireProvider(Settings.Secure.CONTENT_URI)).thenReturn(
- mock(IContentProvider.class));
- mResources = mock(Resources.class);
-
- final Configuration configuration = new Configuration();
- if (userId == 0) {
- configuration.setLocale(LOCALE_EN_US);
- } else {
- configuration.setLocale(LOCALE_FR_CA);
- }
- doReturn(configuration).when(mResources).getConfiguration();
- }
-
- @Override
- public Context createContextAsUser(UserHandle user, int flags) {
- if (user.getIdentifier() != UserHandle.USER_SYSTEM) {
- return sSecondaryUserContext = new TestContext(this, user.getIdentifier());
- }
- return this;
- }
-
- @Override
- public int getUserId() {
- return mUserId;
- }
-
- @Override
- public ContentResolver getContentResolver() {
- return mResolver;
- }
-
- @Override
- public Resources getResources() {
- return mResources;
- }
-
- static Context getSecondaryUserContext() {
- return sSecondaryUserContext;
- }
- }
-
private static void verifySplitEnabledImeStr(@NonNull String enabledImeStr,
@NonNull String... expected) {
final ArrayList<String> actual = new ArrayList<>();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 79e4ad0..73c26a3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9683,6 +9683,17 @@
"parameters_used_for_ntn_lte_signal_bar_int";
/**
+ * Indicating whether plmns associated with carrier satellite can be exposed to user when
+ * manually scanning available cellular network.
+ * If key is {@code true}, satellite plmn should not be exposed to user and should be
+ * automatically set, {@code false} otherwise. Default value is {@code true}.
+ *
+ * @hide
+ */
+ public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL =
+ "remove_satellite_plmn_in_manual_network_scan_bool";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
@@ -10787,6 +10798,7 @@
});
sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT,
CellSignalStrengthLte.USE_RSRP);
+ sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0dce084..e4ea479 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6445,10 +6445,6 @@
* targeting API level 31+.
*
* @return the current call state.
- *
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELECOM}.
- *
* @deprecated Use {@link #getCallStateForSubscription} to retrieve the call state for a
* specific telephony subscription (which allows carrier privileged apps),
* {@link TelephonyCallback.CallStateListener} for real-time call state updates, or
@@ -6456,7 +6452,6 @@
* device.
*/
@RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
- @RequiresFeature(PackageManager.FEATURE_TELECOM)
@Deprecated
public @CallState int getCallState() {
if (mContext != null) {
@@ -10782,9 +10777,7 @@
}
/**
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELECOM}.
- * @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
+ * @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
* @hide
*/
@Deprecated
@@ -10793,15 +10786,12 @@
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
})
- @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isOffhook() {
TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
return tm.isInCall();
}
/**
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELECOM}.
* @deprecated Use {@link android.telecom.TelecomManager#isRinging} instead
* @hide
*/
@@ -10811,15 +10801,12 @@
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
})
- @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isRinging() {
TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
return tm.isRinging();
}
/**
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELECOM}.
* @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
* @hide
*/
@@ -10829,7 +10816,6 @@
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
})
- @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isIdle() {
TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
return !tm.isInCall();
@@ -12044,11 +12030,8 @@
*
* @return {@code true} if the device supports TTY mode, and {@code false} otherwise.
*
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELECOM}.
*/
@Deprecated
- @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isTtyModeSupported() {
try {
TelecomManager telecomManager = null;
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 2a69703..3b0397b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -49,8 +49,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -2142,6 +2144,35 @@
}
}
+ /**
+ * Get all satellite PLMNs for which attach is enable for carrier.
+ *
+ * @param subId subId The subscription ID of the carrier.
+ *
+ * @return List of plmn for carrier satellite service. If no plmn is available, empty list will
+ * be returned.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @NonNull public List<String> getAllSatellitePlmnsForCarrier(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid subscription ID");
+ }
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getAllSatellitePlmnsForCarrier(subId);
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("getAllSatellitePlmnsForCarrier() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return new ArrayList<>();
+ }
+
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
.getTelephonyServiceManager()
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3ea86c7..acbf354 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3258,4 +3258,17 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
boolean isNullCipherNotificationsEnabled();
+
+ /**
+ * Get the aggregated satellite plmn list. This API collects plmn data from multiple sources,
+ * including carrier config, entitlement server, and config update.
+ *
+ * @param subId subId The subscription ID of the carrier.
+ *
+ * @return List of plmns for carrier satellite service. If no plmn is available, empty list will
+ * be returned.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ List<String> getAllSatellitePlmnsForCarrier(int subId);
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index f846164..20b7f1f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -269,6 +269,7 @@
@Test
public void testCreatedTransformsAreApplied() throws Exception {
verifyVcnTransformsApplied(mGatewayConnection, false /* expectForwardTransform */);
+ verify(mUnderlyingNetworkController).updateInboundTransform(any(), any());
}
@Test
@@ -327,6 +328,8 @@
eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
}
+ verify(mUnderlyingNetworkController).updateInboundTransform(any(), any());
+
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
final List<ChildSaProposal> saProposals =
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 4c7b25a..e29e462 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -223,6 +223,8 @@
doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
+ doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
+ doReturn(true).when(mVcnContext).isFlagNetworkMetricMonitorEnabled();
doReturn(mUnderlyingNetworkController)
.when(mDeps)
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index 355c221..6015e931 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -26,6 +26,8 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.net.IpSecConfig;
+import android.net.IpSecTransform;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -141,4 +143,8 @@
mock(Handler.class));
setupSystemService(mContext, mPowerManager, Context.POWER_SERVICE, PowerManager.class);
}
+
+ protected IpSecTransform makeDummyIpSecTransform() throws Exception {
+ return new IpSecTransform(mContext, new IpSecConfig());
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index 992f102..588624b 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -47,6 +47,8 @@
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.IpSecConfig;
+import android.net.IpSecTransform;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -70,6 +72,7 @@
import com.android.server.vcn.routeselection.UnderlyingNetworkController.NetworkBringupCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
import org.junit.Before;
import org.junit.Test;
@@ -153,11 +156,13 @@
@Mock private CarrierConfigManager mCarrierConfigManager;
@Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
@Mock private UnderlyingNetworkControllerCallback mNetworkControllerCb;
+ @Mock private NetworkEvaluatorCallback mEvaluatorCallback;
@Mock private Network mNetwork;
@Spy private Dependencies mDependencies = new Dependencies();
@Captor private ArgumentCaptor<UnderlyingNetworkListener> mUnderlyingNetworkListenerCaptor;
+ @Captor private ArgumentCaptor<NetworkEvaluatorCallback> mEvaluatorCallbackCaptor;
private TestLooper mTestLooper;
private VcnContext mVcnContext;
@@ -176,7 +181,7 @@
mTestLooper.getLooper(),
mVcnNetworkProvider,
false /* isInTestMode */));
- resetVcnContext();
+ resetVcnContext(mVcnContext);
setupSystemService(
mContext,
@@ -202,10 +207,11 @@
.getVcnUnderlyingNetworkPriorities(),
SUB_GROUP,
mSubscriptionSnapshot,
- null));
+ null,
+ mEvaluatorCallback));
doReturn(mNetworkEvaluator)
.when(mDependencies)
- .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any());
+ .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
mUnderlyingNetworkController =
new UnderlyingNetworkController(
@@ -217,9 +223,11 @@
mDependencies);
}
- private void resetVcnContext() {
- reset(mVcnContext);
- doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+ private void resetVcnContext(VcnContext vcnContext) {
+ reset(vcnContext);
+ doNothing().when(vcnContext).ensureRunningOnLooperThread();
+ doReturn(true).when(vcnContext).isFlagNetworkMetricMonitorEnabled();
+ doReturn(true).when(vcnContext).isFlagIpSecTransformStateEnabled();
}
// Package private for use in NetworkPriorityClassifierTest
@@ -245,11 +253,13 @@
final ConnectivityManager cm = mock(ConnectivityManager.class);
setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
final VcnContext vcnContext =
- new VcnContext(
- mContext,
- mTestLooper.getLooper(),
- mVcnNetworkProvider,
- true /* isInTestMode */);
+ spy(
+ new VcnContext(
+ mContext,
+ mTestLooper.getLooper(),
+ mVcnNetworkProvider,
+ true /* isInTestMode */));
+ resetVcnContext(vcnContext);
new UnderlyingNetworkController(
vcnContext,
@@ -554,6 +564,45 @@
verify(mNetworkEvaluator).reevaluate(any(), any(), any(), any());
}
+ @Test
+ public void testUpdateIpSecTransform() {
+ verifyRegistrationOnAvailableAndGetCallback();
+
+ final UnderlyingNetworkRecord expectedRecord =
+ getTestNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ final IpSecTransform expectedTransform = new IpSecTransform(mContext, new IpSecConfig());
+
+ mUnderlyingNetworkController.updateInboundTransform(expectedRecord, expectedTransform);
+ verify(mNetworkEvaluator).setInboundTransform(expectedTransform);
+ }
+
+ @Test
+ public void testOnEvaluationResultChanged() {
+ verifyRegistrationOnAvailableAndGetCallback();
+
+ // Verify #reevaluateNetworks is called by checking #getNetworkRecord
+ verify(mNetworkEvaluator).getNetworkRecord();
+
+ // Trigger the callback
+ verify(mDependencies)
+ .newUnderlyingNetworkEvaluator(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ mEvaluatorCallbackCaptor.capture());
+ mEvaluatorCallbackCaptor.getValue().onEvaluationResultChanged();
+
+ // Verify #reevaluateNetworks is called again
+ verify(mNetworkEvaluator, times(2)).getNetworkRecord();
+ }
+
private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback() {
return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
}
@@ -682,7 +731,7 @@
cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
- verifyOnSelectedUnderlyingNetworkChanged(null);
+ verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
}
@Test
@@ -690,6 +739,7 @@
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
cb.onLost(mNetwork);
+ verify(mNetworkEvaluator).close();
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
}
@@ -755,10 +805,11 @@
underlyingNetworkTemplates,
SUB_GROUP,
mSubscriptionSnapshot,
- null));
+ null,
+ mEvaluatorCallback));
doReturn(evaluator)
.when(mDependencies)
- .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any());
+ .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
cb.onAvailable(network);
cb.onCapabilitiesChanged(network, responseNetworkCaps);
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
index 985e70c..aa81efe 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
@@ -16,27 +16,65 @@
package com.android.server.vcn.routeselection;
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY;
+
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.net.IpSecTransform;
import android.net.vcn.VcnGatewayConnectionConfig;
-import android.os.PersistableBundle;
+
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.Dependencies;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+import java.util.concurrent.TimeUnit;
public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {
- private PersistableBundleWrapper mCarrierConfig;
+ private static final int PENALTY_TIMEOUT_MIN = 10;
+ private static final long PENALTY_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(PENALTY_TIMEOUT_MIN);
+
+ @Mock private PersistableBundleWrapper mCarrierConfig;
+ @Mock private IpSecPacketLossDetector mIpSecPacketLossDetector;
+ @Mock private Dependencies mDependencies;
+ @Mock private NetworkEvaluatorCallback mEvaluatorCallback;
+
+ @Captor private ArgumentCaptor<NetworkMetricMonitorCallback> mMetricMonitorCbCaptor;
+
+ private UnderlyingNetworkEvaluator mNetworkEvaluator;
@Before
public void setUp() throws Exception {
super.setUp();
- mCarrierConfig = new PersistableBundleWrapper(new PersistableBundle());
+
+ when(mDependencies.newIpSecPacketLossDetector(any(), any(), any(), any()))
+ .thenReturn(mIpSecPacketLossDetector);
+
+ when(mCarrierConfig.getIntArray(
+ eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+ .thenReturn(new int[] {PENALTY_TIMEOUT_MIN});
+
+ mNetworkEvaluator = newValidUnderlyingNetworkEvaluator();
}
private UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator() {
@@ -46,7 +84,34 @@
VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
SUB_GROUP,
mSubscriptionSnapshot,
+ mCarrierConfig,
+ mEvaluatorCallback,
+ mDependencies);
+ }
+
+ private UnderlyingNetworkEvaluator newValidUnderlyingNetworkEvaluator() {
+ final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+
+ evaluator.setNetworkCapabilities(
+ CELL_NETWORK_CAPABILITIES,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
mCarrierConfig);
+ evaluator.setLinkProperties(
+ LINK_PROPERTIES,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ evaluator.setIsBlocked(
+ false /* isBlocked */,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+
+ return evaluator;
}
@Test
@@ -98,4 +163,174 @@
assertEquals(2, evaluator.getPriorityClass());
assertEquals(expectedRecord, evaluator.getNetworkRecord());
}
+
+ private void checkSetSelectedNetwork(boolean isSelected) {
+ mNetworkEvaluator.setIsSelected(
+ isSelected,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ verify(mIpSecPacketLossDetector).setIsSelectedUnderlyingNetwork(isSelected);
+ }
+
+ @Test
+ public void testSetIsSelected_selected() throws Exception {
+ checkSetSelectedNetwork(true /* isSelectedExpected */);
+ }
+
+ @Test
+ public void testSetIsSelected_unselected() throws Exception {
+ checkSetSelectedNetwork(false /* isSelectedExpected */);
+ }
+
+ @Test
+ public void testSetIpSecTransform_onSelectedNetwork() throws Exception {
+ final IpSecTransform transform = makeDummyIpSecTransform();
+
+ // Make the network selected
+ mNetworkEvaluator.setIsSelected(
+ true,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ mNetworkEvaluator.setInboundTransform(transform);
+
+ verify(mIpSecPacketLossDetector).setInboundTransform(transform);
+ }
+
+ @Test
+ public void testSetIpSecTransform_onUnSelectedNetwork() throws Exception {
+ mNetworkEvaluator.setIsSelected(
+ false,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ mNetworkEvaluator.setInboundTransform(makeDummyIpSecTransform());
+
+ verify(mIpSecPacketLossDetector, never()).setInboundTransform(any());
+ }
+
+ @Test
+ public void close() throws Exception {
+ mNetworkEvaluator.close();
+
+ verify(mIpSecPacketLossDetector).close();
+ mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+ assertNull(mTestLooper.nextMessage());
+ }
+
+ private NetworkMetricMonitorCallback getMetricMonitorCbCaptor() throws Exception {
+ verify(mDependencies)
+ .newIpSecPacketLossDetector(any(), any(), any(), mMetricMonitorCbCaptor.capture());
+
+ return mMetricMonitorCbCaptor.getValue();
+ }
+
+ private void checkPenalizeNetwork() throws Exception {
+ assertFalse(mNetworkEvaluator.isPenalized());
+
+ // Validation failed
+ when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
+ getMetricMonitorCbCaptor().onValidationResultReceived();
+
+ // Verify the evaluator is penalized
+ assertTrue(mNetworkEvaluator.isPenalized());
+ verify(mEvaluatorCallback).onEvaluationResultChanged();
+ }
+
+ @Test
+ public void testRcvValidationResult_penalizeNetwork_penaltyTimeout() throws Exception {
+ checkPenalizeNetwork();
+
+ // Penalty timeout
+ mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Verify the evaluator is not penalized
+ assertFalse(mNetworkEvaluator.isPenalized());
+ verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
+ }
+
+ @Test
+ public void testRcvValidationResult_penalizeNetwork_passValidation() throws Exception {
+ checkPenalizeNetwork();
+
+ // Validation passed
+ when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
+ getMetricMonitorCbCaptor().onValidationResultReceived();
+
+ // Verify the evaluator is not penalized and penalty timeout is canceled
+ assertFalse(mNetworkEvaluator.isPenalized());
+ verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
+ mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+ assertNull(mTestLooper.nextMessage());
+ }
+
+ @Test
+ public void testRcvValidationResult_penalizeNetwork_closeEvaluator() throws Exception {
+ checkPenalizeNetwork();
+
+ mNetworkEvaluator.close();
+
+ // Verify penalty timeout is canceled
+ mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+ assertNull(mTestLooper.nextMessage());
+ }
+
+ @Test
+ public void testRcvValidationResult_PenaltyStateUnchanged() throws Exception {
+ assertFalse(mNetworkEvaluator.isPenalized());
+
+ // Validation passed
+ when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
+ getMetricMonitorCbCaptor().onValidationResultReceived();
+
+ // Verifications
+ assertFalse(mNetworkEvaluator.isPenalized());
+ verify(mEvaluatorCallback, never()).onEvaluationResultChanged();
+ }
+
+ @Test
+ public void testSetCarrierConfig() throws Exception {
+ final int additionalTimeoutMin = 10;
+ when(mCarrierConfig.getIntArray(
+ eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+ .thenReturn(new int[] {PENALTY_TIMEOUT_MIN + additionalTimeoutMin});
+
+ // Update evaluator and penalize the network
+ mNetworkEvaluator.reevaluate(
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mCarrierConfig);
+ checkPenalizeNetwork();
+
+ // Verify penalty timeout is changed
+ mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+ assertNull(mTestLooper.nextMessage());
+ mTestLooper.moveTimeForward(TimeUnit.MINUTES.toMillis(additionalTimeoutMin));
+ assertNotNull(mTestLooper.nextMessage());
+
+ // Verify NetworkMetricMonitor is notified
+ verify(mIpSecPacketLossDetector).setCarrierConfig(any());
+ }
+
+ @Test
+ public void testCompare() throws Exception {
+ when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
+ getMetricMonitorCbCaptor().onValidationResultReceived();
+
+ final UnderlyingNetworkEvaluator penalized = mNetworkEvaluator;
+ final UnderlyingNetworkEvaluator notPenalized = newValidUnderlyingNetworkEvaluator();
+
+ assertEquals(penalized.getPriorityClass(), notPenalized.getPriorityClass());
+
+ final int result =
+ UnderlyingNetworkEvaluator.getComparator(mVcnContext)
+ .compare(penalized, notPenalized);
+ assertEquals(1, result);
+ }
}