diff options
240 files changed, 4654 insertions, 1798 deletions
diff --git a/TEST_MAPPING b/TEST_MAPPING index 3409838b5611..117faa203325 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -126,6 +126,12 @@ "exclude-annotation": "org.junit.Ignore" } ] + }, + { + "name": "vts_treble_vintf_framework_test" + }, + { + "name": "vts_treble_vintf_vendor_test" } ], "postsubmit-ravenwood": [ diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 9f56933865db..5ecc0b6a6568 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4091,7 +4091,7 @@ package android.content.pm { field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1 field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff field public static final int MATCH_ANY_USER = 4194304; // 0x400000 - field @Deprecated public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000 + field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000 field @FlaggedApi("android.content.pm.fix_duplicated_flags") public static final long MATCH_CLONE_PROFILE_LONG = 17179869184L; // 0x400000000L field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 39f2737dc880..a3cd3dc87db3 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2,6 +2,7 @@ package android { public static final class Manifest.permission { + field @FlaggedApi("com.android.server.accessibility.motion_event_observing") public static final String ACCESSIBILITY_MOTION_EVENT_OBSERVING = "android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING"; field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"; @@ -99,6 +100,8 @@ package android.accessibilityservice { public class AccessibilityServiceInfo implements android.os.Parcelable { method @NonNull public android.content.ComponentName getComponentName(); + method @FlaggedApi("android.view.accessibility.motion_event_observing") public int getObservedMotionEventSources(); + method @FlaggedApi("android.view.accessibility.motion_event_observing") public void setObservedMotionEventSources(int); } } diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 8ad6ea207665..fc342fa3431a 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -20,6 +20,7 @@ import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHt import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage; import static android.content.pm.PackageManager.FEATURE_FINGERPRINT; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -53,6 +54,7 @@ import android.view.InputDevice; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.Flags; import com.android.internal.R; import com.android.internal.compat.IPlatformCompat; @@ -630,7 +632,8 @@ public class AccessibilityServiceInfo implements Parcelable { InputDevice.SOURCE_TOUCH_NAVIGATION, InputDevice.SOURCE_ROTARY_ENCODER, InputDevice.SOURCE_JOYSTICK, - InputDevice.SOURCE_SENSOR + InputDevice.SOURCE_SENSOR, + InputDevice.SOURCE_TOUCHSCREEN }) @Retention(RetentionPolicy.SOURCE) public @interface MotionEventSources {} @@ -642,6 +645,8 @@ public class AccessibilityServiceInfo implements Parcelable { @MotionEventSources private int mMotionEventSources = 0; + private int mObservedMotionEventSources = 0; + /** * Creates a new instance. */ @@ -817,6 +822,9 @@ public class AccessibilityServiceInfo implements Parcelable { mInteractiveUiTimeout = other.mInteractiveUiTimeout; flags = other.flags; mMotionEventSources = other.mMotionEventSources; + if (Flags.motionEventObserving()) { + setObservedMotionEventSources(other.mObservedMotionEventSources); + } // NOTE: Ensure that only properties that are safe to be modified by the service itself // are included here (regardless of hidden setters, etc.). } @@ -1024,16 +1032,75 @@ public class AccessibilityServiceInfo implements Parcelable { */ public void setMotionEventSources(@MotionEventSources int motionEventSources) { mMotionEventSources = motionEventSources; + mObservedMotionEventSources = 0; + } + + /** + * Sets the bit mask of {@link android.view.InputDevice} sources that the accessibility service + * wants to observe generic {@link android.view.MotionEvent}s from if it has already requested + * to listen to them using {@link #setMotionEventSources(int)}. Events from these sources will + * be sent to the rest of the input pipeline without being consumed by accessibility services. + * This service will still be able to see them. + * + * <p><strong>Note:</strong> you will need to call this function every time you call {@link + * #setMotionEventSources(int)}. Calling {@link #setMotionEventSources(int)} clears the list of + * observed motion event sources for this service. + * + * <p><strong>Note:</strong> {@link android.view.InputDevice} sources contain source class bits + * that complicate bitwise flag removal operations. To remove a specific source you should + * rebuild the entire value using bitwise OR operations on the individual source constants. + * + * <p>Including an {@link android.view.InputDevice} source that does not send {@link + * android.view.MotionEvent}s is effectively a no-op for that source, since you will not receive + * any events from that source. + * + * <p><strong>Note:</strong> Calling this function with a source that has not been listened to + * using {@link #setMotionEventSources(int)} will throw an exception. + * + * @see AccessibilityService#onMotionEvent + * @see #MotionEventSources + * @see #setMotionEventSources(int) + * @hide + */ + @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING) + @TestApi + public void setObservedMotionEventSources(int observedMotionEventSources) { + // Confirm that any sources requested here have already been requested for listening. + if ((observedMotionEventSources & ~mMotionEventSources) != 0) { + String message = + String.format( + "Requested motion event sources for listening = 0x%x but requested" + + " motion event sources for observing = 0x%x.", + mMotionEventSources, observedMotionEventSources); + throw new IllegalArgumentException(message); + } + mObservedMotionEventSources = observedMotionEventSources; + } + + /** + * Returns the bit mask of {@link android.view.InputDevice} sources that the accessibility + * service wants to observe generic {@link android.view.MotionEvent}s from if it has already + * requested to listen to them using {@link #setMotionEventSources(int)}. Events from these + * sources will be sent to the rest of the input pipeline without being consumed by + * accessibility services. This service will still be able to see them. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING) + @MotionEventSources + @TestApi + public int getObservedMotionEventSources() { + return mObservedMotionEventSources; } /** * The localized summary of the accessibility service. - * <p> - * <strong>Statically set from - * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> - * </p> - * @return The localized summary if available, and {@code null} if a summary - * has not been provided. + * + * <p><strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA + * meta-data}.</strong> + * + * @return The localized summary if available, and {@code null} if a summary has not been + * provided. */ public CharSequence loadSummary(PackageManager packageManager) { if (mSummaryResId == 0) { @@ -1260,6 +1327,7 @@ public class AccessibilityServiceInfo implements Parcelable { parcel.writeString(mTileServiceName); parcel.writeInt(mIntroResId); parcel.writeInt(mMotionEventSources); + parcel.writeInt(mObservedMotionEventSources); } private void initFromParcel(Parcel parcel) { @@ -1285,6 +1353,8 @@ public class AccessibilityServiceInfo implements Parcelable { mTileServiceName = parcel.readString(); mIntroResId = parcel.readInt(); mMotionEventSources = parcel.readInt(); + // use the setter here because it throws an exception for invalid values. + setObservedMotionEventSources(parcel.readInt()); } @Override diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index ffed40538702..4a9fa9e63bf9 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6884,8 +6884,8 @@ public class Activity extends ContextThemeWrapper * application package was involved. * * <p>If called while inside the handling of {@link #onNewIntent}, this function will - * return the referrer that submitted that new intent to the activity. Otherwise, it - * always returns the referrer of the original Intent.</p> + * return the referrer that submitted that new intent to the activity only after + * {@link #setIntent(Intent)} is called with the provided intent.</p> * * <p>Note that this is <em>not</em> a security feature -- you can not trust the * referrer information, applications can spoof it.</p> diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index a510c7704751..013bcddbb7f3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2205,9 +2205,6 @@ public class Notification implements Parcelable private void visitUris(@NonNull Consumer<Uri> visitor) { visitIconUri(visitor, getIcon()); - if (actionIntent != null) { - actionIntent.visitUris(visitor); - } } @Override @@ -2901,21 +2898,6 @@ public class Notification implements Parcelable } } - // allPendingIntents should contain all associated intents after parcelling, but it may also - // contain intents added by the app to extras for their own purposes. We only care about - // checking the intents known and used by system_server, to avoid the confused deputy issue. - List<PendingIntent> pendingIntents = Arrays.asList(contentIntent, deleteIntent, - fullScreenIntent); - for (PendingIntent intent : pendingIntents) { - if (intent != null) { - intent.visitUris(visitor); - } - } - - if (mBubbleMetadata != null) { - mBubbleMetadata.visitUris(visitor); - } - if (extras != null) { visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class)); visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class)); @@ -2987,28 +2969,15 @@ public class Notification implements Parcelable callPerson.visitUris(visitor); } visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class)); + } - // Extras for MediaStyle. - PendingIntent deviceIntent = extras.getParcelable(EXTRA_MEDIA_REMOTE_INTENT, - PendingIntent.class); - if (deviceIntent != null) { - deviceIntent.visitUris(visitor); - } - - if (extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) { - WearableExtender extender = new WearableExtender(this); - extender.visitUris(visitor); - } - - if (extras.containsKey(TvExtender.EXTRA_TV_EXTENDER)) { - TvExtender extender = new TvExtender(this); - extender.visitUris(visitor); - } + if (mBubbleMetadata != null) { + visitIconUri(visitor, mBubbleMetadata.getIcon()); + } - if (extras.containsKey(CarExtender.EXTRA_CAR_EXTENDER)) { - CarExtender extender = new CarExtender(this); - extender.visitUris(visitor); - } + if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) { + WearableExtender extender = new WearableExtender(this); + extender.visitUris(visitor); } } @@ -10589,16 +10558,6 @@ public class Notification implements Parcelable } } - private void visitUris(@NonNull Consumer<Uri> visitor) { - visitIconUri(visitor, getIcon()); - if (mPendingIntent != null) { - mPendingIntent.visitUris(visitor); - } - if (mDeleteIntent != null) { - mDeleteIntent.visitUris(visitor); - } - } - /** * Builder to construct a {@link BubbleMetadata} object. */ @@ -11797,9 +11756,6 @@ public class Notification implements Parcelable } private void visitUris(@NonNull Consumer<Uri> visitor) { - if (mDisplayIntent != null) { - mDisplayIntent.visitUris(visitor); - } for (Action action : mActions) { action.visitUris(visitor); } @@ -11952,19 +11908,12 @@ public class Notification implements Parcelable /** * Returns the unread conversation conveyed by this notification. - * * @see #setUnreadConversation(UnreadConversation) */ public UnreadConversation getUnreadConversation() { return mUnreadConversation; } - private void visitUris(@NonNull Consumer<Uri> visitor) { - if (mUnreadConversation != null) { - mUnreadConversation.visitUris(visitor); - } - } - /** * A class which holds the unread messages from a conversation. */ @@ -12116,16 +12065,7 @@ public class Notification implements Parcelable onRead, participants, b.getLong(KEY_TIMESTAMP)); } - - private void visitUris(@NonNull Consumer<Uri> visitor) { - if (mReadPendingIntent != null) { - mReadPendingIntent.visitUris(visitor); - } - if (mReplyPendingIntent != null) { - mReplyPendingIntent.visitUris(visitor); - } - } - } + }; /** * Builder class for {@link CarExtender.UnreadConversation} objects. @@ -12448,15 +12388,6 @@ public class Notification implements Parcelable public boolean isSuppressShowOverApps() { return mSuppressShowOverApps; } - - private void visitUris(@NonNull Consumer<Uri> visitor) { - if (mContentIntent != null) { - mContentIntent.visitUris(visitor); - } - if (mDeleteIntent != null) { - mDeleteIntent.visitUris(visitor); - } - } } /** diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 0261f0a02174..62209b0fd27d 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -44,8 +44,6 @@ import android.content.IntentSender; import android.content.pm.PackageManager.ResolveInfoFlagsBits; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -71,7 +69,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; -import java.util.function.Consumer; /** * A description of an Intent and target action to perform with it. Instances @@ -1463,21 +1460,6 @@ public final class PendingIntent implements Parcelable { return sb.toString(); } - /** - * See {@link Intent#visitUris(Consumer)}. - * - * @hide - */ - public void visitUris(@NonNull Consumer<Uri> visitor) { - if (android.app.Flags.visitRiskyUris()) { - Intent intent = Binder.withCleanCallingIdentity(this::getIntent); - - if (intent != null) { - intent.visitUris(visitor); - } - } - } - /** @hide */ public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 90a265937082..4a6349b1b02f 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -9381,7 +9381,7 @@ public class DevicePolicyManager { @Deprecated @SystemApi @RequiresPermission(MANAGE_DEVICE_ADMINS) - public boolean setActiveProfileOwner(@NonNull ComponentName admin, @Deprecated String ownerName) + public boolean setActiveProfileOwner(@NonNull ComponentName admin, String ownerName) throws IllegalArgumentException { throwIfParentInstance("setActiveProfileOwner"); if (mService != null) { diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 6e451479c5a4..4cf9fcab9092 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -16,6 +16,8 @@ package android.appwidget; +import static android.appwidget.flags.Flags.remoteAdapterConversion; + import android.annotation.BroadcastBehavior; import android.annotation.NonNull; import android.annotation.Nullable; @@ -566,11 +568,9 @@ public class AppWidgetManager { private void tryAdapterConversion( FunctionalUtils.RemoteExceptionIgnoringConsumer<RemoteViews> action, RemoteViews original, String failureMsg) { - final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled() + if (remoteAdapterConversion() && (mHasPostedLegacyLists = mHasPostedLegacyLists - || (original != null && original.hasLegacyLists())); - - if (isConvertingAdapter) { + || (original != null && original.hasLegacyLists()))) { final RemoteViews viewsCopy = new RemoteViews(original); Runnable updateWidgetWithTask = () -> { try { @@ -587,13 +587,12 @@ public class AppWidgetManager { } updateWidgetWithTask.run(); - return; - } - - try { - action.acceptOrThrow(original); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); + } else { + try { + action.acceptOrThrow(original); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } } } @@ -838,22 +837,20 @@ public class AppWidgetManager { return; } - if (!RemoteViews.isAdapterConversionEnabled()) { + if (remoteAdapterConversion()) { + if (Looper.myLooper() == Looper.getMainLooper()) { + mHasPostedLegacyLists = true; + createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange( + appWidgetIds, viewId)); + } else { + notifyCollectionWidgetChange(appWidgetIds, viewId); + } + } else { try { mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } - - return; - } - - if (Looper.myLooper() == Looper.getMainLooper()) { - mHasPostedLegacyLists = true; - createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange(appWidgetIds, - viewId)); - } else { - notifyCollectionWidgetChange(appWidgetIds, viewId); } } diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig index 6a735a418b58..c95b864c08bb 100644 --- a/core/java/android/appwidget/flags.aconfig +++ b/core/java/android/appwidget/flags.aconfig @@ -6,3 +6,10 @@ flag { description: "Enable support for generated previews in AppWidgetManager" bug: "306546610" } + +flag { + name: "remote_adapter_conversion" + namespace: "app_widgets" + description: "Enable adapter conversion to RemoteCollectionItemsAdapter" + bug: "245950570" +}
\ No newline at end of file diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index e4a03c596254..d5b5f40a6980 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -37,6 +37,7 @@ import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; @@ -709,7 +710,9 @@ public final class CompanionDeviceManager { IntentSender intentSender = mService .requestNotificationAccess(component, mContext.getUserId()) .getIntentSender(); - mContext.startIntentSender(intentSender, null, 0, 0, 0); + mContext.startIntentSender(intentSender, null, 0, 0, 0, + ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (IntentSender.SendIntentException e) { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 183b9b0000d2..7af0be3b3e75 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -101,7 +101,6 @@ import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.TimeZone; -import java.util.function.Consumer; /** * An intent is an abstract description of an operation to be performed. It @@ -8148,27 +8147,6 @@ public class Intent implements Parcelable, Cloneable { } } - /** - * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission - * grants will need to be issued to ensure the recipient of this object is able to render its - * contents. - * See b/281044385 for more context and examples about what happens when this isn't done - * correctly. - * - * @hide - */ - public void visitUris(@NonNull Consumer<Uri> visitor) { - if (android.app.Flags.visitRiskyUris()) { - visitor.accept(mData); - if (mSelector != null) { - mSelector.visitUris(visitor); - } - if (mOriginalIntent != null) { - mOriginalIntent.visitUris(visitor); - } - } - } - public static Intent getIntentOld(String uri) throws URISyntaxException { Intent intent = getIntentOld(uri, 0); intent.mLocalFlags |= LOCAL_FLAG_FROM_URI; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index e2243292ab8d..a5d16f2f6be1 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1225,12 +1225,10 @@ public abstract class PackageManager { public static final int MATCH_DEBUG_TRIAGED_MISSING = MATCH_DIRECT_BOOT_AUTO; /** - * @deprecated Use {@link #MATCH_CLONE_PROFILE_LONG} instead. + * Use {@link #MATCH_CLONE_PROFILE_LONG} instead. * * @hide */ - @SuppressLint("UnflaggedApi") // Just adding the @Deprecated annotation - @Deprecated @SystemApi public static final int MATCH_CLONE_PROFILE = 0x20000000; @@ -6302,6 +6300,11 @@ public abstract class PackageManager { /** * Check whether a particular package has been granted a particular * permission. + * <p> + * <strong>Note: </strong>This API returns the underlying permission state + * as-is and is mostly intended for permission managing system apps. To + * perform an access check for a certain app, please use the + * {@link Context#checkPermission} APIs instead. * * @param permName The name of the permission you are checking for. * @param packageName The name of the package you are checking against. diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 6c6b33b8d716..57025c25f97b 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -49,3 +49,10 @@ flag { description: "Add support to unlock the private space using biometrics" bug: "312184187" } + +flag { + name: "support_autolock_for_private_space" + namespace: "profile_experiences" + description: "Add support to lock private space automatically after a time period" + bug: "303201022" +} diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 8196bf505e02..20b09326e9c0 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -40,7 +40,6 @@ import android.os.ConditionVariable; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; -import android.util.FeatureFlagUtils; import android.util.IntArray; import android.util.Log; import android.util.Pair; @@ -722,6 +721,7 @@ public final class CameraExtensionCharacteristics { switch(format) { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: + case ImageFormat.JPEG_R: break; default: throw new IllegalArgumentException("Unsupported format: " + format); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 16ffaef03121..10a8022df0ea 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1657,6 +1657,7 @@ public abstract class BatteryStats { */ public abstract CpuScalingPolicies getCpuScalingPolicies(); + @android.ravenwood.annotation.RavenwoodKeepWholeClass public final static class HistoryTag { public static final int HISTORY_TAG_POOL_OVERFLOW = -1; @@ -1713,6 +1714,7 @@ public abstract class BatteryStats { * Optional detailed information that can go into a history step. This is typically * generated each time the battery level changes. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass public final static class HistoryStepDetails { // Time (in 1/100 second) spent in user space and the kernel since the last step. public int userTime; @@ -1797,6 +1799,7 @@ public abstract class BatteryStats { /** * An extension to the history item describing a proc state change for a UID. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass public static final class ProcessStateChange { public int uid; public @BatteryConsumer.ProcessState int processState; @@ -1850,6 +1853,7 @@ public abstract class BatteryStats { /** * Battery history record. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass public static final class HistoryItem { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public HistoryItem next; diff --git a/core/java/android/os/ConditionVariable.java b/core/java/android/os/ConditionVariable.java index a13eaa6f19bf..b5ed53bf0e5e 100644 --- a/core/java/android/os/ConditionVariable.java +++ b/core/java/android/os/ConditionVariable.java @@ -29,6 +29,7 @@ package android.os; * This class uses itself as the object to wait on, so if you wait() * or notify() on a ConditionVariable, the results are undefined. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ConditionVariable { private volatile boolean mCondition; diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java index 77160557f5c2..91b796aba655 100644 --- a/core/java/android/os/HidlSupport.java +++ b/core/java/android/os/HidlSupport.java @@ -218,13 +218,6 @@ public class HidlSupport { @SystemApi public static native int getPidIfSharable(); - /** - * Return true if HIDL is supported on this device and false if not. - * - * @hide - */ - public static native boolean isHidlSupported(); - /** @hide */ public HidlSupport() {} } diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java index bc19655d1618..feed20800fd4 100644 --- a/core/java/android/os/HwBinder.java +++ b/core/java/android/os/HwBinder.java @@ -18,7 +18,6 @@ package android.os; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; -import android.util.Log; import libcore.util.NativeAllocationRegistry; @@ -79,17 +78,6 @@ public abstract class HwBinder implements IHwBinder { String iface, String serviceName) throws RemoteException, NoSuchElementException { - if (!HidlSupport.isHidlSupported() - && (iface.equals("android.hidl.manager@1.0::IServiceManager") - || iface.equals("android.hidl.manager@1.1::IServiceManager") - || iface.equals("android.hidl.manager@1.2::IServiceManager"))) { - Log.i( - TAG, - "Replacing Java hwservicemanager with a fake HwNoService" - + " because HIDL is not supported on this device."); - return new HwNoService(); - } - return getService(iface, serviceName, false /* retry */); } /** diff --git a/core/java/android/os/HwNoService.java b/core/java/android/os/HwNoService.java deleted file mode 100644 index 117c3ad7ee48..000000000000 --- a/core/java/android/os/HwNoService.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.os; - -/** - * A fake hwservicemanager that is used locally when HIDL isn't supported on the device. - * - * @hide - */ -final class HwNoService implements IHwBinder, IHwInterface { - /** @hide */ - @Override - public void transact(int code, HwParcel request, HwParcel reply, int flags) {} - - /** @hide */ - @Override - public IHwInterface queryLocalInterface(String descriptor) { - return new HwNoService(); - } - - /** @hide */ - @Override - public boolean linkToDeath(DeathRecipient recipient, long cookie) { - return true; - } - - /** @hide */ - @Override - public boolean unlinkToDeath(DeathRecipient recipient) { - return true; - } - - /** @hide */ - @Override - public IHwBinder asBinder() { - return this; - } -} diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index f2930fe45295..8e860c35388d 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -465,9 +465,7 @@ public final class Parcel { private static native byte[] nativeMarshall(long nativePtr); private static native void nativeUnmarshall( long nativePtr, byte[] data, int offset, int length); - @RavenwoodThrow private static native int nativeCompareData(long thisNativePtr, long otherNativePtr); - @RavenwoodThrow private static native boolean nativeCompareDataInRange( long ptrA, int offsetA, long ptrB, int offsetB, int length); private static native void nativeAppendFrom( diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 69d86a6604ad..437668c9a7de 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -44,3 +44,10 @@ flag { description: "Enables the independent keyboard vibration settings feature" bug: "289107579" } + +flag { + namespace: "haptics" + name: "adaptive_haptics_enabled" + description: "Enables the adaptive haptics feature" + bug: "305961689" +} diff --git a/core/java/android/service/persistentdata/OWNERS b/core/java/android/service/persistentdata/OWNERS new file mode 100644 index 000000000000..6dfb888dedad --- /dev/null +++ b/core/java/android/service/persistentdata/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/pdb/OWNERS diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 5cbb42e0e346..40382fdcf4b8 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -76,13 +76,8 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_E import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; -import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; @@ -12097,11 +12092,9 @@ public final class ViewRootImpl implements ViewParent, boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN || motionEventAction == MotionEvent.ACTION_MOVE || motionEventAction == MotionEvent.ACTION_UP; - boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION - || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION - || windowType == TYPE_NOTIFICATION_SHADE || windowType == TYPE_STATUS_BAR; + boolean undesiredType = windowType == TYPE_INPUT_METHOD; // use toolkitSetFrameRate flag to gate the change - return desiredAction && desiredType && sToolkitSetFrameRateReadOnlyFlagValue; + return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue; } /** diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index e057660961f6..0cc19fb70fbc 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -59,6 +59,13 @@ flag { } flag { + name: "motion_event_observing" + namespace: "accessibility" + description: "Allows accessibility services to intercept but not consume motion events from specified sources." + bug: "297595990" +} + +flag { namespace: "accessibility" name: "granular_scrolling" description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen" diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index a31a610ab523..8ad10af7250a 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -16,6 +16,7 @@ package android.widget; +import static android.appwidget.flags.Flags.remoteAdapterConversion; import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR; import android.annotation.AttrRes; @@ -36,7 +37,6 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; -import android.app.AppGlobals; import android.app.Application; import android.app.LoadedApk; import android.app.PendingIntent; @@ -108,7 +108,6 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.CompoundButton.OnCheckedChangeListener; import com.android.internal.R; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.util.Preconditions; import com.android.internal.widget.IRemoteViewsFactory; @@ -1015,11 +1014,6 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_PENDING_INTENT_TEMPLATE_TAG; } - - @Override - public void visitUris(@NonNull Consumer<Uri> visitor) { - mPendingIntentTemplate.visitUris(visitor); - } } /** @@ -1434,7 +1428,9 @@ public class RemoteViews implements Parcelable, Filter { @Override public void visitUris(@NonNull Consumer<Uri> visitor) { - mIntent.visitUris(visitor); + // TODO(b/281044385): Maybe visit intent URIs. This may require adding a dedicated + // visitUris method in the Intent class, since it can contain other intents. Otherwise, + // the basic thing to do here would be just visitor.accept(intent.getData()). } } @@ -1514,7 +1510,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public void visitUris(@NonNull Consumer<Uri> visitor) { - mResponse.visitUris(visitor); + // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse. } } @@ -1563,11 +1559,6 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG; } - - @Override - public void visitUris(@NonNull Consumer<Uri> visitor) { - mPendingIntent.visitUris(visitor); - } } /** @@ -1641,7 +1632,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public void visitUris(@NonNull Consumer<Uri> visitor) { - mResponse.visitUris(visitor); + // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse. } } @@ -2202,10 +2193,6 @@ public class RemoteViews implements Parcelable, Filter { final Icon icon = (Icon) getParameterValue(null); if (icon != null) visitIconUri(icon, visitor); break; - case INTENT: - final Intent intent = (Intent) getParameterValue(null); - if (intent != null) intent.visitUris(visitor); - break; // TODO(b/281044385): Should we do anything about type BUNDLE? } } @@ -4962,21 +4949,11 @@ public class RemoteViews implements Parcelable, Filter { */ @Deprecated public void setRemoteAdapter(@IdRes int viewId, Intent intent) { - if (isAdapterConversionEnabled()) { + if (remoteAdapterConversion()) { addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent)); - return; + } else { + addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); } - addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); - } - - /** - * @hide - * @return True if the remote adapter conversion is enabled - */ - public static boolean isAdapterConversionEnabled() { - return AppGlobals.getIntCoreSetting( - SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION, - SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) != 0; } /** @@ -6995,20 +6972,6 @@ public class RemoteViews implements Parcelable, Filter { mElementNames = parcel.createStringArrayList(); } - /** - * See {@link RemoteViews#visitUris(Consumer)}. - * - * @hide - */ - public void visitUris(@NonNull Consumer<Uri> visitor) { - if (mPendingIntent != null) { - mPendingIntent.visitUris(visitor); - } - if (mFillIntent != null) { - mFillIntent.visitUris(visitor); - } - } - private void handleViewInteraction( View v, InteractionHandler handler) { diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 7f65c52d01b8..07beb114898d 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -16,7 +16,7 @@ flag { flag { name: "defer_display_updates" - namespace: "window_manager" + namespace: "windowing_frontend" description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running" bug: "259220649" is_fixed_read_only: true diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index e494346bae5c..bd806bfb3e24 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -519,24 +519,6 @@ public final class SystemUiDeviceConfigFlags { public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot"; /** - * (boolean) Whether to enable the adapter conversion in RemoteViews - */ - public static final String REMOTEVIEWS_ADAPTER_CONVERSION = - "CursorControlFeature__remoteviews_adapter_conversion"; - - /** - * The key name used in app core settings for {@link #REMOTEVIEWS_ADAPTER_CONVERSION} - */ - public static final String KEY_REMOTEVIEWS_ADAPTER_CONVERSION = - "systemui__remoteviews_adapter_conversion"; - - /** - * Default value for whether the adapter conversion is enabled or not. This is set for - * RemoteViews and should not be a common practice. - */ - public static final boolean REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT = false; - - /** * (boolean) Whether the task manager should show a stop button if the app is allowlisted * by the user. */ diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 7d78f299c625..0be98040af73 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -72,6 +72,7 @@ import java.util.concurrent.locks.ReentrantLock; * All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by * locks on BatteryStatsImpl object. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BatteryStatsHistory { private static final boolean DEBUG = false; private static final String TAG = "BatteryStatsHistory"; @@ -259,6 +260,7 @@ public class BatteryStatsHistory { * until the first change occurs. */ @VisibleForTesting + @android.ravenwood.annotation.RavenwoodKeepWholeClass public static class TraceDelegate { // Note: certain tests currently run as platform_app which is not allowed // to set debug system properties. To ensure that system properties are set @@ -391,10 +393,18 @@ public class BatteryStatsHistory { public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize, HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, MonotonicClock monotonicClock) { + this(maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator, clock, monotonicClock, + new TraceDelegate()); + } + + @VisibleForTesting + public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize, + HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, + MonotonicClock monotonicClock, TraceDelegate traceDelegate) { mMaxHistoryFiles = maxHistoryFiles; mMaxHistoryBufferSize = maxHistoryBufferSize; mStepDetailsCalculator = stepDetailsCalculator; - mTracer = new TraceDelegate(); + mTracer = traceDelegate; mClock = clock; mMonotonicClock = monotonicClock; @@ -2096,6 +2106,7 @@ public class BatteryStatsHistory { * fewer bytes. It is a bit more expensive than just writing the long into the parcel, * but at scale saves a lot of storage and allows recording of longer battery history. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass public static final class VarintParceler { /** * Writes an array of longs into Parcel using the varint format, see diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java index 6bd5898b1637..2dffe15dc4be 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java +++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java @@ -28,6 +28,7 @@ import java.util.Iterator; /** * An iterator for {@link BatteryStats.HistoryItem}'s. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.HistoryItem>, AutoCloseable { private static final boolean DEBUG = false; diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java index 1a7efac82278..56263fb924ea 100644 --- a/core/java/com/android/internal/os/PowerStats.java +++ b/core/java/com/android/internal/os/PowerStats.java @@ -41,6 +41,7 @@ import java.util.Objects; * Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for * details. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class PowerStats { private static final String TAG = "PowerStats"; @@ -67,6 +68,7 @@ public final class PowerStats { * This descriptor is used for storing PowerStats and can also be used by power models * to adjust the algorithm in accordance with the stats available on the device. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass public static class Descriptor { public static final String XML_TAG_DESCRIPTOR = "descriptor"; private static final String XML_ATTR_ID = "id"; diff --git a/core/jni/android_os_HidlSupport.cpp b/core/jni/android_os_HidlSupport.cpp index 3e51e9315d89..e3602d8f5c72 100644 --- a/core/jni/android_os_HidlSupport.cpp +++ b/core/jni/android_os_HidlSupport.cpp @@ -15,7 +15,6 @@ */ #include <hidl/HidlTransportSupport.h> -#include <hidl/ServiceManagement.h> #include <nativehelper/JNIHelp.h> #include "core_jni_helpers.h" @@ -25,13 +24,8 @@ static jint android_os_HidlSupport_getPidIfSharable(JNIEnv*, jclass) { return android::hardware::details::getPidIfSharable(); } -static jboolean android_os_HidlSupport_isHidlSupported(JNIEnv*, jclass) { - return android::hardware::isHidlSupported(); -} - static const JNINativeMethod gHidlSupportMethods[] = { - {"getPidIfSharable", "()I", (void*)android_os_HidlSupport_getPidIfSharable}, - {"isHidlSupported", "()Z", (void*)android_os_HidlSupport_isHidlSupported}, + {"getPidIfSharable", "()I", (void*)android_os_HidlSupport_getPidIfSharable}, }; const char* const kHidlSupportPathName = "android/os/HidlSupport"; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index dd93586c340b..c6a241f2fa62 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4806,6 +4806,13 @@ <permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" android:protectionLevel="signature" /> + <!-- @FlaggedApi("com.android.server.accessibility.motion_event_observing") + @hide + @TestApi + Allows an accessibility service to observe motion events without consuming them. --> + <permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING" + android:protectionLevel="signature" /> + <!-- @hide Allows an application to collect frame statistics --> <permission android:name="android.permission.FRAME_STATS" android:protectionLevel="signature" /> diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java index 8c231de5598f..e7b5dff60110 100644 --- a/core/tests/coretests/src/android/os/BundleTest.java +++ b/core/tests/coretests/src/android/os/BundleTest.java @@ -197,7 +197,6 @@ public class BundleTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void kindofEquals_bothParcelled_same() { Bundle bundle1 = new Bundle(); bundle1.putString("StringKey", "S"); @@ -215,7 +214,6 @@ public class BundleTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void kindofEquals_bothParcelled_different() { Bundle bundle1 = new Bundle(); bundle1.putString("StringKey", "S"); @@ -247,7 +245,6 @@ public class BundleTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void kindofEquals_lazyValues() { Parcelable p1 = new CustomParcelable(13, "Tiramisu"); Parcelable p2 = new CustomParcelable(13, "Tiramisu"); @@ -281,7 +278,6 @@ public class BundleTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void kindofEquals_lazyValuesWithIdenticalParcels_returnsTrue() { Parcelable p1 = new CustomParcelable(13, "Tiramisu"); Parcelable p2 = new CustomParcelable(13, "Tiramisu"); diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java index 8cd6773936ef..851e61259241 100644 --- a/core/tests/coretests/src/android/os/MessageQueueTest.java +++ b/core/tests/coretests/src/android/os/MessageQueueTest.java @@ -16,6 +16,7 @@ package android.os; +import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.MediumTest; @@ -153,6 +154,7 @@ public class MessageQueueTest { @Test @MediumTest + @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testFieldIntegrity() throws Exception { TestHandlerThread tester = new TestFieldIntegrityHandler() { diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index 5bbd2219e2f0..26f6d696768a 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -132,7 +132,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenSameData() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); @@ -169,7 +168,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenDifferentData() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); @@ -186,7 +184,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenLimitOutOfBounds_throws() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); @@ -213,7 +210,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenLengthZero() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); @@ -232,7 +228,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenNegativeLength_throws() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); @@ -248,7 +243,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenNegativeOffset_throws() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); diff --git a/core/tests/coretests/src/android/util/SparseSetArrayTest.java b/core/tests/coretests/src/android/util/SparseSetArrayTest.java index 1df1090e0343..1c72185ea93c 100644 --- a/core/tests/coretests/src/android/util/SparseSetArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseSetArrayTest.java @@ -37,6 +37,7 @@ public class SparseSetArrayTest { public final RavenwoodRule mRavenwood = new RavenwoodRule(); @Test + @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testAddAll() { final SparseSetArray<Integer> sparseSetArray = new SparseSetArray<>(); diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 15c90474c017..c8ea3742b3aa 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -34,6 +34,9 @@ import android.app.PendingIntent; import android.appwidget.AppWidgetHostView; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; @@ -834,33 +837,6 @@ public class RemoteViewsTest { } @Test - public void visitUris_intents() { - RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); - - Uri fillIntentUri = Uri.parse("content://intent/fill"); - views.setOnCheckedChangeResponse( - R.id.layout, - RemoteViews.RemoteResponse.fromFillInIntent(new Intent("action", fillIntentUri))); - - Uri pendingIntentUri = Uri.parse("content://intent/pending"); - PendingIntent pendingIntent = getPendingIntentWithUri(pendingIntentUri); - views.setOnClickResponse( - R.id.layout, - RemoteViews.RemoteResponse.fromPendingIntent(pendingIntent)); - - Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); - views.visitUris(visitor); - verify(visitor, times(1)).accept(eq(fillIntentUri)); - verify(visitor, times(1)).accept(eq(pendingIntentUri)); - } - - private PendingIntent getPendingIntentWithUri(Uri uri) { - return PendingIntent.getActivity(mContext, 0, - new Intent("action", uri), - PendingIntent.FLAG_IMMUTABLE); - } - - @Test public void layoutInflaterFactory_nothingSet_returnsNull() { final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); assertNull(rv.getLayoutInflaterFactory()); diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java index f34b185b1c5a..c9536b9b8129 100644 --- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java @@ -57,6 +57,16 @@ public class LongArrayMultiStateCounterTest { } @Test + public void setValue() { + LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4); + + counter.setValues(0, new long[]{1, 2, 3, 4}); + counter.setValues(1, new long[]{5, 6, 7, 8}); + assertCounts(counter, 0, new long[]{1, 2, 3, 4}); + assertCounts(counter, 1, new long[]{5, 6, 7, 8}); + } + + @Test public void setEnabled() { LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4); counter.setState(0, 1000); diff --git a/core/tests/utiltests/src/android/util/TimeUtilsTest.java b/core/tests/utiltests/src/android/util/TimeUtilsTest.java index e8246c83e086..ac659e1bc593 100644 --- a/core/tests/utiltests/src/android/util/TimeUtilsTest.java +++ b/core/tests/utiltests/src/android/util/TimeUtilsTest.java @@ -18,8 +18,12 @@ package android.util; import static org.junit.Assert.assertEquals; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.runner.AndroidJUnit4; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,6 +33,9 @@ import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) public class TimeUtilsTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + public static final long SECOND_IN_MILLIS = 1000; public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60; @@ -78,6 +85,7 @@ public class TimeUtilsTest { } @Test + @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testDumpTime() { assertEquals("2023-01-01 00:00:00.000", runWithPrintWriter((pw) -> { TimeUtils.dumpTime(pw, 1672556400000L); @@ -91,6 +99,7 @@ public class TimeUtilsTest { } @Test + @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testFormatForLogging() { assertEquals("unknown", TimeUtils.formatForLogging(0)); assertEquals("unknown", TimeUtils.formatForLogging(-1)); @@ -99,6 +108,7 @@ public class TimeUtilsTest { } @Test + @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testLogTimeOfDay() { assertEquals("01-01 00:00:00.000", TimeUtils.logTimeOfDay(1672556400000L)); } diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS index f0ed6ee5cb67..e346b51a4f19 100644 --- a/libs/WindowManager/Shell/OWNERS +++ b/libs/WindowManager/Shell/OWNERS @@ -1,4 +1,4 @@ xutan@google.com # Give submodule owners in shell resource approval -per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com +per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, nmusgrave@google.com, pbdr@google.com, tkachenkoi@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index b1b196d40357..fe65fdd30e48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -16,6 +16,7 @@ package com.android.wm.shell; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; @@ -168,6 +169,13 @@ public class ShellTaskOrganizer extends TaskOrganizer implements private final Object mLock = new Object(); private StartingWindowController mStartingWindow; + /** Overlay surface for home root task */ + private final SurfaceControl mHomeTaskOverlayContainer = new SurfaceControl.Builder() + .setName("home_task_overlay_container") + .setContainerLayer() + .setHidden(false) + .build(); + /** * In charge of showing compat UI. Can be {@code null} if the device doesn't support size * compat or if this isn't the main {@link ShellTaskOrganizer}. @@ -428,6 +436,14 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } } + /** + * Returns a surface which can be used to attach overlays to the home root task + */ + @NonNull + public SurfaceControl getHomeTaskOverlayContainer() { + return mHomeTaskOverlayContainer; + } + @Override public void addStartingWindow(StartingWindowInfo info) { if (mStartingWindow != null) { @@ -485,6 +501,15 @@ public class ShellTaskOrganizer extends TaskOrganizer implements if (mUnfoldAnimationController != null) { mUnfoldAnimationController.onTaskAppeared(info.getTaskInfo(), info.getLeash()); } + + if (info.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) { + ProtoLog.v(WM_SHELL_TASK_ORG, "Adding overlay to home task"); + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.setLayer(mHomeTaskOverlayContainer, Integer.MAX_VALUE); + t.reparent(mHomeTaskOverlayContainer, info.getLeash()); + t.apply(); + } + notifyLocusVisibilityIfNeeded(info.getTaskInfo()); notifyCompatUI(info.getTaskInfo(), listener); mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo())); @@ -579,6 +604,12 @@ public class ShellTaskOrganizer extends TaskOrganizer implements notifyCompatUI(taskInfo, null /* taskListener */); // Notify the recent tasks that a task has been removed mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo)); + if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.reparent(mHomeTaskOverlayContainer, null); + t.apply(); + ProtoLog.v(WM_SHELL_TASK_ORG, "Removing overlay surface"); + } if (!ENABLE_SHELL_TRANSITIONS && (appearedInfo.getLeash() != null)) { // Preemptively clean up the leash only if shell transitions are not enabled diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java index ef763ec45994..afd3b14e8b1d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.content.Context; +import android.content.Intent; import android.graphics.Rect; import android.os.SystemClock; import android.view.LayoutInflater; @@ -227,9 +228,12 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract } private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) { + final Intent intent = taskInfo.baseIntent; return taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton && (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed || taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled) + && Intent.ACTION_MAIN.equals(intent.getAction()) + && intent.hasCategory(Intent.CATEGORY_LAUNCHER) && (!mUserAspectRatioButtonShownChecker.get() || isShowingButton()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS index deb7c6db338f..1385f42bc676 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS @@ -1,3 +1,7 @@ # WM shell sub-module desktop owners atsjenk@google.com +jorgegil@google.com madym@google.com +nmusgrave@google.com +pbdr@google.com +tkachenkoi@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS index a3803ed82844..8a0eea0a9bdd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS @@ -2,3 +2,6 @@ atsjenk@google.com jorgegil@google.com madym@google.com +nmusgrave@google.com +pbdr@google.com +tkachenkoi@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl index 644a6a5114a7..7f4a8f1d476a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl @@ -16,6 +16,7 @@ package com.android.wm.shell.transition; +import android.view.SurfaceControl; import android.window.RemoteTransition; import android.window.TransitionFilter; @@ -42,6 +43,13 @@ interface IShellTransitions { */ IBinder getShellApplyToken() = 3; - /** Set listener that will receive callbacks about transitions involving home activity */ + /** + * Set listener that will receive callbacks about transitions involving home activity. + */ oneway void setHomeTransitionListener(in IHomeTransitionListener listener) = 4; + + /** + * Returns a container surface for the home root task. + */ + SurfaceControl getHomeTaskOverlayContainer() = 5; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index b98762d5e104..af69b5272ad5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -64,7 +64,6 @@ import android.window.TransitionInfo; import android.window.TransitionMetrics; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import android.window.WindowOrganizer; import androidx.annotation.BinderThread; @@ -72,6 +71,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; @@ -172,7 +172,7 @@ public class Transitions implements RemoteCallable<Transitions>, /** Transition to animate task to desktop. */ public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15; - private final WindowOrganizer mOrganizer; + private final ShellTaskOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; private final ShellExecutor mAnimExecutor; @@ -264,7 +264,7 @@ public class Transitions implements RemoteCallable<Transitions>, public Transitions(@NonNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, - @NonNull WindowOrganizer organizer, + @NonNull ShellTaskOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, @NonNull ShellExecutor mainExecutor, @@ -280,7 +280,7 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull ShellInit shellInit, @Nullable ShellCommandHandler shellCommandHandler, @NonNull ShellController shellController, - @NonNull WindowOrganizer organizer, + @NonNull ShellTaskOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, @NonNull ShellExecutor mainExecutor, @@ -1240,6 +1240,10 @@ public class Transitions implements RemoteCallable<Transitions>, } } + private SurfaceControl getHomeTaskOverlayContainer() { + return mOrganizer.getHomeTaskOverlayContainer(); + } + /** * Interface for a callback that must be called after a TransitionHandler finishes playing an * animation. @@ -1470,6 +1474,17 @@ public class Transitions implements RemoteCallable<Transitions>, listener); }); } + + @Override + public SurfaceControl getHomeTaskOverlayContainer() { + SurfaceControl[] result = new SurfaceControl[1]; + executeRemoteCallWithTaskPermission(mTransitions, "getHomeTaskOverlayContainer", + (controller) -> { + result[0] = controller.getHomeTaskOverlayContainer(); + }, true /* blocking */); + // Return a copy as writing to parcel releases the original surface + return new SurfaceControl(result[0], "Transitions.HomeOverlay"); + } } private class SettingsObserver extends ContentObserver { diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index deebad545c5e..d718e157afdb 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -9,3 +9,6 @@ hwwang@google.com chenghsiuchang@google.com atsjenk@google.com jorgegil@google.com +nmusgrave@google.com +pbdr@google.com +tkachenkoi@google.com diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java index 065293960da7..9fe2cb11e804 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -16,6 +16,9 @@ package com.android.wm.shell.compatui; +import static android.content.Intent.ACTION_MAIN; +import static android.content.Intent.CATEGORY_LAUNCHER; +import static android.hardware.usb.UsbManager.ACTION_USB_STATE; import static android.view.WindowInsets.Type.navigationBars; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -33,6 +36,7 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.ComponentName; +import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; import android.testing.AndroidTestingRunner; @@ -108,7 +112,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { MockitoAnnotations.initMocks(this); mExecutor = new TestShellExecutor(); mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - false, /* topActivityBoundsLetterboxed */ true); + false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0, @@ -179,7 +183,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { // No diff clearInvocations(mWindowManager); TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - true, /* topActivityBoundsLetterboxed */ true); + true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true)); verify(mWindowManager, never()).updateSurfacePosition(); @@ -200,7 +204,24 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { clearInvocations(mWindowManager); clearInvocations(mLayout); taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - false, /* topActivityBoundsLetterboxed */ true); + false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); + assertFalse( + mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true)); + verify(mWindowManager).release(); + + // Recreate button + clearInvocations(mWindowManager); + taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); + assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true)); + + verify(mWindowManager).release(); + verify(mWindowManager).createLayout(/* canShow= */ true); + + // Change has no launcher category and is not main intent, dispose the component + clearInvocations(mWindowManager); + taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true, ACTION_USB_STATE, ""); assertFalse( mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true)); verify(mWindowManager).release(); @@ -217,7 +238,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { // inflated clearInvocations(mWindowManager); TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - false, /* topActivityBoundsLetterboxed */ true); + false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); verify(mWindowManager, never()).inflateLayout(); @@ -225,7 +246,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated. clearInvocations(mWindowManager); taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - true, /* topActivityBoundsLetterboxed */ true); + true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); verify(mWindowManager).inflateLayout(); @@ -304,7 +325,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { clearInvocations(mWindowManager); spyOn(mWindowManager); TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - true, /* topActivityBoundsLetterboxed */ true); + true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); // User aspect ratio settings button has not yet been shown. doReturn(false).when(mUserAspectRatioButtonShownChecker).get(); @@ -378,7 +399,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } private static TaskInfo createTaskInfo(boolean eligibleForUserAspectRatioButton, - boolean topActivityBoundsLetterboxed) { + boolean topActivityBoundsLetterboxed, String action, String category) { ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.taskId = TASK_ID; taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton = @@ -386,6 +407,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed; taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK; taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity"); + taskInfo.baseIntent = new Intent(action).addCategory(category); return taskInfo; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java index 50802c3759c9..66efa02de764 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java @@ -40,12 +40,12 @@ import android.os.RemoteException; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionInfo.TransitionMode; -import android.window.WindowOrganizer; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; @@ -68,7 +68,7 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class HomeTransitionObserverTest extends ShellTestCase { - private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class); + private final ShellTaskOrganizer mOrganizer = mock(ShellTaskOrganizer.class); private final TransactionPool mTransactionPool = mock(TransactionPool.class); private final Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 01c9bd0cb9f7..e22bf3de30e4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -87,7 +87,6 @@ import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import android.window.WindowOrganizer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -98,6 +97,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.R; import com.android.internal.policy.TransitionAnimation; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; @@ -130,7 +130,7 @@ import java.util.function.Function; @RunWith(AndroidJUnit4.class) public class ShellTransitionTests extends ShellTestCase { - private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class); + private final ShellTaskOrganizer mOrganizer = mock(ShellTaskOrganizer.class); private final TransactionPool mTransactionPool = mock(TransactionPool.class); private final Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 79a735786c38..47411701e5ab 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -79,14 +79,6 @@ cc_defaults { "external/skia/src/core", ], - product_variables: { - eng: { - lto: { - never: true, - }, - }, - }, - target: { android: { include_dirs: [ diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index 8445032293dd..69718a6c4b3e 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -43,12 +43,15 @@ cc_test { }, shared_libs: [ "libandroid_runtime", + "libbase", + "libinput", "libinputservice", "libhwui", "libgui", "libutils", ], static_libs: [ + "libflagtest", "libgmock", "libgtest", ], diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index d9efd3c2fd83..adfa91e96ebb 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include <com_android_input_flags.h> +#include <flag_macros.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <input/PointerController.h> @@ -28,6 +30,8 @@ namespace android { +namespace input_flags = com::android::input::flags; + enum TestCursorType { CURSOR_TYPE_DEFAULT = 0, CURSOR_TYPE_HOVER, @@ -261,7 +265,20 @@ TEST_F(PointerControllerTest, useStylusTypeForStylusHover) { mPointerController->reloadPointerResources(); } -TEST_F(PointerControllerTest, updatePointerIcon) { +TEST_F_WITH_FLAGS(PointerControllerTest, setPresentationBeforeDisplayViewportDoesNotLoadResources, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) { + // Setting the presentation mode before a display viewport is set will not load any resources. + mPointerController->setPresentation(PointerController::Presentation::POINTER); + ASSERT_TRUE(mPolicy->noResourcesAreLoaded()); + + // When the display is set, then the resources are loaded. + ensureDisplayViewportIsSet(); + ASSERT_TRUE(mPolicy->allResourcesAreLoaded()); +} + +TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIcon, + REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(input_flags, + enable_pointer_choreographer))) { ensureDisplayViewportIsSet(); mPointerController->setPresentation(PointerController::Presentation::POINTER); mPointerController->unfade(PointerController::Transition::IMMEDIATE); @@ -277,6 +294,24 @@ TEST_F(PointerControllerTest, updatePointerIcon) { mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type)); } +TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIconWithChoreographer, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) { + // When PointerChoreographer is enabled, the presentation mode is set before the viewport. + mPointerController->setPresentation(PointerController::Presentation::POINTER); + ensureDisplayViewportIsSet(); + mPointerController->unfade(PointerController::Transition::IMMEDIATE); + + int32_t type = CURSOR_TYPE_ADDITIONAL; + std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type); + EXPECT_CALL(*mPointerSprite, setVisible(true)); + EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); + EXPECT_CALL(*mPointerSprite, + setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(type)), + Field(&SpriteIcon::hotSpotX, hotspot.first), + Field(&SpriteIcon::hotSpotY, hotspot.second)))); + mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type)); +} + TEST_F(PointerControllerTest, setCustomPointerIcon) { ensureDisplayViewportIsSet(); mPointerController->unfade(PointerController::Transition::IMMEDIATE); diff --git a/media/OWNERS b/media/OWNERS index 4a6648e91af4..994a7b810009 100644 --- a/media/OWNERS +++ b/media/OWNERS @@ -21,7 +21,6 @@ wonsik@google.com include platform/frameworks/av:/media/janitors/media_solutions_OWNERS # SEO -sungsoo@google.com # SEA/KIR/BVE jtinker@google.com diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java index 0f48abeb6882..c881a03c6732 100644 --- a/media/java/android/media/AudioHalVersionInfo.java +++ b/media/java/android/media/AudioHalVersionInfo.java @@ -80,9 +80,8 @@ public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHa * List of all valid Audio HAL versions. This list need to be in sync with sAudioHALVersions * defined in frameworks/av/media/libaudiohal/FactoryHalHidl.cpp. */ - // TODO: add AIDL_1_0 with sAudioHALVersions. public static final @NonNull List<AudioHalVersionInfo> VERSIONS = - List.of(HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0, HIDL_4_0); + List.of(AIDL_1_0, HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0, HIDL_4_0); private static final String TAG = "AudioHalVersionInfo"; private AudioHalVersion mHalVersion = new AudioHalVersion(); diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS index bbe5e06bb282..058c5be6af6c 100644 --- a/media/java/android/media/OWNERS +++ b/media/java/android/media/OWNERS @@ -2,7 +2,6 @@ fgoldfain@google.com elaurent@google.com lajos@google.com -sungsoo@google.com jmtrivi@google.com # go/android-fwk-media-solutions for info on areas of ownership. diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index a8ffd2b4dd8f..901ea46ba38b 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -110,10 +110,6 @@ interface ITvInputManager { void pauseRecording(in IBinder sessionToken, in Bundle params, int userId); void resumeRecording(in IBinder sessionToken, in Bundle params, int userId); - // For playback control - void startPlayback(in IBinder sessionToken, int userId); - void stopPlayback(in IBinder sessionToken, int mode, int userId); - // For broadcast info void requestBroadcastInfo(in IBinder sessionToken, in BroadcastInfoRequest request, int userId); void removeBroadcastInfo(in IBinder sessionToken, int id, int userId); diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index e37ee6e342e5..5246f5c4dc1e 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -63,9 +63,6 @@ oneway interface ITvInputSession { void timeShiftSetMode(int mode); void timeShiftEnablePositionTracking(boolean enable); - void startPlayback(); - void stopPlayback(int mode); - // For the recording session void startRecording(in Uri programUri, in Bundle params); void stopRecording(); diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index ae3ee6535c71..d749b91e3889 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -79,8 +79,6 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_TIME_SHIFT_SET_MODE = 30; private static final int DO_SET_TV_MESSAGE_ENABLED = 31; private static final int DO_NOTIFY_TV_MESSAGE = 32; - private static final int DO_STOP_PLAYBACK = 33; - private static final int DO_START_PLAYBACK = 34; private final boolean mIsRecordingSession; private final HandlerCaller mCaller; @@ -288,14 +286,6 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2); break; } - case DO_STOP_PLAYBACK: { - mTvInputSessionImpl.stopPlayback(msg.arg1); - break; - } - case DO_START_PLAYBACK: { - mTvInputSessionImpl.startPlayback(); - break; - } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -493,17 +483,6 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand enabled)); } - @Override - public void stopPlayback(int mode) { - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_STOP_PLAYBACK, mode)); - } - - @Override - public void startPlayback() { - mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_START_PLAYBACK)); - } - - private final class TvInputEventReceiver extends InputEventReceiver { TvInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index d4612ea8c7d4..631ab9a2693d 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -3302,30 +3302,6 @@ public final class TvInputManager { } } - void stopPlayback(int mode) { - if (mToken == null) { - Log.w(TAG, "The session has been already released"); - return; - } - try { - mService.stopPlayback(mToken, mode, mUserId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - void startPlayback() { - if (mToken == null) { - Log.w(TAG, "The session has been already released"); - return; - } - try { - mService.startPlayback(mToken, mUserId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Sends TV messages to the service for testing purposes */ diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 55fa51755177..720d9a6291de 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -34,7 +34,6 @@ import android.graphics.Rect; import android.hardware.hdmi.HdmiDeviceInfo; import android.media.AudioPresentation; import android.media.PlaybackParams; -import android.media.tv.interactive.TvInteractiveAppService; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; @@ -1532,32 +1531,6 @@ public abstract class TvInputService extends Service { } /** - * Called when the application requests playback of the Audio, Video, and CC streams to be - * stopped, but the metadata should continue to be filtered. - * - * <p>The metadata that will continue to be filtered includes the PSI - * (Program specific information) and SI (Service Information), part of ISO/IEC 13818-1. - * - * <p> Note that this is different form {@link #timeShiftPause()} as should release the - * stream, making it impossible to resume from this position again. - * @param mode - * @hide - */ - public void onStopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) { - } - - /** - * Starts playback of the Audio, Video, and CC streams. - * - * <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be - * used after stopping playback. This is used to restart playback from the current position - * in the live broadcast. - * @hide - */ - public void onStartPlayback() { - } - - /** * Called when the application requests to play a given recorded TV program. * * @param recordedProgramUri The URI of a recorded TV program. @@ -2020,20 +1993,6 @@ public abstract class TvInputService extends Service { } /** - * Calls {@link #onStopPlayback(int)}. - */ - void stopPlayback(int mode) { - onStopPlayback(mode); - } - - /** - * Calls {@link #onStartPlayback()}. - */ - void startPlayback() { - onStartPlayback(); - } - - /** * Calls {@link #onTimeShiftPlay(Uri)}. */ void timeShiftPlay(Uri recordedProgramUri) { diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 233f96675543..196b5c3112c0 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -37,7 +37,6 @@ import android.media.PlaybackParams; import android.media.tv.TvInputManager.Session; import android.media.tv.TvInputManager.Session.FinishedInputEventCallback; import android.media.tv.TvInputManager.SessionCallback; -import android.media.tv.interactive.TvInteractiveAppService; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -644,35 +643,6 @@ public class TvView extends ViewGroup { } } - /** - * Stops playback of the Audio, Video, and CC streams, but continue filtering the metadata. - * - * <p>The metadata that will continue to be filtered includes the PSI - * (Program specific information) and SI (Service Information), part of ISO/IEC 13818-1. - * - * <p> Note that this is different form {@link #timeShiftPause()} as this completely drops - * the stream, making it impossible to resume from this position again. - * @hide - */ - public void stopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) { - if (mSession != null) { - mSession.stopPlayback(mode); - } - } - - /** - * Starts playback of the Audio, Video, and CC streams. - * - * <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be - * used after stopping playback. This is used to restart playback from the current position - * in the live broadcast. - * @hide - */ - public void startPlayback() { - if (mSession != null) { - mSession.startPlayback(); - } - } /** * Sends TV messages to the session for testing purposes diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index 281eba66123b..6019aa8560e1 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -156,7 +156,7 @@ <string name="permission_storage">Photos and media</string> <!-- Notification permission will be granted of corresponding profile [CHAR LIMIT=30] --> - <string name="permission_notification">Notifications</string> + <string name="permission_notifications">Notifications</string> <!-- Apps permission will be granted of corresponding profile [CHAR LIMIT=30] --> <string name="permission_app_streaming">Apps</string> @@ -165,28 +165,31 @@ <string name="permission_nearby_device_streaming">Streaming</string> <!-- Description of phone permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_phone_summary">Can make and manage phone calls</string> + <string name="permission_phone_summary">Make and manage phone calls</string> <!-- Description of Call logs permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_call_logs_summary">Can read and write phone call log</string> + <string name="permission_call_logs_summary">Read and write phone call log</string> <!-- Description of SMS permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_sms_summary">Can send and view SMS messages</string> + <string name="permission_sms_summary">Send and view SMS messages</string> <!-- Description of contacts permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_contacts_summary">Can access your contacts</string> + <string name="permission_contacts_summary">Access your contacts</string> <!-- Description of calendar permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_calendar_summary">Can access your calendar</string> + <string name="permission_calendar_summary">Access your calendar</string> <!-- Description of microphone permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_microphone_summary">Can record audio</string> + <string name="permission_microphone_summary">Record audio</string> <!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_nearby_devices_summary">Can find, connect to, and determine the relative position of nearby devices</string> + <string name="permission_nearby_devices_summary">Find, connect to, and determine the relative position of nearby devices</string> - <!-- Description of notification permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_notification_summary">Can read all notifications, including information like contacts, messages, and photos</string> + <!-- Description of NLA (notification listener access) of corresponding profile [CHAR LIMIT=NONE] --> + <string name="permission_notification_listener_access_summary">Read all notifications, including information like contacts, messages, and photos</string> + + <!-- Description of NLA & POST_NOTIFICATION of corresponding profile [CHAR LIMIT=NONE] --> + <string name="permission_notifications_summary">\u2022 Read all notifications, including info like contacts, messages, and photos<br/>\u2022 Send notifications<br/><br/>You can manage this app\'s ability to read and send notifications anytime in Settings > Notifications.</string> <!-- Description of app streaming permission of corresponding profile [CHAR LIMIT=NONE] --> <string name="permission_app_streaming_summary">Stream your phone\u2019s apps</string> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 97016f5384f6..0abf285bd19c 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -27,13 +27,13 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState; import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT; -import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TYPES; -import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME; -import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICON; -import static com.android.companiondevicemanager.CompanionDeviceResources.SUMMARIES; +import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_PERMISSIONS; +import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_NAMES; +import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICONS; +import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_SUMMARIES; import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES; import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_SELF_MANAGED_PROFILES; -import static com.android.companiondevicemanager.CompanionDeviceResources.TITLES; +import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_TITLES; import static com.android.companiondevicemanager.Utils.getApplicationLabel; import static com.android.companiondevicemanager.Utils.getHtmlFromResources; import static com.android.companiondevicemanager.Utils.getIcon; @@ -482,7 +482,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements return; } - title = getHtmlFromResources(this, TITLES.get(deviceProfile), deviceName); + title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), deviceName); setupPermissionList(deviceProfile); // Summary is not needed for selfManaged dialog. @@ -525,7 +525,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements mSelectedDevice = requireNonNull(deviceFilterPairs.get(0)); - final Drawable profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile)); + final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile)); updatePermissionUi(); @@ -545,14 +545,14 @@ public class CompanionDeviceActivity extends FragmentActivity implements throw new RuntimeException("Unsupported profile " + deviceProfile); } - profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile)); + profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile)); if (deviceProfile == null) { title = getHtmlFromResources(this, R.string.chooser_title_non_profile, appLabel); mButtonNotAllowMultipleDevices.setText(R.string.consent_no); } else { title = getHtmlFromResources(this, - R.string.chooser_title, getString(PROFILES_NAME.get(deviceProfile))); + R.string.chooser_title, getString(PROFILE_NAMES.get(deviceProfile))); } mDeviceAdapter = new DeviceListAdapter(this, this::onDeviceClicked); @@ -609,10 +609,10 @@ public class CompanionDeviceActivity extends FragmentActivity implements private void updatePermissionUi() { final String deviceProfile = mRequest.getDeviceProfile(); - final int summaryResourceId = SUMMARIES.get(deviceProfile); + final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile); final String remoteDeviceName = mSelectedDevice.getDisplayName(); final Spanned title = getHtmlFromResources( - this, TITLES.get(deviceProfile), mAppLabel, remoteDeviceName); + this, PROFILE_TITLES.get(deviceProfile), mAppLabel, remoteDeviceName); final Spanned summary; // No need to show permission consent dialog if it is a isSkipPrompt(true) @@ -680,7 +680,8 @@ public class CompanionDeviceActivity extends FragmentActivity implements // and when mPermissionListRecyclerView is fully populated. // Lastly, disable the Allow and Don't allow buttons. private void setupPermissionList(String deviceProfile) { - final List<Integer> permissionTypes = new ArrayList<>(PERMISSION_TYPES.get(deviceProfile)); + final List<Integer> permissionTypes = new ArrayList<>( + PROFILE_PERMISSIONS.get(deviceProfile)); mPermissionListAdapter.setPermissionType(permissionTypes); mPermissionListRecyclerView.setAdapter(mPermissionListAdapter); mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java index 551e9754032b..23a11d618085 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java @@ -22,28 +22,15 @@ import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER; import static android.companion.AssociationRequest.DEVICE_PROFILE_GLASSES; import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING; import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH; - -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_APP_STREAMING; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALENDAR; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALL_LOGS; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CHANGE_MEDIA_OUTPUT; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CONTACTS; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_MICROPHONE; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICES; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICE_STREAMING; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NOTIFICATION; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_PHONE; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_SMS; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_STORAGE; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; +import android.os.Build; import android.util.ArrayMap; import android.util.ArraySet; -import com.android.media.flags.Flags; - import java.util.Arrays; import java.util.List; import java.util.Map; @@ -54,7 +41,85 @@ import java.util.Set; * for the corresponding profile. */ final class CompanionDeviceResources { - static final Map<String, Integer> TITLES; + + // Permission resources + private static final int PERMISSION_NOTIFICATION_LISTENER_ACCESS = 0; + private static final int PERMISSION_STORAGE = 1; + private static final int PERMISSION_APP_STREAMING = 2; + private static final int PERMISSION_PHONE = 3; + private static final int PERMISSION_SMS = 4; + private static final int PERMISSION_CONTACTS = 5; + private static final int PERMISSION_CALENDAR = 6; + private static final int PERMISSION_NEARBY_DEVICES = 7; + private static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8; + private static final int PERMISSION_MICROPHONE = 9; + private static final int PERMISSION_CALL_LOGS = 10; + // Notification Listener Access & POST_NOTIFICATION permission + private static final int PERMISSION_NOTIFICATIONS = 11; + private static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 12; + + static final Map<Integer, Integer> PERMISSION_TITLES; + static { + final Map<Integer, Integer> map = new ArrayMap<>(); + map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, R.string.permission_notifications); + map.put(PERMISSION_STORAGE, R.string.permission_storage); + map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming); + map.put(PERMISSION_PHONE, R.string.permission_phone); + map.put(PERMISSION_SMS, R.string.permission_sms); + map.put(PERMISSION_CONTACTS, R.string.permission_contacts); + map.put(PERMISSION_CALENDAR, R.string.permission_calendar); + map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices); + map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming); + map.put(PERMISSION_MICROPHONE, R.string.permission_microphone); + map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs); + map.put(PERMISSION_NOTIFICATIONS, R.string.permission_notifications); + map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control); + PERMISSION_TITLES = unmodifiableMap(map); + } + + static final Map<Integer, Integer> PERMISSION_SUMMARIES; + static { + final Map<Integer, Integer> map = new ArrayMap<>(); + map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, + R.string.permission_notification_listener_access_summary); + map.put(PERMISSION_STORAGE, R.string.permission_storage_summary); + map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming_summary); + map.put(PERMISSION_PHONE, R.string.permission_phone_summary); + map.put(PERMISSION_SMS, R.string.permission_sms_summary); + map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary); + map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary); + map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary); + map.put(PERMISSION_NEARBY_DEVICE_STREAMING, + R.string.permission_nearby_device_streaming_summary); + map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary); + map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs_summary); + map.put(PERMISSION_NOTIFICATIONS, R.string.permission_notifications_summary); + map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control_summary); + PERMISSION_SUMMARIES = unmodifiableMap(map); + } + + static final Map<Integer, Integer> PERMISSION_ICONS; + static { + final Map<Integer, Integer> map = new ArrayMap<>(); + map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, R.drawable.ic_permission_notifications); + map.put(PERMISSION_STORAGE, R.drawable.ic_permission_storage); + map.put(PERMISSION_APP_STREAMING, R.drawable.ic_permission_app_streaming); + map.put(PERMISSION_PHONE, R.drawable.ic_permission_phone); + map.put(PERMISSION_SMS, R.drawable.ic_permission_sms); + map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts); + map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar); + map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices); + map.put(PERMISSION_NEARBY_DEVICE_STREAMING, + R.drawable.ic_permission_nearby_device_streaming); + map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone); + map.put(PERMISSION_CALL_LOGS, R.drawable.ic_permission_call_logs); + map.put(PERMISSION_NOTIFICATIONS, R.drawable.ic_permission_notifications); + map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.drawable.ic_permission_media_routing_control); + PERMISSION_ICONS = unmodifiableMap(map); + } + + // Profile resources + static final Map<String, Integer> PROFILE_TITLES; static { final Map<String, Integer> map = new ArrayMap<>(); map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming); @@ -65,71 +130,61 @@ final class CompanionDeviceResources { map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses); map.put(null, R.string.confirmation_title); - TITLES = unmodifiableMap(map); + PROFILE_TITLES = unmodifiableMap(map); + } + + static final Map<String, Integer> PROFILE_SUMMARIES; + static { + final Map<String, Integer> map = new ArrayMap<>(); + map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch); + map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses); + map.put(null, R.string.summary_generic); + + PROFILE_SUMMARIES = unmodifiableMap(map); } - static final Map<String, List<Integer>> PERMISSION_TYPES; + static final Map<String, List<Integer>> PROFILE_PERMISSIONS; static { final Map<String, List<Integer>> map = new ArrayMap<>(); map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING)); map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList( - PERMISSION_NOTIFICATION, PERMISSION_STORAGE)); + PERMISSION_NOTIFICATION_LISTENER_ACCESS, PERMISSION_STORAGE)); map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING)); - if (!Flags.enablePrivilegedRoutingForMediaRoutingControl()) { - map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE, - PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR, - PERMISSION_NEARBY_DEVICES)); - } else { - map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE, + if (Build.VERSION.SDK_INT > UPSIDE_DOWN_CAKE) { + map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATIONS, PERMISSION_PHONE, PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR, PERMISSION_NEARBY_DEVICES, PERMISSION_CHANGE_MEDIA_OUTPUT)); + } else { + map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION_LISTENER_ACCESS, + PERMISSION_PHONE, PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, + PERMISSION_CALENDAR, PERMISSION_NEARBY_DEVICES)); } - map.put(DEVICE_PROFILE_GLASSES, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE, - PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_MICROPHONE, + map.put(DEVICE_PROFILE_GLASSES, Arrays.asList(PERMISSION_NOTIFICATION_LISTENER_ACCESS, + PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_MICROPHONE, PERMISSION_NEARBY_DEVICES)); - PERMISSION_TYPES = unmodifiableMap(map); + PROFILE_PERMISSIONS = unmodifiableMap(map); } - static final Map<String, Integer> SUMMARIES; - static { - final Map<String, Integer> map = new ArrayMap<>(); - map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch); - map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses); - map.put(null, R.string.summary_generic); - - SUMMARIES = unmodifiableMap(map); - } - - static final Map<String, Integer> PROFILES_NAME; + static final Map<String, Integer> PROFILE_NAMES; static { final Map<String, Integer> map = new ArrayMap<>(); map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch); map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_glasses); map.put(null, R.string.profile_name_generic); - PROFILES_NAME = unmodifiableMap(map); - } - - static final Map<String, Integer> PROFILES_NAME_MULTI; - static { - final Map<String, Integer> map = new ArrayMap<>(); - map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_generic); - map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch); - map.put(null, R.string.profile_name_generic); - - PROFILES_NAME_MULTI = unmodifiableMap(map); + PROFILE_NAMES = unmodifiableMap(map); } - static final Map<String, Integer> PROFILE_ICON; + static final Map<String, Integer> PROFILE_ICONS; static { final Map<String, Integer> map = new ArrayMap<>(); map.put(DEVICE_PROFILE_WATCH, R.drawable.ic_watch); map.put(DEVICE_PROFILE_GLASSES, R.drawable.ic_glasses); map.put(null, R.drawable.ic_device_other); - PROFILE_ICON = unmodifiableMap(map); + PROFILE_ICONS = unmodifiableMap(map); } static final Set<String> SUPPORTED_PROFILES; diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java index e21aee3cedb8..4a1f8014a2f0 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java @@ -16,14 +16,14 @@ package com.android.companiondevicemanager; +import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_ICONS; +import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_SUMMARIES; +import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TITLES; import static com.android.companiondevicemanager.Utils.getHtmlFromResources; import static com.android.companiondevicemanager.Utils.getIcon; -import static java.util.Collections.unmodifiableMap; - import android.content.Context; import android.text.Spanned; -import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -35,7 +35,6 @@ import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; import java.util.List; -import java.util.Map; class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.ViewHolder> { private final Context mContext; @@ -43,75 +42,6 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V // Add the expand buttons if permissions are more than PERMISSION_SIZE in the permission list. private static final int PERMISSION_SIZE = 2; - static final int PERMISSION_NOTIFICATION = 0; - static final int PERMISSION_STORAGE = 1; - static final int PERMISSION_APP_STREAMING = 2; - static final int PERMISSION_PHONE = 3; - static final int PERMISSION_SMS = 4; - static final int PERMISSION_CONTACTS = 5; - static final int PERMISSION_CALENDAR = 6; - static final int PERMISSION_NEARBY_DEVICES = 7; - static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8; - static final int PERMISSION_MICROPHONE = 9; - static final int PERMISSION_CALL_LOGS = 10; - static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 11; - - private static final Map<Integer, Integer> sTitleMap; - static { - final Map<Integer, Integer> map = new ArrayMap<>(); - map.put(PERMISSION_NOTIFICATION, R.string.permission_notification); - map.put(PERMISSION_STORAGE, R.string.permission_storage); - map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming); - map.put(PERMISSION_PHONE, R.string.permission_phone); - map.put(PERMISSION_SMS, R.string.permission_sms); - map.put(PERMISSION_CONTACTS, R.string.permission_contacts); - map.put(PERMISSION_CALENDAR, R.string.permission_calendar); - map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices); - map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming); - map.put(PERMISSION_MICROPHONE, R.string.permission_microphone); - map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs); - map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control); - sTitleMap = unmodifiableMap(map); - } - - private static final Map<Integer, Integer> sSummaryMap; - static { - final Map<Integer, Integer> map = new ArrayMap<>(); - map.put(PERMISSION_NOTIFICATION, R.string.permission_notification_summary); - map.put(PERMISSION_STORAGE, R.string.permission_storage_summary); - map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming_summary); - map.put(PERMISSION_PHONE, R.string.permission_phone_summary); - map.put(PERMISSION_SMS, R.string.permission_sms_summary); - map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary); - map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary); - map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary); - map.put(PERMISSION_NEARBY_DEVICE_STREAMING, - R.string.permission_nearby_device_streaming_summary); - map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary); - map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs_summary); - map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control_summary); - sSummaryMap = unmodifiableMap(map); - } - - private static final Map<Integer, Integer> sIconMap; - static { - final Map<Integer, Integer> map = new ArrayMap<>(); - map.put(PERMISSION_NOTIFICATION, R.drawable.ic_permission_notifications); - map.put(PERMISSION_STORAGE, R.drawable.ic_permission_storage); - map.put(PERMISSION_APP_STREAMING, R.drawable.ic_permission_app_streaming); - map.put(PERMISSION_PHONE, R.drawable.ic_permission_phone); - map.put(PERMISSION_SMS, R.drawable.ic_permission_sms); - map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts); - map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar); - map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices); - map.put(PERMISSION_NEARBY_DEVICE_STREAMING, - R.drawable.ic_permission_nearby_device_streaming); - map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone); - map.put(PERMISSION_CALL_LOGS, R.drawable.ic_permission_call_logs); - map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.drawable.ic_permission_media_routing_control); - sIconMap = unmodifiableMap(map); - } - PermissionListAdapter(Context context) { mContext = context; } @@ -121,7 +51,8 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V View view = LayoutInflater.from(parent.getContext()).inflate( R.layout.list_item_permission, parent, false); ViewHolder viewHolder = new ViewHolder(view); - viewHolder.mPermissionIcon.setImageDrawable(getIcon(mContext, sIconMap.get(viewType))); + viewHolder.mPermissionIcon.setImageDrawable( + getIcon(mContext, PERMISSION_ICONS.get(viewType))); if (viewHolder.mExpandButton.getTag() == null) { viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more); @@ -165,8 +96,8 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V @Override public void onBindViewHolder(ViewHolder holder, int position) { int type = getItemViewType(position); - final Spanned title = getHtmlFromResources(mContext, sTitleMap.get(type)); - final Spanned summary = getHtmlFromResources(mContext, sSummaryMap.get(type)); + final Spanned title = getHtmlFromResources(mContext, PERMISSION_TITLES.get(type)); + final Spanned summary = getHtmlFromResources(mContext, PERMISSION_SUMMARIES.get(type)); holder.mPermissionSummary.setText(summary); holder.mPermissionName.setText(title); @@ -192,6 +123,7 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V private final TextView mPermissionSummary; private final ImageView mPermissionIcon; private final ImageButton mExpandButton; + ViewHolder(View itemView) { super(itemView); mPermissionName = itemView.findViewById(R.id.permission_name); @@ -203,7 +135,7 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V private void setAccessibility(View view, int viewType, int action, int statusResourceId, int actionResourceId) { - final String permission = mContext.getString(sTitleMap.get(viewType)); + final String permission = mContext.getString(PERMISSION_TITLES.get(viewType)); if (actionResourceId != 0) { view.announceForAccessibility( diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml index e3b93ba34045..f4641b9930cb 100644 --- a/packages/PackageInstaller/res/values/strings.xml +++ b/packages/PackageInstaller/res/values/strings.xml @@ -202,11 +202,6 @@ <!-- Dialog attributes to indicate parse errors --> <string name="Parse_error_dlg_text">There was a problem parsing the package.</string> - <!-- Title of dialog telling users that Install/Uninstall action is not supported on Android Wear. [CHAR LIMIT=30] --> - <string name="wear_not_allowed_dlg_title">Android Wear</string> - <!-- Title of dialog telling users that Install/Uninstall action is not supported on Android Wear. [CHAR LIMIT=none] --> - <string name="wear_not_allowed_dlg_text">Install/Uninstall actions not supported on Wear.</string> - <!-- Message that the app to be installed is being staged [CHAR LIMIT=50] --> <string name="message_staging">Staging app…</string> diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java index 96a11eeb3b78..5b39f4ee1541 100644 --- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java +++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java @@ -112,26 +112,6 @@ public class RestrictedLockUtils { } /** - * Shows restricted setting dialog. - */ - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - public static void sendShowRestrictedSettingDialogIntent(Context context, - String packageName, int uid) { - final Intent intent = getShowRestrictedSettingsIntent(packageName, uid); - context.startActivity(intent); - } - - /** - * Gets restricted settings dialog intent. - */ - private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) { - final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); - intent.putExtra(Intent.EXTRA_UID, uid); - return intent; - } - - /** * Checks if current user is profile or not */ @RequiresApi(Build.VERSION_CODES.M) @@ -238,4 +218,35 @@ public class RestrictedLockUtils { + '}'; } } + + + /** + * Shows restricted setting dialog. + * + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + public static void sendShowRestrictedSettingDialogIntent(Context context, + String packageName, int uid) { + final Intent intent = getShowRestrictedSettingsIntent(packageName, uid); + context.startActivity(intent); + } + + /** + * Gets restricted settings dialog intent. + * + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated + private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) { + final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(Intent.EXTRA_UID, uid); + return intent; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index 4454b710b7e4..02374462f093 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -77,6 +77,9 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { ECM_KEYS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS); ECM_KEYS.add(Manifest.permission.BIND_DEVICE_ADMIN); } + + ECM_KEYS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS); + ECM_KEYS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java index db2a6ec2da68..50e3bd08026c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java @@ -96,12 +96,29 @@ public class RestrictedPreference extends TwoTargetPreference { mHelper.checkRestrictionAndSetDisabled(userRestriction, userId); } + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param restriction The key identifying the setting + * @param packageName the package to check the restriction for + * @param uid the uid of the package + */ + public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) { + mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid); + } + @Override public void setEnabled(boolean enabled) { if (enabled && isDisabledByAdmin()) { mHelper.setDisabledByAdmin(null); return; } + + if (enabled && isDisabledByEcm()) { + mHelper.setDisabledByEcm(null); + return; + } + super.setEnabled(enabled); } @@ -111,16 +128,14 @@ public class RestrictedPreference extends TwoTargetPreference { } } - public void setDisabledByAppOps(boolean disabled) { - if (mHelper.setDisabledByAppOps(disabled)) { - notifyChanged(); - } - } - public boolean isDisabledByAdmin() { return mHelper.isDisabledByAdmin(); } + public boolean isDisabledByEcm() { + return mHelper.isDisabledByEcm(); + } + public int getUid() { return mHelper != null ? mHelper.uid : Process.INVALID_UID; } @@ -128,4 +143,16 @@ public class RestrictedPreference extends TwoTargetPreference { public String getPackageName() { return mHelper != null ? mHelper.packageName : null; } + + /** + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated + public void setDisabledByAppOps(boolean disabled) { + if (mHelper.setDisabledByAppOps(disabled)) { + notifyChanged(); + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 29ea25e13835..a479269f40fb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -17,10 +17,12 @@ package com.android.settingslib; import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY; + import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.content.Intent; import android.content.res.TypedArray; import android.os.Build; import android.os.UserHandle; @@ -52,7 +54,8 @@ public class RestrictedPreferenceHelper { private String mAttrUserRestriction = null; private boolean mDisabledSummary = false; - private boolean mDisabledByAppOps; + private boolean mDisabledByEcm; + private Intent mDisabledByEcmIntent = null; public RestrictedPreferenceHelper(Context context, Preference preference, AttributeSet attrs, String packageName, int uid) { @@ -101,7 +104,7 @@ public class RestrictedPreferenceHelper { * Modify PreferenceViewHolder to add padlock if restriction is disabled. */ public void onBindViewHolder(PreferenceViewHolder holder) { - if (mDisabledByAdmin || mDisabledByAppOps) { + if (mDisabledByAdmin || mDisabledByEcm) { holder.itemView.setEnabled(true); } if (mDisabledSummary) { @@ -112,7 +115,7 @@ public class RestrictedPreferenceHelper { : mContext.getString(R.string.disabled_by_admin_summary_text); if (mDisabledByAdmin) { summaryView.setText(disabledText); - } else if (mDisabledByAppOps) { + } else if (mDisabledByEcm) { summaryView.setText(R.string.disabled_by_app_ops_text); } else if (TextUtils.equals(disabledText, summaryView.getText())) { // It's previously set to disabled text, clear it. @@ -144,7 +147,12 @@ public class RestrictedPreferenceHelper { RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin); return true; } - if (mDisabledByAppOps) { + if (mDisabledByEcm) { + if (android.security.Flags.extendEcmToAllSettings()) { + mContext.startActivity(mDisabledByEcmIntent); + return true; + } + RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName, uid); return true; @@ -174,6 +182,20 @@ public class RestrictedPreferenceHelper { } /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param restriction The key identifying the setting + * @param packageName the package to check the restriction for + * @param uid the uid of the package + */ + public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) { + updatePackageDetails(packageName, uid); + Intent intent = RestrictedLockUtilsInternal.checkIfRequiresEnhancedConfirmation( + mContext, restriction, uid, packageName); + setDisabledByEcm(intent); + } + + /** * @return EnforcedAdmin if we have been passed the restriction in the xml. */ public EnforcedAdmin checkRestrictionEnforced() { @@ -211,10 +233,19 @@ public class RestrictedPreferenceHelper { return changed; } - public boolean setDisabledByAppOps(boolean disabled) { + /** + * Disable the preference based on the passed in Intent + * @param disabledIntent The intent which is started when the user clicks the disabled + * preference. If it is {@code null}, then this preference will be enabled. Otherwise, it will + * be disabled. + * @return true if the disabled state was changed. + */ + public boolean setDisabledByEcm(Intent disabledIntent) { + boolean disabled = disabledIntent != null; boolean changed = false; - if (mDisabledByAppOps != disabled) { - mDisabledByAppOps = disabled; + if (mDisabledByEcm != disabled) { + mDisabledByEcmIntent = disabledIntent; + mDisabledByEcm = disabled; changed = true; updateDisabledState(); } @@ -226,8 +257,8 @@ public class RestrictedPreferenceHelper { return mDisabledByAdmin; } - public boolean isDisabledByAppOps() { - return mDisabledByAppOps; + public boolean isDisabledByEcm() { + return mDisabledByEcm; } public void updatePackageDetails(String packageName, int uid) { @@ -236,13 +267,31 @@ public class RestrictedPreferenceHelper { } private void updateDisabledState() { + boolean isEnabled = !(mDisabledByAdmin || mDisabledByEcm); if (!(mPreference instanceof RestrictedTopLevelPreference)) { - mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps)); + mPreference.setEnabled(isEnabled); } if (mPreference instanceof PrimarySwitchPreference) { - ((PrimarySwitchPreference) mPreference) - .setSwitchEnabled(!(mDisabledByAdmin || mDisabledByAppOps)); + ((PrimarySwitchPreference) mPreference).setSwitchEnabled(isEnabled); } } + + + /** + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated + public boolean setDisabledByAppOps(boolean disabled) { + boolean changed = false; + if (mDisabledByEcm != disabled) { + mDisabledByEcm = disabled; + changed = true; + updateDisabledState(); + } + + return changed; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index 60321eb1a9dc..3b8f66577f6e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -197,6 +197,17 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat { mHelper.checkRestrictionAndSetDisabled(userRestriction, userId); } + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param restriction The key identifying the setting + * @param packageName the package to check the restriction for + * @param uid the uid of the package + */ + public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) { + mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid); + } + @Override public void setEnabled(boolean enabled) { boolean changed = false; @@ -204,8 +215,8 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat { mHelper.setDisabledByAdmin(null); changed = true; } - if (enabled && isDisabledByAppOps()) { - mHelper.setDisabledByAppOps(false); + if (enabled && isDisabledByEcm()) { + mHelper.setDisabledByEcm(null); changed = true; } if (!changed) { @@ -223,25 +234,50 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat { return mHelper.isDisabledByAdmin(); } + public boolean isDisabledByEcm() { + return mHelper.isDisabledByEcm(); + } + + /** + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated private void setDisabledByAppOps(boolean disabled) { if (mHelper.setDisabledByAppOps(disabled)) { notifyChanged(); } } - public boolean isDisabledByAppOps() { - return mHelper.isDisabledByAppOps(); - } - + /** + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated public int getUid() { return mHelper != null ? mHelper.uid : Process.INVALID_UID; } + /** + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated public String getPackageName() { return mHelper != null ? mHelper.packageName : null; } - /** Updates enabled state based on associated package. */ + /** + * Updates enabled state based on associated package + * + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated public void updateState( @NonNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled) { mHelper.updatePackageDetails(packageName, uid); @@ -258,7 +294,7 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat { setEnabled(false); } else if (isEnabled) { setEnabled(true); - } else if (appOpsAllowed && isDisabledByAppOps()) { + } else if (appOpsAllowed && isDisabledByEcm()) { setEnabled(true); } else if (!appOpsAllowed){ setDisabledByAppOps(true); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index d12d9d665a8c..bacab0f8f1e8 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -879,6 +879,9 @@ <uses-permission android:name="android.permission.GET_BINDING_UID_IMPORTANCE" /> + <!-- Permissions required for CTS test - CtsAccessibilityServiceTestCases--> + <uses-permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 7061e2cb8a4e..f10ac1bf1e0a 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -204,6 +204,7 @@ android_library { "lottie", "LowLightDreamLib", "motion_tool_lib", + "notification_flags_lib", ], libs: [ "keepanno-annotations", @@ -328,6 +329,7 @@ android_library { "androidx.compose.ui_ui", "flag-junit", "platform-test-annotations", + "notification_flags_lib", ], } 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 914e5f2c17bf..fd04b5ee0d9c 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 @@ -51,6 +51,7 @@ object ComposeFacade : BaseComposeFacade { activity: ComponentActivity, viewModel: BaseCommunalViewModel, onOpenWidgetPicker: () -> Unit, + onEditDone: () -> Unit, ) { throwComposeUnavailableError() } 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 59bd95bd9027..5055ee1d73f6 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 @@ -66,12 +66,14 @@ object ComposeFacade : BaseComposeFacade { activity: ComponentActivity, viewModel: BaseCommunalViewModel, onOpenWidgetPicker: () -> Unit, + onEditDone: () -> Unit, ) { activity.setContent { PlatformTheme { CommunalHub( viewModel = viewModel, onOpenWidgetPicker = onOpenWidgetPicker, + onEditDone = onEditDone, ) } } 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 e8ecd3a66186..2a9cf0fdc507 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 @@ -25,10 +25,12 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan @@ -36,23 +38,41 @@ import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel @@ -66,21 +86,38 @@ fun CommunalHub( modifier: Modifier = Modifier, viewModel: BaseCommunalViewModel, onOpenWidgetPicker: (() -> Unit)? = null, + onEditDone: (() -> Unit)? = null, ) { val communalContent by viewModel.communalContent.collectAsState(initial = emptyList()) + var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } + var toolbarSize: IntSize? by remember { mutableStateOf(null) } + var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } + var isDraggingToRemove by remember { mutableStateOf(false) } + Box( modifier = modifier.fillMaxSize().background(Color.White), ) { CommunalHubLazyGrid( - modifier = Modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart), + modifier = Modifier.align(Alignment.CenterStart), communalContent = communalContent, - isEditMode = viewModel.isEditMode, viewModel = viewModel, - ) - if (viewModel.isEditMode && onOpenWidgetPicker != null) { - IconButton(onClick = onOpenWidgetPicker) { - Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text)) + contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize), + setGridCoordinates = { gridCoordinates = it }, + updateDragPositionForRemove = { + isDraggingToRemove = + checkForDraggingToRemove(it, removeButtonCoordinates, gridCoordinates) + isDraggingToRemove } + ) + + if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) { + Toolbar( + isDraggingToRemove = isDraggingToRemove, + setToolbarSize = { toolbarSize = it }, + setRemoveButtonCoordinates = { removeButtonCoordinates = it }, + onEditDone = onEditDone, + onOpenWidgetPicker = onOpenWidgetPicker, + ) } else { IconButton(onClick = viewModel::onOpenWidgetEditor) { Icon(Icons.Default.Edit, stringResource(R.string.button_to_open_widget_editor)) @@ -103,25 +140,38 @@ fun CommunalHub( @Composable private fun CommunalHubLazyGrid( communalContent: List<CommunalContentModel>, - isEditMode: Boolean, viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier, + contentPadding: PaddingValues, + setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit, + updateDragPositionForRemove: (offset: Offset) -> Boolean, ) { var gridModifier = modifier val gridState = rememberLazyGridState() var list = communalContent var dragDropState: GridDragDropState? = null - if (isEditMode && viewModel is CommunalEditModeViewModel) { + if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) { val contentListState = rememberContentListState(communalContent, viewModel) list = contentListState.list - dragDropState = rememberGridDragDropState(gridState, contentListState) - gridModifier = gridModifier.dragContainer(dragDropState) + dragDropState = + rememberGridDragDropState( + gridState = gridState, + contentListState = contentListState, + updateDragPositionForRemove = updateDragPositionForRemove + ) + gridModifier = + gridModifier + .fillMaxSize() + .dragContainer(dragDropState, beforeContentPadding(contentPadding)) + .onGloballyPositioned { setGridCoordinates(it) } + } else { + gridModifier = gridModifier.height(Dimensions.GridHeight) } LazyHorizontalGrid( modifier = gridModifier, state = gridState, rows = GridCells.Fixed(CommunalContentSize.FULL.span), - contentPadding = PaddingValues(horizontal = Dimensions.Spacing), + contentPadding = contentPadding, horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing), verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing), ) { @@ -130,19 +180,18 @@ private fun CommunalHubLazyGrid( key = { index -> list[index].key }, span = { index -> GridItemSpan(list[index].size.span) }, ) { index -> - val cardModifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth) + val cardModifier = Modifier.width(Dimensions.CardWidth) val size = SizeF( Dimensions.CardWidth.value, list[index].size.dp().value, ) - if (isEditMode && dragDropState != null) { + if (viewModel.isEditMode && dragDropState != null) { DraggableItem(dragDropState = dragDropState, enabled = true, index = index) { isDragging -> val elevation by animateDpAsState(if (isDragging) 4.dp else 1.dp) CommunalContent( modifier = cardModifier, - deleteOnClick = viewModel::onDeleteWidget, elevation = elevation, model = list[index], viewModel = viewModel, @@ -161,6 +210,95 @@ private fun CommunalHubLazyGrid( } } +/** + * Toolbar that contains action buttons to + * 1) open the widget picker + * 2) remove a widget from the grid and + * 3) exit the edit mode. + */ +@Composable +private fun Toolbar( + isDraggingToRemove: Boolean, + setToolbarSize: (toolbarSize: IntSize) -> Unit, + setRemoveButtonCoordinates: (coordinates: LayoutCoordinates) -> Unit, + onOpenWidgetPicker: () -> Unit, + onEditDone: () -> Unit, +) { + Row( + modifier = + Modifier.fillMaxWidth() + .padding( + top = Dimensions.ToolbarPaddingTop, + start = Dimensions.ToolbarPaddingHorizontal, + end = Dimensions.ToolbarPaddingHorizontal, + ) + .onSizeChanged { setToolbarSize(it) }, + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + val buttonContentPadding = + PaddingValues( + vertical = Dimensions.ToolbarButtonPaddingVertical, + horizontal = Dimensions.ToolbarButtonPaddingHorizontal, + ) + val spacerModifier = Modifier.width(Dimensions.ToolbarButtonSpaceBetween) + Button( + onClick = onOpenWidgetPicker, + colors = filledSecondaryButtonColors(), + contentPadding = buttonContentPadding + ) { + Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor)) + Spacer(spacerModifier) + Text( + text = stringResource(R.string.hub_mode_add_widget_button_text), + ) + } + + val buttonColors = + if (isDraggingToRemove) filledButtonColors() else ButtonDefaults.outlinedButtonColors() + OutlinedButton( + onClick = {}, + colors = buttonColors, + contentPadding = buttonContentPadding, + modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }, + ) { + Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_open_widget_editor)) + Spacer(spacerModifier) + Text( + text = stringResource(R.string.button_to_remove_widget), + ) + } + + Button( + onClick = onEditDone, + colors = filledButtonColors(), + contentPadding = buttonContentPadding + ) { + Text( + text = stringResource(R.string.hub_mode_editing_exit_button_text), + ) + } + } +} + +@Composable +private fun filledButtonColors(): ButtonColors { + val colors = LocalAndroidColorScheme.current + return ButtonDefaults.buttonColors( + containerColor = colors.primary, + contentColor = colors.onPrimary, + ) +} + +@Composable +private fun filledSecondaryButtonColors(): ButtonColors { + val colors = LocalAndroidColorScheme.current + return ButtonDefaults.buttonColors( + containerColor = colors.secondary, + contentColor = colors.onSecondary, + ) +} + @Composable private fun CommunalContent( model: CommunalContentModel, @@ -168,11 +306,9 @@ private fun CommunalContent( size: SizeF, modifier: Modifier = Modifier, elevation: Dp = 0.dp, - deleteOnClick: ((id: Int) -> Unit)? = null, ) { when (model) { - is CommunalContentModel.Widget -> - WidgetContent(model, size, elevation, deleteOnClick, modifier) + is CommunalContentModel.Widget -> WidgetContent(model, size, elevation, modifier) is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier) is CommunalContentModel.Tutorial -> TutorialContent(modifier) is CommunalContentModel.Umo -> Umo(viewModel, modifier) @@ -184,19 +320,12 @@ private fun WidgetContent( model: CommunalContentModel.Widget, size: SizeF, elevation: Dp, - deleteOnClick: ((id: Int) -> Unit)?, modifier: Modifier = Modifier, ) { - // TODO(b/309009246): update background color Card( - modifier = modifier.fillMaxSize().background(Color.White), + modifier = modifier.height(size.height.dp), elevation = CardDefaults.cardElevation(draggedElevation = elevation), ) { - if (deleteOnClick != null) { - IconButton(onClick = { deleteOnClick(model.appWidgetId) }) { - Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget)) - } - } AndroidView( modifier = modifier, factory = { context -> @@ -249,6 +378,60 @@ private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) ) } +/** + * Returns the `contentPadding` of the grid. Use the vertical padding to push the grid content area + * below the toolbar and let the grid take the max size. This ensures the item can be dragged + * outside the grid over the toolbar, without part of it getting clipped by the container. + */ +@Composable +private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues { + if (!isEditMode || toolbarSize == null) { + return PaddingValues(horizontal = Dimensions.Spacing) + } + val configuration = LocalConfiguration.current + val density = LocalDensity.current + val screenHeight = configuration.screenHeightDp.dp + val toolbarHeight = with(density) { Dimensions.ToolbarPaddingTop + toolbarSize.height.toDp() } + val verticalPadding = + ((screenHeight - toolbarHeight - Dimensions.GridHeight) / 2).coerceAtLeast( + Dimensions.Spacing + ) + return PaddingValues( + start = Dimensions.ToolbarPaddingHorizontal, + end = Dimensions.ToolbarPaddingHorizontal, + top = verticalPadding + toolbarHeight, + bottom = verticalPadding + ) +} + +@Composable +private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingInPx { + return with(LocalDensity.current) { + ContentPaddingInPx( + startPadding = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(), + topPadding = paddingValues.calculateTopPadding().toPx() + ) + } +} + +/** + * Check whether the pointer position that the item is being dragged at is within the coordinates of + * the remove button in the toolbar. Returns true if the item is removable. + */ +private fun checkForDraggingToRemove( + offset: Offset, + removeButtonCoordinates: LayoutCoordinates?, + gridCoordinates: LayoutCoordinates?, +): Boolean { + if (removeButtonCoordinates == null || gridCoordinates == null) { + return false + } + val pointer = gridCoordinates.positionInWindow() + offset + val removeButton = removeButtonCoordinates.positionInWindow() + return pointer.x in removeButton.x..removeButton.x + removeButtonCoordinates.size.width && + pointer.y in removeButton.y..removeButton.y + removeButtonCoordinates.size.height +} + private fun CommunalContentSize.dp(): Dp { return when (this) { CommunalContentSize.FULL -> Dimensions.CardHeightFull @@ -257,6 +440,8 @@ private fun CommunalContentSize.dp(): Dp { } } +data class ContentPaddingInPx(val startPadding: Float, val topPadding: Float) + object Dimensions { val CardWidth = 464.dp val CardHeightFull = 630.dp @@ -264,4 +449,11 @@ object Dimensions { val CardHeightThird = 199.dp val GridHeight = CardHeightFull val Spacing = 16.dp + + // The sizing/padding of the toolbar in glanceable hub edit mode + val ToolbarPaddingTop = 27.dp + val ToolbarPaddingHorizontal = 16.dp + val ToolbarButtonPaddingHorizontal = 24.dp + val ToolbarButtonPaddingVertical = 16.dp + val ToolbarButtonSpaceBetween = 8.dp } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt index 6cfa2f233f46..5451d0550095 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt @@ -48,12 +48,18 @@ import kotlinx.coroutines.launch @Composable fun rememberGridDragDropState( gridState: LazyGridState, - contentListState: ContentListState + contentListState: ContentListState, + updateDragPositionForRemove: (offset: Offset) -> Boolean, ): GridDragDropState { val scope = rememberCoroutineScope() val state = remember(gridState, contentListState) { - GridDragDropState(state = gridState, contentListState = contentListState, scope = scope) + GridDragDropState( + state = gridState, + contentListState = contentListState, + scope = scope, + updateDragPositionForRemove = updateDragPositionForRemove + ) } LaunchedEffect(state) { while (true) { @@ -67,23 +73,30 @@ fun rememberGridDragDropState( /** * Handles drag and drop cards in the glanceable hub. While dragging to move, other items that are * affected will dynamically get positioned and the state is tracked by [ContentListState]. When - * dragging to remove, affected cards will be moved and [ContentListState.onRemove] is called to - * remove the dragged item. On dragging ends, call [ContentListState.onSaveList] to persist the - * change. + * dragging to remove, affected cards will be moved and [updateDragPositionForRemove] is called to + * check whether the dragged item can be removed. On dragging ends, call [ContentListState.onRemove] + * to remove the dragged item if condition met and call [ContentListState.onSaveList] to persist any + * change in ordering. */ class GridDragDropState internal constructor( private val state: LazyGridState, private val contentListState: ContentListState, private val scope: CoroutineScope, + private val updateDragPositionForRemove: (offset: Offset) -> Boolean ) { var draggingItemIndex by mutableStateOf<Int?>(null) private set + var isDraggingToRemove by mutableStateOf(false) + private set + internal val scrollChannel = Channel<Float>() private var draggingItemDraggedDelta by mutableStateOf(Offset.Zero) private var draggingItemInitialOffset by mutableStateOf(Offset.Zero) + private var dragStartPointerOffset by mutableStateOf(Offset.Zero) + internal val draggingItemOffset: Offset get() = draggingItemLayoutInfo?.let { item -> @@ -94,27 +107,36 @@ internal constructor( private val draggingItemLayoutInfo: LazyGridItemInfo? get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex } - internal fun onDragStart(offset: Offset) { + internal fun onDragStart(offset: Offset, contentOffset: Offset) { state.layoutInfo.visibleItemsInfo .firstOrNull { item -> + // grid item offset is based off grid content container so we need to deduct + // before content padding from the initial pointer position item.isEditable && - offset.x.toInt() in item.offset.x..item.offsetEnd.x && - offset.y.toInt() in item.offset.y..item.offsetEnd.y + (offset.x - contentOffset.x).toInt() in item.offset.x..item.offsetEnd.x && + (offset.y - contentOffset.y).toInt() in item.offset.y..item.offsetEnd.y } ?.apply { + dragStartPointerOffset = offset - this.offset.toOffset() draggingItemIndex = index draggingItemInitialOffset = this.offset.toOffset() } } internal fun onDragInterrupted() { - if (draggingItemIndex != null) { + draggingItemIndex?.let { + if (isDraggingToRemove) { + contentListState.onRemove(it) + isDraggingToRemove = false + updateDragPositionForRemove(Offset.Zero) + } // persist list editing changes on dragging ends contentListState.onSaveList() draggingItemIndex = null } draggingItemDraggedDelta = Offset.Zero draggingItemInitialOffset = Offset.Zero + dragStartPointerOffset = Offset.Zero } internal fun onDrag(offset: Offset) { @@ -152,18 +174,13 @@ internal constructor( contentListState.onMove(draggingItem.index, targetItem.index) } draggingItemIndex = targetItem.index + isDraggingToRemove = false } else { val overscroll = checkForOverscroll(startOffset, endOffset) if (overscroll != 0f) { scrollChannel.trySend(overscroll) } - val removeOffset = checkForRemove(startOffset) - if (removeOffset != 0f) { - draggingItemIndex?.let { - contentListState.onRemove(it) - draggingItemIndex = null - } - } + isDraggingToRemove = checkForRemove(startOffset) } } @@ -185,14 +202,11 @@ internal constructor( } } - // TODO(b/309968801): a temporary solution to decide whether to remove card when it's dragged up - // and out of grid. Once we have a taskbar, calculate the intersection of the dragged item with - // the Remove button. - private fun checkForRemove(startOffset: Offset): Float { + /** Calls the callback with the updated drag position and returns whether to remove the item. */ + private fun checkForRemove(startOffset: Offset): Boolean { return if (draggingItemDraggedDelta.y < 0) - (startOffset.y + Dimensions.CardHeightHalf.value - state.layoutInfo.viewportStartOffset) - .coerceAtMost(0f) - else 0f + updateDragPositionForRemove(startOffset + dragStartPointerOffset) + else false } } @@ -204,14 +218,22 @@ private operator fun Offset.plus(size: Size): Offset { return Offset(x + size.width, y + size.height) } -fun Modifier.dragContainer(dragDropState: GridDragDropState): Modifier { - return pointerInput(dragDropState) { +fun Modifier.dragContainer( + dragDropState: GridDragDropState, + beforeContentPadding: ContentPaddingInPx +): Modifier { + return pointerInput(dragDropState, beforeContentPadding) { detectDragGesturesAfterLongPress( onDrag = { change, offset -> change.consume() dragDropState.onDrag(offset = offset) }, - onDragStart = { offset -> dragDropState.onDragStart(offset) }, + onDragStart = { offset -> + dragDropState.onDragStart( + offset, + Offset(beforeContentPadding.startPadding, beforeContentPadding.topPadding) + ) + }, onDragEnd = { dragDropState.onDragInterrupted() }, onDragCancel = { dragDropState.onDragInterrupted() } ) @@ -237,6 +259,7 @@ fun LazyGridItemScope.DraggableItem( Modifier.zIndex(1f).graphicsLayer { translationX = dragDropState.draggingItemOffset.x translationY = dragDropState.draggingItemOffset.y + alpha = if (dragDropState.isDraggingToRemove) 0.5f else 1f } } else { Modifier.animateItemPlacement() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt index ded6cc155b0b..32025b4f1258 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt @@ -73,37 +73,37 @@ enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) { internal fun Modifier.nestedScrollToScene( layoutImpl: SceneTransitionLayoutImpl, orientation: Orientation, - startBehavior: NestedScrollBehavior, - endBehavior: NestedScrollBehavior, + topOrLeftBehavior: NestedScrollBehavior, + bottomOrRightBehavior: NestedScrollBehavior, ) = this then NestedScrollToSceneElement( layoutImpl = layoutImpl, orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) private data class NestedScrollToSceneElement( private val layoutImpl: SceneTransitionLayoutImpl, private val orientation: Orientation, - private val startBehavior: NestedScrollBehavior, - private val endBehavior: NestedScrollBehavior, + private val topOrLeftBehavior: NestedScrollBehavior, + private val bottomOrRightBehavior: NestedScrollBehavior, ) : ModifierNodeElement<NestedScrollToSceneNode>() { override fun create() = NestedScrollToSceneNode( layoutImpl = layoutImpl, orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) override fun update(node: NestedScrollToSceneNode) { node.update( layoutImpl = layoutImpl, orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) } @@ -111,23 +111,23 @@ private data class NestedScrollToSceneElement( name = "nestedScrollToScene" properties["layoutImpl"] = layoutImpl properties["orientation"] = orientation - properties["startBehavior"] = startBehavior - properties["endBehavior"] = endBehavior + properties["topOrLeftBehavior"] = topOrLeftBehavior + properties["bottomOrRightBehavior"] = bottomOrRightBehavior } } private class NestedScrollToSceneNode( layoutImpl: SceneTransitionLayoutImpl, orientation: Orientation, - startBehavior: NestedScrollBehavior, - endBehavior: NestedScrollBehavior, + topOrLeftBehavior: NestedScrollBehavior, + bottomOrRightBehavior: NestedScrollBehavior, ) : DelegatingNode() { private var priorityNestedScrollConnection: PriorityNestedScrollConnection = scenePriorityNestedScrollConnection( layoutImpl = layoutImpl, orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) private var nestedScrollNode: DelegatableNode = @@ -148,8 +148,8 @@ private class NestedScrollToSceneNode( fun update( layoutImpl: SceneTransitionLayoutImpl, orientation: Orientation, - startBehavior: NestedScrollBehavior, - endBehavior: NestedScrollBehavior, + topOrLeftBehavior: NestedScrollBehavior, + bottomOrRightBehavior: NestedScrollBehavior, ) { // Clean up the old nested scroll connection priorityNestedScrollConnection.reset() @@ -160,8 +160,8 @@ private class NestedScrollToSceneNode( scenePriorityNestedScrollConnection( layoutImpl = layoutImpl, orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) nestedScrollNode = nestedScrollModifierNode( @@ -175,12 +175,12 @@ private class NestedScrollToSceneNode( private fun scenePriorityNestedScrollConnection( layoutImpl: SceneTransitionLayoutImpl, orientation: Orientation, - startBehavior: NestedScrollBehavior, - endBehavior: NestedScrollBehavior, + topOrLeftBehavior: NestedScrollBehavior, + bottomOrRightBehavior: NestedScrollBehavior, ) = SceneNestedScrollHandler( gestureHandler = layoutImpl.gestureHandler(orientation = orientation), - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) .connection diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index f5561cb404b6..6a7a3a00d4fe 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -86,16 +86,26 @@ private class SceneScopeImpl( return element(layoutImpl, scene, key) } - override fun Modifier.nestedScrollToScene( - orientation: Orientation, - startBehavior: NestedScrollBehavior, - endBehavior: NestedScrollBehavior, + override fun Modifier.horizontalNestedScrollToScene( + leftBehavior: NestedScrollBehavior, + rightBehavior: NestedScrollBehavior, ): Modifier = nestedScrollToScene( layoutImpl = layoutImpl, - orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + orientation = Orientation.Horizontal, + topOrLeftBehavior = leftBehavior, + bottomOrRightBehavior = rightBehavior, + ) + + override fun Modifier.verticalNestedScrollToScene( + topBehavior: NestedScrollBehavior, + bottomBehavior: NestedScrollBehavior + ): Modifier = + nestedScrollToScene( + layoutImpl = layoutImpl, + orientation = Orientation.Vertical, + topOrLeftBehavior = topBehavior, + bottomOrRightBehavior = bottomBehavior, ) @Composable diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt index 01179d3b8f73..03f37d0c9bda 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt @@ -494,9 +494,9 @@ private class SceneDraggableHandler( } internal class SceneNestedScrollHandler( - private val gestureHandler: SceneGestureHandler, - private val startBehavior: NestedScrollBehavior, - private val endBehavior: NestedScrollBehavior, + private val gestureHandler: SceneGestureHandler, + private val topOrLeftBehavior: NestedScrollBehavior, + private val bottomOrRightBehavior: NestedScrollBehavior, ) : NestedScrollHandler { override val connection: PriorityNestedScrollConnection = nestedScrollConnection() @@ -565,8 +565,8 @@ internal class SceneNestedScrollHandler( canStartPostScroll = { offsetAvailable, offsetBeforeStart -> val behavior: NestedScrollBehavior = when { - offsetAvailable > 0f -> startBehavior - offsetAvailable < 0f -> endBehavior + offsetAvailable > 0f -> topOrLeftBehavior + offsetAvailable < 0f -> bottomOrRightBehavior else -> return@PriorityNestedScrollConnection false } @@ -594,8 +594,8 @@ internal class SceneNestedScrollHandler( canStartPostFling = { velocityAvailable -> val behavior: NestedScrollBehavior = when { - velocityAvailable > 0f -> startBehavior - velocityAvailable < 0f -> endBehavior + velocityAvailable > 0f -> topOrLeftBehavior + velocityAvailable < 0f -> bottomOrRightBehavior else -> return@PriorityNestedScrollConnection false } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index afa184b15901..239971ff6be8 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -126,14 +126,24 @@ interface SceneScope { * Adds a [NestedScrollConnection] to intercept scroll events not handled by the scrollable * component. * - * @param orientation is used to determine if we handle top/bottom or left/right events. - * @param startBehavior when we should perform the overscroll animation at the top/left. - * @param endBehavior when we should perform the overscroll animation at the bottom/right. + * @param leftBehavior when we should perform the overscroll animation at the left. + * @param rightBehavior when we should perform the overscroll animation at the right. */ - fun Modifier.nestedScrollToScene( - orientation: Orientation, - startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, - endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + fun Modifier.horizontalNestedScrollToScene( + leftBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + rightBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + ): Modifier + + /** + * Adds a [NestedScrollConnection] to intercept scroll events not handled by the scrollable + * component. + * + * @param topBehavior when we should perform the overscroll animation at the top. + * @param bottomBehavior when we should perform the overscroll animation at the bottom. + */ + fun Modifier.verticalNestedScrollToScene( + topBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + bottomBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, ): Modifier /** diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt index aa942e039856..34afc4c91d4c 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt @@ -117,8 +117,8 @@ class SceneGestureHandlerTest { fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) = SceneNestedScrollHandler( gestureHandler = sceneGestureHandler, - startBehavior = nestedScrollBehavior, - endBehavior = nestedScrollBehavior, + topOrLeftBehavior = nestedScrollBehavior, + bottomOrRightBehavior = nestedScrollBehavior, ) .connection 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 16cfa2398fd5..1f8e29adc983 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 @@ -161,7 +161,7 @@ class CommunalInteractorTest : SysuiTestCase() { whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java)) val targets = listOf(target1, target2, target3) - smartspaceRepository.setLockscreenSmartspaceTargets(targets) + smartspaceRepository.setCommunalSmartspaceTargets(targets) val smartspaceContent by collectLastValue(underTest.smartspaceContent) assertThat(smartspaceContent?.size).isEqualTo(1) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 8896e6e64bd9..314dfdfd6f2a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -116,7 +116,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { whenever(target.smartspaceTargetId).thenReturn("target") whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) - smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target)) + smartspaceRepository.setCommunalSmartspaceTargets(listOf(target)) // Media playing. mediaRepository.mediaPlaying.value = true 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 7fbcae0d8986..8a71168324aa 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 @@ -135,7 +135,7 @@ class CommunalViewModelTest : SysuiTestCase() { whenever(target.smartspaceTargetId).thenReturn("target") whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) - smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target)) + smartspaceRepository.setCommunalSmartspaceTargets(listOf(target)) // Media playing. mediaRepository.mediaPlaying.value = true diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt new file mode 100644 index 000000000000..30d1822b28da --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.data.restoreprocessors + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.shared.TileSpec +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 + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class WorkTileRestoreProcessorTest : SysuiTestCase() { + + private val underTest = WorkTileRestoreProcessor() + @Test + fun restoreWithWorkTile_removeTracking() = runTest { + val removeTracking by collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER))) + runCurrent() + + val restoreData = + RestoreData( + restoredTiles = listOf(TILE_SPEC), + restoredAutoAddedTiles = setOf(TILE_SPEC), + USER, + ) + + underTest.postProcessRestore(restoreData) + + assertThat(removeTracking).isEqualTo(Unit) + } + + @Test + fun restoreWithWorkTile_otherUser_noRemoveTracking() = runTest { + val removeTracking by + collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER + 1))) + runCurrent() + + val restoreData = + RestoreData( + restoredTiles = listOf(TILE_SPEC), + restoredAutoAddedTiles = setOf(TILE_SPEC), + USER, + ) + + underTest.postProcessRestore(restoreData) + + assertThat(removeTracking).isNull() + } + + @Test + fun restoreWithoutWorkTile_noSignal() = runTest { + val removeTracking by collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER))) + runCurrent() + + val restoreData = + RestoreData( + restoredTiles = emptyList(), + restoredAutoAddedTiles = emptySet(), + USER, + ) + + underTest.postProcessRestore(restoreData) + + assertThat(removeTracking).isNull() + } + + companion object { + private const val USER = 10 + private val TILE_SPEC = TileSpec.Companion.create("work") + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt index adccc84e494b..c7e7845f206c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt @@ -25,6 +25,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor +import com.android.systemui.qs.pipeline.data.model.workTileRestoreProcessor import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking import com.android.systemui.qs.pipeline.shared.TileSpec @@ -32,25 +37,28 @@ import com.android.systemui.qs.tiles.WorkModeTile import com.android.systemui.settings.FakeUserTracker import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class WorkTileAutoAddableTest : SysuiTestCase() { + private val kosmos = Kosmos() + + private val restoreProcessor: RestoreProcessor + get() = kosmos.workTileRestoreProcessor + private lateinit var userTracker: FakeUserTracker private lateinit var underTest: WorkTileAutoAddable @Before fun setup() { - MockitoAnnotations.initMocks(this) - userTracker = FakeUserTracker( _userId = USER_INFO_0.id, @@ -58,7 +66,7 @@ class WorkTileAutoAddableTest : SysuiTestCase() { _userProfiles = listOf(USER_INFO_0) ) - underTest = WorkTileAutoAddable(userTracker) + underTest = WorkTileAutoAddable(userTracker, kosmos.workTileRestoreProcessor) } @Test @@ -114,10 +122,80 @@ class WorkTileAutoAddableTest : SysuiTestCase() { assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always) } + @Test + fun restoreDataWithWorkTile_noCurrentManagedProfile_triggersRemove() = runTest { + val userId = 0 + val signal by collectLastValue(underTest.autoAddSignal(userId)) + runCurrent() + + val restoreData = createRestoreWithWorkTile(userId) + + restoreProcessor.postProcessRestore(restoreData) + + assertThat(signal!!).isEqualTo(AutoAddSignal.RemoveTracking(SPEC)) + } + + @Test + fun restoreDataWithWorkTile_currentlyManagedProfile_doesntTriggerRemove() = runTest { + userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0) + val userId = 0 + val signals by collectValues(underTest.autoAddSignal(userId)) + runCurrent() + + val restoreData = createRestoreWithWorkTile(userId) + + restoreProcessor.postProcessRestore(restoreData) + + assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC)) + } + + @Test + fun restoreDataWithoutWorkTile_noManagedProfile_doesntTriggerRemove() = runTest { + val userId = 0 + val signals by collectValues(underTest.autoAddSignal(userId)) + runCurrent() + + val restoreData = createRestoreWithoutWorkTile(userId) + + restoreProcessor.postProcessRestore(restoreData) + + assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC)) + } + + @Test + fun restoreDataWithoutWorkTile_managedProfile_doesntTriggerRemove() = runTest { + userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0) + val userId = 0 + val signals by collectValues(underTest.autoAddSignal(userId)) + runCurrent() + + val restoreData = createRestoreWithoutWorkTile(userId) + + restoreProcessor.postProcessRestore(restoreData) + + assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC)) + } + companion object { private val SPEC = TileSpec.create(WorkModeTile.TILE_SPEC) private val USER_INFO_0 = UserInfo(0, "", FLAG_PRIMARY or FLAG_FULL) private val USER_INFO_1 = UserInfo(1, "", FLAG_FULL) private val USER_INFO_WORK = UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE) + + private fun createRestoreWithWorkTile(userId: Int): RestoreData { + return RestoreData( + listOf(TileSpec.create("a"), SPEC, TileSpec.create("b")), + setOf(SPEC), + userId, + ) + } + + private fun createRestoreWithoutWorkTile(userId: Int): RestoreData { + return RestoreData( + listOf(TileSpec.create("a"), TileSpec.create("b")), + emptySet(), + userId, + ) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt index 41a7ec03408d..54b03a90229b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt @@ -183,6 +183,22 @@ class AutoAddInteractorTest : SysuiTestCase() { assertThat(autoAddedTiles).contains(SPEC) } + @Test + fun autoAddable_removeTrackingSignal_notRemovedButUnmarked() = + testScope.runTest { + autoAddRepository.markTileAdded(USER, SPEC) + val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER)) + val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always) + + underTest = createInteractor(setOf(fakeAutoAddable)) + + fakeAutoAddable.sendRemoveTrackingSignal(USER) + runCurrent() + + verify(currentTilesInteractor, never()).removeTiles(any()) + assertThat(autoAddedTiles).doesNotContain(SPEC) + } + private fun createInteractor(autoAddables: Set<AutoAddable>): AutoAddInteractor { return AutoAddInteractor( autoAddables, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt index f73cab8a10a3..b2a9783d2e60 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt @@ -5,10 +5,15 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository import com.android.systemui.qs.pipeline.data.repository.FakeQSSettingsRestoredRepository import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter +import com.android.systemui.qs.pipeline.domain.interactor.RestoreReconciliationInteractorTest.TestableRestoreProcessor.Companion.POSTPROCESS +import com.android.systemui.qs.pipeline.domain.interactor.RestoreReconciliationInteractorTest.TestableRestoreProcessor.Companion.PREPROCESS +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -17,7 +22,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.MockitoAnnotations +import org.mockito.Mockito.inOrder @RunWith(AndroidJUnit4::class) @SmallTest @@ -28,6 +33,9 @@ class RestoreReconciliationInteractorTest : SysuiTestCase() { private val qsSettingsRestoredRepository = FakeQSSettingsRestoredRepository() + private val restoreProcessor: TestableRestoreProcessor = TestableRestoreProcessor() + private val qsLogger: QSPipelineLogger = mock() + private lateinit var underTest: RestoreReconciliationInteractor private val testDispatcher = StandardTestDispatcher() @@ -35,13 +43,13 @@ class RestoreReconciliationInteractorTest : SysuiTestCase() { @Before fun setUp() { - MockitoAnnotations.initMocks(this) - underTest = RestoreReconciliationInteractor( tileSpecRepository, autoAddRepository, qsSettingsRestoredRepository, + setOf(restoreProcessor), + qsLogger, testScope.backgroundScope, testDispatcher ) @@ -85,6 +93,44 @@ class RestoreReconciliationInteractorTest : SysuiTestCase() { assertThat(autoAdd).isEqualTo(expectedAutoAdd.toTilesSet()) } + @Test + fun restoreProcessorsCalled() = + testScope.runTest { + val user = 10 + + val restoredSpecs = "a,c,d,f" + val restoredAutoAdded = "d,e" + + val restoreData = + RestoreData( + restoredSpecs.toTilesList(), + restoredAutoAdded.toTilesSet(), + user, + ) + + qsSettingsRestoredRepository.onDataRestored(restoreData) + runCurrent() + + assertThat(restoreProcessor.calls).containsExactly(PREPROCESS, POSTPROCESS).inOrder() + } + + private class TestableRestoreProcessor : RestoreProcessor { + val calls = mutableListOf<Any>() + + override suspend fun preProcessRestore(restoreData: RestoreData) { + calls.add(PREPROCESS) + } + + override suspend fun postProcessRestore(restoreData: RestoreData) { + calls.add(POSTPROCESS) + } + + companion object { + val PREPROCESS = Any() + val POSTPROCESS = Any() + } + } + companion object { private fun String.toTilesList() = TilesSettingConverter.toTilesList(this) private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt new file mode 100644 index 000000000000..96d57743e2ee --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt @@ -0,0 +1,176 @@ +/* + * 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.qs.pipeline.domain.interactor + +import android.content.pm.UserInfo +import android.os.UserManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.FakeQSFactory +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository +import com.android.systemui.qs.pipeline.data.repository.fakeTileSpecRepository +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.qsTileFactory +import com.android.systemui.settings.fakeUserTracker +import com.android.systemui.settings.userTracker +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** + * This integration test is for testing the solution to b/314781280. In particular, there are two + * issues we want to verify after a restore of a device with a work profile and a work mode tile: + * * When the work profile is re-enabled in the target device, it is auto-added. + * * The tile is auto-added in the same position that it was in the restored device. + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() { + + private val kosmos = Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) } + // Getter here so it can change when there is a managed profile. + private val workTileAvailable: Boolean + get() = hasManagedProfile() + private val currentUser: Int + get() = kosmos.userTracker.userId + + private val testScope: TestScope + get() = kosmos.testScope + + @Before + fun setUp() { + mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE) + + kosmos.qsTileFactory = FakeQSFactory(::tileCreator) + kosmos.restoreReconciliationInteractor.start() + kosmos.autoAddInteractor.init(kosmos.currentTilesInteractor) + } + + @Test + fun workTileRestoredAndPreviouslyAutoAdded_notAvailable_willBeAutoaddedInCorrectPosition() = + testScope.runTest { + val tiles by collectLastValue(kosmos.currentTilesInteractor.currentTiles) + + // Set up + val currentTiles = listOf("a".toTileSpec()) + kosmos.fakeTileSpecRepository.setTiles(currentUser, currentTiles) + + val restoredTiles = + listOf(WORK_TILE_SPEC) + listOf("b", "c", "d").map { it.toTileSpec() } + val restoredAutoAdded = setOf(WORK_TILE_SPEC) + + val restoreData = RestoreData(restoredTiles, restoredAutoAdded, currentUser) + + // WHEN we restore tiles that auto-added the WORK tile and it's not available (there + // are no managed profiles) + kosmos.fakeRestoreRepository.onDataRestored(restoreData) + + // THEN the work tile is not part of the current tiles + assertThat(tiles!!).hasSize(3) + assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC) + + // WHEN we add a work profile + createManagedProfileAndAdd() + + // THEN the work profile is added in the correct place + assertThat(tiles!!.first().spec).isEqualTo(WORK_TILE_SPEC) + } + + @Test + fun workTileNotRestoredAndPreviouslyAutoAdded_wontBeAutoAddedWhenWorkProfileIsAdded() = + testScope.runTest { + val tiles by collectLastValue(kosmos.currentTilesInteractor.currentTiles) + + // Set up + val currentTiles = listOf("a".toTileSpec()) + kosmos.fakeTileSpecRepository.setTiles(currentUser, currentTiles) + runCurrent() + + val restoredTiles = listOf("b", "c", "d").map { it.toTileSpec() } + val restoredAutoAdded = setOf(WORK_TILE_SPEC) + + val restoreData = RestoreData(restoredTiles, restoredAutoAdded, currentUser) + + // WHEN we restore tiles that auto-added the WORK tile + kosmos.fakeRestoreRepository.onDataRestored(restoreData) + + // THEN the work tile is not part of the current tiles + assertThat(tiles!!).hasSize(3) + assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC) + + // WHEN we add a work profile + createManagedProfileAndAdd() + + // THEN the work profile is not added because the user had manually removed it in the + // past + assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC) + } + + private fun tileCreator(spec: String): QSTile { + return if (spec == WORK_TILE_SPEC.spec) { + FakeQSTile(currentUser, workTileAvailable) + } else { + FakeQSTile(currentUser) + } + } + + private fun hasManagedProfile(): Boolean { + return kosmos.userTracker.userProfiles.any { it.isManagedProfile } + } + + private fun TestScope.createManagedProfileAndAdd() { + kosmos.fakeUserTracker.set( + listOf(USER_0_INFO, MANAGED_USER_INFO), + 0, + ) + runCurrent() + } + + private companion object { + val WORK_TILE_SPEC = "work".toTileSpec() + val USER_0_INFO = + UserInfo( + 0, + "zero", + "", + UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, + ) + val MANAGED_USER_INFO = + UserInfo( + 10, + "ten-managed", + "", + 0, + UserManager.USER_TYPE_PROFILE_MANAGED, + ) + + fun String.toTileSpec() = TileSpec.create(this) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt index 2b744ac8398a..00405d0a07e7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.alarm.domain import android.app.AlarmManager +import android.graphics.drawable.TestStubDrawable import android.widget.Switch import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -40,7 +41,14 @@ class AlarmTileMapperTest : SysuiTestCase() { private val kosmos = Kosmos() private val alarmTileConfig = kosmos.qsAlarmTileConfig // Using lazy (versus =) to make sure we override the right context -- see b/311612168 - private val mapper by lazy { AlarmTileMapper(context.orCreateTestableResources.resources) } + private val mapper by lazy { + AlarmTileMapper( + context.orCreateTestableResources + .apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) } + .resources, + context.theme + ) + } @Test fun notAlarmSet() { @@ -100,7 +108,7 @@ class AlarmTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.status_bar_alarm) return QSTileState( - { Icon.Resource(R.drawable.ic_alarm, null) }, + { Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null) }, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt index 7b2ac90b9766..b60f483cccbb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.impl.flashlight.domain +import android.graphics.drawable.TestStubDrawable import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -35,7 +36,17 @@ import org.junit.runner.RunWith class FlashlightMapperTest : SysuiTestCase() { private val kosmos = Kosmos() private val qsTileConfig = kosmos.qsFlashlightTileConfig - private val mapper by lazy { FlashlightMapper(context.orCreateTestableResources.resources) } + private val mapper by lazy { + FlashlightMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_flashlight_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_flashlight_icon_on, TestStubDrawable()) + } + .resources, + context.theme + ) + } @Test fun mapsDisabledDataToInactiveState() { @@ -56,20 +67,20 @@ class FlashlightMapperTest : SysuiTestCase() { @Test fun mapsEnabledDataToOnIconState() { - val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_on, null) - val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true)) + val expectedIcon = + Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null) val actualIcon = tileState.icon() assertThat(actualIcon).isEqualTo(expectedIcon) } @Test fun mapsDisabledDataToOffIconState() { - val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_off, null) - val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false)) + val expectedIcon = + Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null) val actualIcon = tileState.icon() assertThat(actualIcon).isEqualTo(expectedIcon) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt index 8791877f8863..ea74a4c0d398 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.impl.location.domain +import android.graphics.drawable.TestStubDrawable import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -36,7 +37,17 @@ class LocationTileMapperTest : SysuiTestCase() { private val kosmos = Kosmos() private val qsTileConfig = kosmos.qsLocationTileConfig - private val mapper by lazy { LocationTileMapper(context.orCreateTestableResources.resources) } + private val mapper by lazy { + LocationTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_location_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_location_icon_on, TestStubDrawable()) + } + .resources, + context.theme + ) + } @Test fun mapsDisabledDataToInactiveState() { @@ -56,20 +67,18 @@ class LocationTileMapperTest : SysuiTestCase() { @Test fun mapsEnabledDataToOnIconState() { - val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_on, null) - val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true)) + val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null) val actualIcon = tileState.icon() Truth.assertThat(actualIcon).isEqualTo(expectedIcon) } @Test fun mapsDisabledDataToOffIconState() { - val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_off, null) - val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false)) + val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null) val actualIcon = tileState.icon() Truth.assertThat(actualIcon).isEqualTo(expectedIcon) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt index d1824129590b..d162c778f607 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.impl.saver.domain +import android.graphics.drawable.TestStubDrawable import android.widget.Switch import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -37,7 +38,17 @@ class DataSaverTileMapperTest : SysuiTestCase() { private val dataSaverTileConfig = kosmos.qsDataSaverTileConfig // Using lazy (versus =) to make sure we override the right context -- see b/311612168 - private val mapper by lazy { DataSaverTileMapper(context.orCreateTestableResources.resources) } + private val mapper by lazy { + DataSaverTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_data_saver_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_data_saver_icon_on, TestStubDrawable()) + } + .resources, + context.theme + ) + } @Test fun activeStateMatchesEnabledModel() { @@ -80,7 +91,7 @@ class DataSaverTileMapperTest : SysuiTestCase() { else context.resources.getStringArray(R.array.tile_states_saver)[0] return QSTileState( - { Icon.Resource(iconRes, null) }, + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt index 87f50090e58b..a9776068b20c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.uimodenight.domain import android.app.UiModeManager +import android.graphics.drawable.TestStubDrawable import android.text.TextUtils import android.view.View import android.widget.Switch @@ -41,7 +42,15 @@ class UiModeNightTileMapperTest : SysuiTestCase() { private val qsTileConfig = kosmos.qsUiModeNightTileConfig private val mapper by lazy { - UiModeNightTileMapper(context.orCreateTestableResources.resources) + UiModeNightTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_light_dark_theme_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_light_dark_theme_icon_on, TestStubDrawable()) + } + .resources, + context.theme + ) } private fun createUiNightModeTileState( @@ -60,7 +69,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { expandedAccessibilityClass: KClass<out View>? = Switch::class, ): QSTileState { return QSTileState( - { Icon.Resource(iconRes, null) }, + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt new file mode 100644 index 000000000000..ef2046d85a14 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt @@ -0,0 +1,172 @@ +/* + * 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.smartspace + +import android.app.smartspace.SmartspaceManager +import android.app.smartspace.SmartspaceSession +import android.app.smartspace.SmartspaceTarget +import android.content.Context +import android.graphics.drawable.Drawable +import android.testing.TestableLooper +import android.view.View +import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.smartspace.CommunalSmartspaceController +import com.android.systemui.plugins.BcSmartspaceConfigPlugin +import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.util.concurrency.Execution +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat +import java.util.Optional +import java.util.concurrent.Executor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +class CommunalSmartspaceControllerTest : SysuiTestCase() { + @Mock private lateinit var smartspaceManager: SmartspaceManager + + @Mock private lateinit var execution: Execution + + @Mock private lateinit var uiExecutor: Executor + + @Mock private lateinit var targetFilter: SmartspaceTargetFilter + + @Mock private lateinit var plugin: BcSmartspaceDataPlugin + + @Mock private lateinit var precondition: SmartspacePrecondition + + @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener + + @Mock private lateinit var session: SmartspaceSession + + private lateinit var controller: CommunalSmartspaceController + + // TODO(b/272811280): Remove usage of real view + private val fakeParent = FrameLayout(context) + + /** + * A class which implements SmartspaceView and extends View. This is mocked to provide the right + * object inheritance and interface implementation used in CommunalSmartspaceController + */ + private class TestView(context: Context?) : View(context), SmartspaceView { + override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {} + + override fun registerConfigProvider(plugin: BcSmartspaceConfigPlugin?) {} + + override fun setPrimaryTextColor(color: Int) {} + + override fun setUiSurface(uiSurface: String) {} + + override fun setDozeAmount(amount: Float) {} + + override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {} + + override fun setFalsingManager(falsingManager: FalsingManager?) {} + + override fun setDnd(image: Drawable?, description: String?) {} + + override fun setNextAlarm(image: Drawable?, description: String?) {} + + override fun setMediaTarget(target: SmartspaceTarget?) {} + + override fun getSelectedPage(): Int { + return 0 + } + + override fun getCurrentCardTopPadding(): Int { + return 0 + } + } + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session) + + controller = + CommunalSmartspaceController( + context, + smartspaceManager, + execution, + uiExecutor, + precondition, + Optional.of(targetFilter), + Optional.of(plugin) + ) + } + + /** Ensures smartspace session begins on a listener only flow. */ + @Test + fun testConnectOnListen() { + `when`(precondition.conditionsMet()).thenReturn(true) + controller.addListener(listener) + + verify(smartspaceManager).createSmartspaceSession(any()) + + var targetListener = + withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> { + verify(session).addOnTargetsAvailableListener(any(), capture()) + } + + `when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true) + + var target = Mockito.mock(SmartspaceTarget::class.java) + targetListener.onTargetsAvailable(listOf(target)) + + var targets = + withArgCaptor<List<SmartspaceTarget>> { verify(plugin).onTargetsAvailable(capture()) } + + assertThat(targets.contains(target)).isTrue() + + controller.removeListener(listener) + + verify(session).close() + } + + /** + * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace + * view is detached. + */ + @Test + fun testDisconnect_emitsEmptyListAndRemovesNotifier() { + `when`(precondition.conditionsMet()).thenReturn(true) + controller.addListener(listener) + + verify(smartspaceManager).createSmartspaceSession(any()) + + controller.removeListener(listener) + + verify(session).close() + + // And the listener receives an empty list of targets and unregisters the notifier + verify(plugin).onTargetsAvailable(emptyList()) + verify(plugin).registerSmartspaceEventNotifier(null) + } +} diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index 64c0f99f4ba7..c99cb39f91bf 100644 --- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -44,6 +44,7 @@ public interface BcSmartspaceDataPlugin extends Plugin { String UI_SURFACE_HOME_SCREEN = "home"; String UI_SURFACE_MEDIA = "media_data_manager"; String UI_SURFACE_DREAM = "dream"; + String UI_SURFACE_GLANCEABLE_HUB = "glanceable_hub"; String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA"; int VERSION = 1; diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml deleted file mode 100644 index 02e10cd4ad7c..000000000000 --- a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?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. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_focused="true"> - <shape android:shape="rectangle"> - <corners android:radius="16dp" /> - <stroke android:width="3dp" - android:color="@color/bouncer_password_focus_color" /> - </shape> - </item> -</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml index 0b35559148af..66c54f2a668e 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml @@ -23,7 +23,7 @@ android:layout_marginTop="@dimen/keyguard_lock_padding" android:importantForAccessibility="no" android:ellipsize="marquee" - android:focusable="false" + android:focusable="true" android:gravity="center" android:singleLine="true" /> </merge> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 6e6709f94abb..88f7bcd5d907 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -76,7 +76,6 @@ </style> <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item> <item name="android:gravity">center</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index a22fd18fc2c0..bcc3c83b4560 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -93,9 +93,6 @@ <color name="qs_user_switcher_selected_avatar_icon_color">#202124</color> <!-- Color of background circle of user avatars in quick settings user switcher --> <color name="qs_user_switcher_avatar_background">#3C4043</color> - <!-- Color of border for keyguard password input when focused --> - <color name="bouncer_password_focus_color">@*android:color/system_secondary_dark</color> - <!-- Accessibility floating menu --> <color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% --> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 462fc95b8cd1..5f6a39a91b8b 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -56,8 +56,6 @@ <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color> <!-- Color of background circle of user avatars in keyguard user switcher --> <color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color> - <!-- Color of border for keyguard password input when focused --> - <color name="bouncer_password_focus_color">@*android:color/system_secondary_light</color> <!-- Icon color for user avatars in user switcher quick settings --> <color name="qs_user_switcher_avatar_icon_color">#3C4043</color> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 78b701caa3b6..13d2feae1d9b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1066,9 +1066,11 @@ <!-- Description for the button that opens the widget editor on click. [CHAR LIMIT=50] --> <string name="button_to_open_widget_editor">Open the widget editor</string> <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] --> - <string name="button_to_remove_widget">Remove a widget</string> + <string name="button_to_remove_widget">Remove</string> <!-- Text for the button that launches the hub mode widget picker. [CHAR LIMIT=50] --> - <string name="hub_mode_add_widget_button_text">Add Widget</string> + <string name="hub_mode_add_widget_button_text">Add widget</string> + <!-- Text for the button that exits the hub mode editing mode. [CHAR LIMIT=50] --> + <string name="hub_mode_editing_exit_button_text">Done</string> <!-- Related to user switcher --><skip/> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 9764de1993e5..36fe75f69a45 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -168,6 +168,7 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView // Set selected property on so the view can send accessibility events. mPasswordEntry.setSelected(true); + mPasswordEntry.setDefaultFocusHighlightEnabled(false); mOkButton = findViewById(R.id.key_enter); 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 1a2a4253e761..e342c6bca6fa 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 @@ -122,7 +122,7 @@ constructor( if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) { flowOf(emptyList()) } else { - smartspaceRepository.lockscreenSmartspaceTargets.map { targets -> + smartspaceRepository.communalSmartspaceTargets.map { targets -> targets .filter { target -> target.featureType == SmartspaceTarget.FEATURE_TIMER && diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt new file mode 100644 index 000000000000..c5610c877f57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt @@ -0,0 +1,195 @@ +/* + * 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.communal.smartspace + +import android.app.smartspace.SmartspaceConfig +import android.app.smartspace.SmartspaceManager +import android.app.smartspace.SmartspaceSession +import android.app.smartspace.SmartspaceTarget +import android.content.Context +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView +import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB +import com.android.systemui.smartspace.SmartspacePrecondition +import com.android.systemui.smartspace.SmartspaceTargetFilter +import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION +import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER +import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN +import com.android.systemui.util.concurrency.Execution +import java.util.Optional +import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Named + +/** Controller for managing the smartspace view on the dream */ +@SysUISingleton +class CommunalSmartspaceController +@Inject +constructor( + private val context: Context, + private val smartspaceManager: SmartspaceManager?, + private val execution: Execution, + @Main private val uiExecutor: Executor, + @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition, + @Named(DREAM_SMARTSPACE_TARGET_FILTER) + private val optionalTargetFilter: Optional<SmartspaceTargetFilter>, + @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>, +) { + companion object { + private const val TAG = "CommunalSmartspaceCtrlr" + } + + private var session: SmartspaceSession? = null + private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null) + private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null) + + // A shadow copy of listeners is maintained to track whether the session should remain open. + private var listeners = mutableSetOf<SmartspaceTargetListener>() + + private var unfilteredListeners = mutableSetOf<SmartspaceTargetListener>() + + // Smartspace can be used on multiple displays, such as when the user casts their screen + private var smartspaceViews = mutableSetOf<SmartspaceView>() + + var preconditionListener = + object : SmartspacePrecondition.Listener { + override fun onCriteriaChanged() { + reloadSmartspace() + } + } + + init { + precondition.addListener(preconditionListener) + } + + var filterListener = + object : SmartspaceTargetFilter.Listener { + override fun onCriteriaChanged() { + reloadSmartspace() + } + } + + init { + targetFilter?.addListener(filterListener) + } + + private val sessionListener = + SmartspaceSession.OnTargetsAvailableListener { targets -> + execution.assertIsMainThread() + + val filteredTargets = + targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true } + plugin?.onTargetsAvailable(filteredTargets) + } + + private fun hasActiveSessionListeners(): Boolean { + return smartspaceViews.isNotEmpty() || + listeners.isNotEmpty() || + unfilteredListeners.isNotEmpty() + } + + private fun connectSession() { + if (smartspaceManager == null) { + return + } + if (plugin == null) { + return + } + if (session != null || !hasActiveSessionListeners()) { + return + } + + if (!precondition.conditionsMet()) { + return + } + + val newSession = + smartspaceManager.createSmartspaceSession( + SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build() + ) + Log.d(TAG, "Starting smartspace session for dream") + newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener) + this.session = newSession + + plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } + + reloadSmartspace() + } + + /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */ + private fun disconnect() { + if (hasActiveSessionListeners()) return + + execution.assertIsMainThread() + + if (session == null) { + return + } + + session?.let { + it.removeOnTargetsAvailableListener(sessionListener) + it.close() + } + + session = null + + plugin?.registerSmartspaceEventNotifier(null) + plugin?.onTargetsAvailable(emptyList()) + Log.d(TAG, "Ending smartspace session for dream") + } + + fun addListener(listener: SmartspaceTargetListener) { + addAndRegisterListener(listener, plugin) + } + + fun removeListener(listener: SmartspaceTargetListener) { + removeAndUnregisterListener(listener, plugin) + } + + private fun addAndRegisterListener( + listener: SmartspaceTargetListener, + smartspaceDataPlugin: BcSmartspaceDataPlugin? + ) { + execution.assertIsMainThread() + smartspaceDataPlugin?.registerListener(listener) + listeners.add(listener) + + connectSession() + } + + private fun removeAndUnregisterListener( + listener: SmartspaceTargetListener, + smartspaceDataPlugin: BcSmartspaceDataPlugin? + ) { + execution.assertIsMainThread() + smartspaceDataPlugin?.unregisterListener(listener) + listeners.remove(listener) + disconnect() + } + + private fun reloadSmartspace() { + session?.requestSmartspaceUpdate() + } + + private fun onTargetsAvailableUnfiltered(targets: List<SmartspaceTarget>) { + unfilteredListeners.forEach { it.onSmartspaceTargetsUpdated(targets) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 7b94fc182fe2..573a748b4290 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -76,6 +76,10 @@ constructor( Intent(applicationContext, WidgetPickerActivity::class.java) ) }, + onEditDone = { + // TODO(b/315154364): in a separate change, lock the device and transition to GH + finish() + } ) } } diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 65d44957222a..3a927396527c 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -63,6 +63,7 @@ interface BaseComposeFacade { activity: ComponentActivity, viewModel: BaseCommunalViewModel, onOpenWidgetPicker: () -> Unit, + onEditDone: () -> Unit, ) /** Create a [View] to represent [viewModel] on screen. */ diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt index 63b01edb01fa..0daa058720ba 100644 --- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt @@ -35,23 +35,22 @@ import java.util.concurrent.Executor import javax.inject.Inject /** Dialog to select contrast options */ -class ContrastDialogDelegate @Inject constructor( - private val sysuiDialogFactory : SystemUIDialog.Factory, +class ContrastDialogDelegate +@Inject +constructor( + private val sysuiDialogFactory: SystemUIDialog.Factory, @Main private val mainExecutor: Executor, private val uiModeManager: UiModeManager, private val userTracker: UserTracker, private val secureSettings: SecureSettings, ) : SystemUIDialog.Delegate, UiModeManager.ContrastChangeListener { - override fun createDialog(): SystemUIDialog { - return sysuiDialogFactory.create(this) - } - @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout> lateinit var dialogView: View @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD) - override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + override fun createDialog(): SystemUIDialog { + val dialog = sysuiDialogFactory.create(this) dialogView = dialog.layoutInflater.inflate(R.layout.contrast_dialog, null) with(dialog) { setView(dialogView) @@ -67,12 +66,16 @@ class ContrastDialogDelegate @Inject constructor( } setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dialog.dismiss() } } + + return dialog + } + + override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { contrastButtons = mapOf( - CONTRAST_LEVEL_STANDARD to dialogView.requireViewById( - R.id.contrast_button_standard), - CONTRAST_LEVEL_MEDIUM to dialogView.requireViewById(R.id.contrast_button_medium), - CONTRAST_LEVEL_HIGH to dialogView.requireViewById(R.id.contrast_button_high) + CONTRAST_LEVEL_STANDARD to dialog.requireViewById(R.id.contrast_button_standard), + CONTRAST_LEVEL_MEDIUM to dialog.requireViewById(R.id.contrast_button_medium), + CONTRAST_LEVEL_HIGH to dialog.requireViewById(R.id.contrast_button_high) ) contrastButtons.forEach { (contrastLevel, contrastButton) -> diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index d5b95d6721f9..5ec51f4c3dad 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -16,6 +16,12 @@ package com.android.systemui.flags +import com.android.server.notification.Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS +import com.android.server.notification.Flags.FLAG_POLITE_NOTIFICATIONS +import com.android.server.notification.Flags.FLAG_VIBRATE_WHILE_UNLOCKED +import com.android.server.notification.Flags.crossAppPoliteNotifications +import com.android.server.notification.Flags.politeNotifications +import com.android.server.notification.Flags.vibrateWhileUnlocked import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.dagger.SysUISingleton @@ -36,5 +42,14 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha val keyguardBottomAreaRefactor = FlagToken( FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()) KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor + + val crossAppPoliteNotifToken = + FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications()) + val politeNotifToken = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications()) + crossAppPoliteNotifToken dependsOn politeNotifToken + + val vibrateWhileUnlockedToken = + FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked()) + vibrateWhileUnlockedToken dependsOn politeNotifToken } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index b753ff742100..b1d4587c20d8 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -111,7 +111,7 @@ object Flags { // TODO(b/301955929) @JvmField val NOTIF_LS_BACKGROUND_THREAD = - unreleasedFlag("notification_lockscreen_mgr_bg_thread", teamfood = true) + releasedFlag("notification_lockscreen_mgr_bg_thread") // 200 - keyguard/lockscreen // ** Flag retired ** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index b51edab6dfe8..0df7f9b809fd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -160,6 +160,9 @@ interface KeyguardRepository { /** Last point that [KeyguardRootView] was tapped */ val lastRootViewTapPosition: MutableStateFlow<Point?> + /** Is the ambient indication area visible? */ + val ambientIndicationVisible: MutableStateFlow<Boolean> + /** Observable for the [StatusBarState] */ val statusBarState: StateFlow<StatusBarState> @@ -423,6 +426,8 @@ constructor( override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null) + override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val isDreamingWithOverlay: Flow<Boolean> = conflatedCallbackFlow { val callback = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index c12efe875b0b..defca18b64b8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -174,6 +174,9 @@ constructor( /** Last point that [KeyguardRootView] view was tapped */ val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow() + /** Is the ambient indication area visible? */ + val ambientIndicationVisible: Flow<Boolean> = repository.ambientIndicationVisible.asStateFlow() + /** Whether the primary bouncer is showing or not. */ val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow @@ -311,6 +314,10 @@ constructor( repository.lastRootViewTapPosition.value = point } + fun setAmbientIndicationVisible(isVisible: Boolean) { + repository.ambientIndicationVisible.value = isVisible + } + companion object { private const val TAG = "KeyguardInteractor" } 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 1d4520ff8f03..26dace00ad76 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 @@ -180,7 +180,9 @@ constructor( goneToAodTransitionViewModel .enterFromTopTranslationY(enterFromTopAmount) .onStart { emit(0f) }, - occludedToLockscreenTransitionViewModel.lockscreenTranslationY, + occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart { + emit(0f) + }, ) { keyguardTransitionY, burnInTranslationY, @@ -193,6 +195,7 @@ constructor( occludedToLockscreenTransitionTranslationY } } + .distinctUntilChanged() val translationX: Flow<Float> = burnIn().map { it.translationX.toFloat() } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt index fdc70a83e8b1..76ef8a2b813c 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt @@ -278,7 +278,12 @@ class PrivacyDialogControllerV2( d.setShowForAllUsers(true) d.addOnDismissListener(onDialogDismissed) if (view != null) { - dialogLaunchAnimator.showFromView(d, view) + val controller = getPrivacyDialogController(view) + if (controller == null) { + d.show() + } else { + dialogLaunchAnimator.show(d, controller) + } } else { d.show() } @@ -291,6 +296,13 @@ class PrivacyDialogControllerV2( } } + private fun getPrivacyDialogController(source: View): DialogLaunchAnimator.Controller? { + val delegate = DialogLaunchAnimator.Controller.fromView(source) ?: return null + return object : DialogLaunchAnimator.Controller by delegate { + override fun shouldAnimateExit() = false + } + } + /** Dismisses the dialog */ fun dismissDialog() { dialog?.dismiss() diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt index b50798e59953..4bad45f19673 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt @@ -20,6 +20,7 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostRepository import com.android.systemui.qs.pipeline.data.repository.DefaultTilesRepository import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository @@ -39,14 +40,17 @@ import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap +import dagger.multibindings.Multibinds -@Module(includes = [QSAutoAddModule::class]) +@Module(includes = [QSAutoAddModule::class, RestoreProcessorsModule::class]) abstract class QSPipelineModule { /** Implementation for [TileSpecRepository] */ @Binds abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository + @Multibinds abstract fun provideRestoreProcessors(): Set<RestoreProcessor> + @Binds abstract fun provideDefaultTilesRepository( impl: DefaultTilesQSHostRepository diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt new file mode 100644 index 000000000000..e970c84d166f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.dagger + +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor +import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet + +@Module +interface RestoreProcessorsModule { + + @Binds + @IntoSet + fun bindWorkTileRestoreProcessor(impl: WorkTileRestoreProcessor): RestoreProcessor +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt new file mode 100644 index 000000000000..8f7de1976b36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt @@ -0,0 +1,38 @@ +/* + * 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.qs.pipeline.data.model + +/** + * Perform processing of the [RestoreData] before or after it's applied to repositories. + * + * The order in which the restore processors are applied in not deterministic. + * + * In order to declare a restore processor, add it in [RestoreProcessingModule] using + * + * ``` + * @Binds + * @IntoSet + * `` + */ +interface RestoreProcessor { + + /** Should be called before applying the restore to the necessary repositories */ + suspend fun preProcessRestore(restoreData: RestoreData) {} + + /** Should be called after requesting the repositories to update. */ + suspend fun postProcessRestore(restoreData: RestoreData) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt index 7998dfbe3f92..d40f3f4db5f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt @@ -20,9 +20,8 @@ import android.util.SparseArray import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.pipeline.data.model.RestoreData import com.android.systemui.qs.pipeline.shared.TileSpec -import kotlinx.coroutines.flow.Flow import javax.inject.Inject -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.Flow /** Repository to track what QS tiles have been auto-added */ interface AutoAddRepository { @@ -49,8 +48,9 @@ interface AutoAddRepository { @SysUISingleton class AutoAddSettingRepository @Inject -constructor(private val userAutoAddRepositoryFactory: UserAutoAddRepository.Factory) : - AutoAddRepository { +constructor( + private val userAutoAddRepositoryFactory: UserAutoAddRepository.Factory, +) : AutoAddRepository { private val userAutoAddRepositories = SparseArray<UserAutoAddRepository>() diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt index 6cee1161a104..e718eea09665 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt @@ -10,6 +10,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository.Companion.BUFFER_CAPACITY import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter.toTilesList import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import javax.inject.Inject @@ -28,6 +29,14 @@ import kotlinx.coroutines.flow.shareIn /** Provides restored data (from Backup and Restore) for Quick Settings pipeline */ interface QSSettingsRestoredRepository { val restoreData: Flow<RestoreData> + + companion object { + // This capacity is the number of restore data that we will keep buffered in the shared + // flow. It is unlikely that at any given time there would be this many restores being + // processed by consumers, but just in case that a couple of users are restored at the + // same time and they need to be replayed for the consumers of the flow. + const val BUFFER_CAPACITY = 10 + } } @SysUISingleton @@ -86,11 +95,6 @@ constructor( private companion object { private const val TAG = "QSSettingsRestoredBroadcastRepository" - // This capacity is the number of restore data that we will keep buffered in the shared - // flow. It is unlikely that at any given time there would be this many restores being - // processed by consumers, but just in case that a couple of users are restored at the - // same time and they need to be replayed for the consumers of the flow. - private const val BUFFER_CAPACITY = 10 private val INTENT_FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED) private const val TILES_SETTING = Settings.Secure.QS_TILES diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt new file mode 100644 index 000000000000..7376aa90883f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt @@ -0,0 +1,82 @@ +/* + * 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.qs.pipeline.data.restoreprocessors + +import android.os.UserHandle +import android.util.SparseIntArray +import androidx.annotation.GuardedBy +import androidx.core.util.getOrDefault +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor +import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.WorkModeTile +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map + +/** + * Processor for restore data for work tile. + * + * It will indicate when auto-add tracking may be removed for a user. This may be necessary if the + * tile will be destroyed due to being not available, but needs to be added once work profile is + * enabled (after restore), in the same position as it was in the restored data. + */ +@SysUISingleton +class WorkTileRestoreProcessor @Inject constructor() : RestoreProcessor { + + @GuardedBy("lastRestorePosition") private val lastRestorePosition = SparseIntArray() + + private val _removeTrackingForUser = + MutableSharedFlow<Int>(extraBufferCapacity = QSSettingsRestoredRepository.BUFFER_CAPACITY) + + /** + * Flow indicating that we may need to remove auto-add tracking for the work tile for a given + * user. + */ + fun removeTrackingForUser(userHandle: UserHandle): Flow<Unit> { + return _removeTrackingForUser.filter { it == userHandle.identifier }.map {} + } + + override suspend fun postProcessRestore(restoreData: RestoreData) { + if (TILE_SPEC in restoreData.restoredTiles) { + synchronized(lastRestorePosition) { + lastRestorePosition.put( + restoreData.userId, + restoreData.restoredTiles.indexOf(TILE_SPEC) + ) + } + _removeTrackingForUser.emit(restoreData.userId) + } + } + + fun pollLastPosition(userId: Int): Int { + return synchronized(lastRestorePosition) { + lastRestorePosition.getOrDefault(userId, TileSpecRepository.POSITION_AT_END).also { + lastRestorePosition.delete(userId) + } + } + } + + companion object { + private val TILE_SPEC = TileSpec.create(WorkModeTile.TILE_SPEC) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt index 5e3c34841c50..b22119966460 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt @@ -17,8 +17,10 @@ package com.android.systemui.qs.pipeline.domain.autoaddable import android.content.pm.UserInfo +import android.os.UserHandle import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking import com.android.systemui.qs.pipeline.domain.model.AutoAddable @@ -28,6 +30,8 @@ import com.android.systemui.settings.UserTracker import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.merge /** * [AutoAddable] for [WorkModeTile.TILE_SPEC]. @@ -36,17 +40,37 @@ import kotlinx.coroutines.flow.Flow * signal to remove it if there is not. */ @SysUISingleton -class WorkTileAutoAddable @Inject constructor(private val userTracker: UserTracker) : AutoAddable { +class WorkTileAutoAddable +@Inject +constructor( + private val userTracker: UserTracker, + private val workTileRestoreProcessor: WorkTileRestoreProcessor, +) : AutoAddable { private val spec = TileSpec.create(WorkModeTile.TILE_SPEC) override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> { - return conflatedCallbackFlow { + val removeTrackingDueToRestore: Flow<AutoAddSignal> = + workTileRestoreProcessor.removeTrackingForUser(UserHandle.of(userId)).mapNotNull { + val profiles = userTracker.userProfiles + if (profiles.any { it.id == userId } && !profiles.any { it.isManagedProfile }) { + // Only remove auto-added if there are no managed profiles for this user + AutoAddSignal.RemoveTracking(spec) + } else { + null + } + } + val signalsFromCallback = conflatedCallbackFlow { fun maybeSend(profiles: List<UserInfo>) { if (profiles.any { it.id == userId }) { // We are looking at the profiles of the correct user. if (profiles.any { it.isManagedProfile }) { - trySend(AutoAddSignal.Add(spec)) + trySend( + AutoAddSignal.Add( + spec, + workTileRestoreProcessor.pollLastPosition(userId), + ) + ) } else { trySend(AutoAddSignal.Remove(spec)) } @@ -65,6 +89,7 @@ class WorkTileAutoAddable @Inject constructor(private val userTracker: UserTrack awaitClose { userTracker.removeCallback(callback) } } + return merge(removeTrackingDueToRestore, signalsFromCallback) } override val autoAddTracking = AutoAddTracking.Always diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt index b74739322fcd..cde38359a871 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt @@ -103,6 +103,10 @@ constructor( qsPipelineLogger.logTileAutoRemoved(userId, signal.spec) repository.unmarkTileAdded(userId, signal.spec) } + is AutoAddSignal.RemoveTracking -> { + qsPipelineLogger.logTileUnmarked(userId, signal.spec) + repository.unmarkTileAdded(userId, signal.spec) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt index 9844903eff26..a5be14ec3776 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt @@ -3,14 +3,15 @@ package com.android.systemui.qs.pipeline.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.take @@ -33,6 +34,8 @@ constructor( private val tileSpecRepository: TileSpecRepository, private val autoAddRepository: AutoAddRepository, private val qsSettingsRestoredRepository: QSSettingsRestoredRepository, + private val restoreProcessors: Set<@JvmSuppressWildcards RestoreProcessor>, + private val qsPipelineLogger: QSPipelineLogger, @Application private val applicationScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, ) { @@ -40,14 +43,30 @@ constructor( @OptIn(ExperimentalCoroutinesApi::class) fun start() { applicationScope.launch(backgroundDispatcher) { - qsSettingsRestoredRepository.restoreData.flatMapConcat { data -> - autoAddRepository.autoAddedTiles(data.userId) - .take(1) - .map { tiles -> data to tiles } - }.collect { (restoreData, autoAdded) -> - tileSpecRepository.reconcileRestore(restoreData, autoAdded) - autoAddRepository.reconcileRestore(restoreData) - } + qsSettingsRestoredRepository.restoreData + .flatMapConcat { data -> + autoAddRepository.autoAddedTiles(data.userId).take(1).map { tiles -> + data to tiles + } + } + .collect { (restoreData, autoAdded) -> + restoreProcessors.forEach { + it.preProcessRestore(restoreData) + qsPipelineLogger.logRestoreProcessorApplied( + it::class.simpleName, + QSPipelineLogger.RestorePreprocessorStep.PREPROCESSING, + ) + } + tileSpecRepository.reconcileRestore(restoreData, autoAdded) + autoAddRepository.reconcileRestore(restoreData) + restoreProcessors.forEach { + it.postProcessRestore(restoreData) + qsPipelineLogger.logRestoreProcessorApplied( + it::class.simpleName, + QSPipelineLogger.RestorePreprocessorStep.POSTPROCESSING, + ) + } + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt index ed7b8bd4c2f4..8263680d7fad 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt @@ -34,4 +34,9 @@ sealed interface AutoAddSignal { data class Remove( override val spec: TileSpec, ) : AutoAddSignal + + /** Signal for remove the auto-add marker from the tile, but not remove the tile */ + data class RemoveTracking( + override val spec: TileSpec, + ) : AutoAddSignal } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt index bca86c9ee8af..7d2c6c8f70fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt @@ -209,6 +209,18 @@ constructor( ) } + fun logTileUnmarked(userId: Int, spec: TileSpec) { + tileAutoAddLogBuffer.log( + AUTO_ADD_TAG, + LogLevel.DEBUG, + { + int1 = userId + str1 = spec.toString() + }, + { "Tile $str1 unmarked as auto-added for user $int1" } + ) + } + fun logSettingsRestored(restoreData: RestoreData) { restoreLogBuffer.log( RESTORE_TAG, @@ -226,6 +238,21 @@ constructor( ) } + fun logRestoreProcessorApplied( + restoreProcessorClassName: String?, + step: RestorePreprocessorStep, + ) { + restoreLogBuffer.log( + RESTORE_TAG, + LogLevel.DEBUG, + { + str1 = restoreProcessorClassName + str2 = step.name + }, + { "Restore $str2 processed by $str1" } + ) + } + /** Reasons for destroying an existing tile. */ enum class TileDestroyedReason(val readable: String) { TILE_REMOVED("Tile removed from current set"), @@ -234,4 +261,9 @@ constructor( EXISTING_TILE_NOT_AVAILABLE("Existing tile not available"), TILE_NOT_PRESENT_IN_NEW_USER("Tile not present in new user"), } + + enum class RestorePreprocessorStep { + PREPROCESSING, + POSTPROCESSING + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt index 09d7a1f7142d..17b78ebf106c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt @@ -24,7 +24,7 @@ interface QSTileUserActionInteractor<DATA_TYPE> { * [QSTileInput.data]. It's guaranteed that [QSTileInput.userId] is the same as the id passed to * [QSTileDataInteractor] to get [QSTileInput.data]. * - * It's safe to run long running computations inside this function in this. + * It's safe to run long running computations inside this function. */ @WorkerThread suspend fun handleInput(input: QSTileInput<DATA_TYPE>) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt index 4a34276671c1..b325b4daeb81 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles.di +import android.content.Context +import android.content.res.Resources.Theme import com.android.systemui.qs.external.CustomTileStatePersister import com.android.systemui.qs.external.CustomTileStatePersisterImpl import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler @@ -27,6 +29,7 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProviderImpl import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import dagger.Binds import dagger.Module +import dagger.Provides import dagger.multibindings.Multibinds /** Module listing subcomponents */ @@ -57,4 +60,9 @@ interface QSTilesModule { @Binds fun bindCustomTileStatePersister(impl: CustomTileStatePersisterImpl): CustomTileStatePersister + + companion object { + + @Provides fun provideTilesTheme(context: Context): Theme = context.theme + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 2350b5dce8b8..9d214e7141a8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -59,12 +59,12 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.Prefs; -import com.android.systemui.res.R; import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wifitrackerlib.WifiEntry; @@ -157,14 +157,6 @@ public class InternetDialog extends SystemUIDialog implements // Wi-Fi scanning progress bar protected boolean mIsProgressBarVisible; - protected boolean mIsSearchingHidden; - protected final Runnable mHideProgressBarRunnable = () -> { - setProgressBarVisible(false); - }; - protected Runnable mHideSearchingRunnable = () -> { - mIsSearchingHidden = true; - mInternetDialogSubTitle.setText(getSubtitleText()); - }; @Inject public InternetDialog(Context context, InternetDialogFactory internetDialogFactory, @@ -285,8 +277,6 @@ public class InternetDialog extends SystemUIDialog implements if (DEBUG) { Log.d(TAG, "onStop"); } - mHandler.removeCallbacks(mHideProgressBarRunnable); - mHandler.removeCallbacks(mHideSearchingRunnable); mMobileNetworkLayout.setOnClickListener(null); mConnectedWifListLayout.setOnClickListener(null); if (mSecondaryMobileNetworkLayout != null) { @@ -335,7 +325,6 @@ public class InternetDialog extends SystemUIDialog implements return; } - showProgressBar(); final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked(); final boolean isWifiEnabled = mInternetDialogController.isWifiEnabled(); final boolean isWifiScanEnabled = mInternetDialogController.isWifiScanEnabled(); @@ -641,8 +630,7 @@ public class InternetDialog extends SystemUIDialog implements @Nullable CharSequence getSubtitleText() { - return mInternetDialogController.getSubtitleText( - mIsProgressBarVisible && !mIsSearchingHidden); + return mInternetDialogController.getSubtitleText(mIsProgressBarVisible); } private Drawable getSignalStrengthDrawable(int subId) { @@ -657,20 +645,6 @@ public class InternetDialog extends SystemUIDialog implements return mInternetDialogController.getMobileNetworkSummary(subId); } - protected void showProgressBar() { - if (!mInternetDialogController.isWifiEnabled() - || mInternetDialogController.isDeviceLocked()) { - setProgressBarVisible(false); - return; - } - setProgressBarVisible(true); - if (mConnectedWifiEntry != null || mWifiEntriesCount > 0) { - mHandler.postDelayed(mHideProgressBarRunnable, PROGRESS_DELAY_MS); - } else if (!mIsSearchingHidden) { - mHandler.postDelayed(mHideSearchingRunnable, PROGRESS_DELAY_MS); - } - } - private void setProgressBarVisible(boolean visible) { if (mIsProgressBarVisible == visible) { return; @@ -823,6 +797,11 @@ public class InternetDialog extends SystemUIDialog implements } @Override + public void onWifiScan(boolean isScan) { + setProgressBarVisible(isScan); + } + + @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (mAlertDialog != null && !mAlertDialog.isShowing()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index f516f5521d25..592cb3b18e80 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -75,7 +75,6 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.SignalStrengthUtil; import com.android.settingslib.wifi.WifiUtils; import com.android.settingslib.wifi.dpp.WifiDppIntentHelper; -import com.android.systemui.res.R; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -84,6 +83,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.res.R; import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.LocationController; @@ -1129,6 +1129,15 @@ public class InternetDialogController implements AccessPointController.AccessPoi public void onSettingsActivityTriggered(Intent settingsIntent) { } + @Override + public void onWifiScan(boolean isScan) { + if (!isWifiEnabled() || isDeviceLocked()) { + mCallback.onWifiScan(false); + return; + } + mCallback.onWifiScan(isScan); + } + private class InternetTelephonyCallback extends TelephonyCallback implements TelephonyCallback.DataConnectionStateListener, TelephonyCallback.DisplayInfoListener, @@ -1372,6 +1381,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries, @Nullable WifiEntry connectedEntry, boolean hasMoreWifiEntries); + + void onWifiScan(boolean isScan); } void makeOverlayToast(int stringId) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt index cfb544226c83..9b8dba166274 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.airplane.domain import android.content.res.Resources +import android.content.res.Resources.Theme import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper @@ -27,18 +28,25 @@ import com.android.systemui.res.R import javax.inject.Inject /** Maps [AirplaneModeTileModel] to [QSTileState]. */ -class AirplaneModeMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<AirplaneModeTileModel> { +class AirplaneModeMapper +@Inject +constructor( + @Main private val resources: Resources, + val theme: Theme, +) : QSTileDataToStateMapper<AirplaneModeTileModel> { override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState = - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { val icon = - Icon.Resource( - if (data.isEnabled) { - R.drawable.qs_airplane_icon_on - } else { - R.drawable.qs_airplane_icon_off - }, + Icon.Loaded( + resources.getDrawable( + if (data.isEnabled) { + R.drawable.qs_airplane_icon_on + } else { + R.drawable.qs_airplane_icon_off + }, + theme, + ), contentDescription = null ) this.icon = { icon } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt index 63865777e14f..e075e76595d4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.alarm.domain import android.content.res.Resources +import android.content.res.Resources.Theme import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel @@ -30,14 +31,18 @@ import java.util.TimeZone import javax.inject.Inject /** Maps [AlarmTileModel] to [QSTileState]. */ -class AlarmTileMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<AlarmTileModel> { +class AlarmTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Theme, +) : QSTileDataToStateMapper<AlarmTileModel> { companion object { val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E hh:mm a") val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm") } override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState = - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { when (data) { is AlarmTileModel.NextAlarmSet -> { activationState = QSTileState.ActivationState.ACTIVE diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt index 881a6bd156d2..1b3b5848a7ce 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.flashlight.domain import android.content.res.Resources +import android.content.res.Resources.Theme import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper @@ -27,18 +28,25 @@ import com.android.systemui.res.R import javax.inject.Inject /** Maps [FlashlightTileModel] to [QSTileState]. */ -class FlashlightMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<FlashlightTileModel> { +class FlashlightMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Theme, +) : QSTileDataToStateMapper<FlashlightTileModel> { override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState = - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { val icon = - Icon.Resource( - if (data.isEnabled) { - R.drawable.qs_flashlight_icon_on - } else { - R.drawable.qs_flashlight_icon_off - }, + Icon.Loaded( + resources.getDrawable( + if (data.isEnabled) { + R.drawable.qs_flashlight_icon_on + } else { + R.drawable.qs_flashlight_icon_off + }, + theme, + ), contentDescription = null ) this.icon = { icon } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt index 7e7034d65efd..fe5445d00670 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.location.domain import android.content.res.Resources +import android.content.res.Resources.Theme import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper @@ -27,18 +28,25 @@ import com.android.systemui.res.R import javax.inject.Inject /** Maps [LocationTileModel] to [QSTileState]. */ -class LocationTileMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<LocationTileModel> { +class LocationTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Theme, +) : QSTileDataToStateMapper<LocationTileModel> { override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState = - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { val icon = - Icon.Resource( - if (data.isEnabled) { - R.drawable.qs_location_icon_on - } else { - R.drawable.qs_location_icon_off - }, + Icon.Loaded( + resources.getDrawable( + if (data.isEnabled) { + R.drawable.qs_location_icon_on + } else { + R.drawable.qs_location_icon_off + }, + theme, + ), contentDescription = null ) this.icon = { icon } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt index 25b09131522b..df25600228a5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt @@ -27,20 +27,28 @@ import com.android.systemui.res.R import javax.inject.Inject /** Maps [DataSaverTileModel] to [QSTileState]. */ -class DataSaverTileMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<DataSaverTileModel> { +class DataSaverTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, +) : QSTileDataToStateMapper<DataSaverTileModel> { override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState = - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { with(data) { + val iconRes: Int if (isEnabled) { activationState = QSTileState.ActivationState.ACTIVE - icon = { Icon.Resource(R.drawable.qs_data_saver_icon_on, null) } + iconRes = R.drawable.qs_data_saver_icon_on secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[2] } else { activationState = QSTileState.ActivationState.INACTIVE - icon = { Icon.Resource(R.drawable.qs_data_saver_icon_off, null) } + iconRes = R.drawable.qs_data_saver_icon_off secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1] } + val loadedIcon = + Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null) + icon = { loadedIcon } contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt index 3f30c75a6b6a..ffef2b6ecfb5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles.impl.uimodenight.domain import android.app.UiModeManager import android.content.res.Resources +import android.content.res.Resources.Theme import android.text.TextUtils import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Main @@ -31,15 +32,19 @@ import java.time.format.DateTimeFormatter import javax.inject.Inject /** Maps [UiModeNightTileModel] to [QSTileState]. */ -class UiModeNightTileMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<UiModeNightTileModel> { +class UiModeNightTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Theme, +) : QSTileDataToStateMapper<UiModeNightTileModel> { companion object { val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a") val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm") } override fun map(config: QSTileConfig, data: UiModeNightTileModel): QSTileState = with(data) { - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { var shouldSetSecondaryLabel = false if (isPowerSave) { @@ -116,8 +121,9 @@ class UiModeNightTileMapper @Inject constructor(@Main private val resources: Res if (activationState == QSTileState.ActivationState.ACTIVE) R.drawable.qs_light_dark_theme_icon_on else R.drawable.qs_light_dark_theme_icon_off - val iconResource = Icon.Resource(iconRes, null) - icon = { iconResource } + val loadedIcon = + Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null) + icon = { loadedIcon } supportedActions = if (activationState == QSTileState.ActivationState.UNAVAILABLE) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt index 23e0cb66bb6a..be1b7404314f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.viewmodel import android.content.res.Resources +import android.content.res.Resources.Theme import android.service.quicksettings.Tile import android.view.View import android.widget.Switch @@ -47,14 +48,17 @@ data class QSTileState( fun build( resources: Resources, + theme: Theme, config: QSTileUIConfig, build: Builder.() -> Unit - ): QSTileState = - build( - { Icon.Resource(config.iconRes, null) }, + ): QSTileState { + val iconDrawable = resources.getDrawable(config.iconRes, theme) + return build( + { Icon.Loaded(iconDrawable, null) }, resources.getString(config.labelRes), build, ) + } fun build(icon: () -> Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState = Builder(icon, label).apply(build).build() diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index 8a93ef65b4bf..d3459b109d79 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -32,6 +32,7 @@ import javax.inject.Inject * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion. */ @SysUISingleton +@Deprecated("Use ShadeInteractor instead") class ShadeExpansionStateManager @Inject constructor() { private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>() @@ -49,6 +50,7 @@ class ShadeExpansionStateManager @Inject constructor() { * * @see #addExpansionListener */ + @Deprecated("Use ShadeInteractor instead") fun addExpansionListener(listener: ShadeExpansionListener): ShadeExpansionChangeEvent { expansionListeners.add(listener) return ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount) @@ -60,6 +62,7 @@ class ShadeExpansionStateManager @Inject constructor() { } /** Adds a listener that will be notified when the panel state has changed. */ + @Deprecated("Use ShadeInteractor instead") fun addStateListener(listener: ShadeStateListener) { stateListeners.add(listener) } diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt index c59ef2632f15..d26fded19cc1 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt @@ -59,6 +59,11 @@ abstract class SmartspaceModule { * The BcSmartspaceDataPlugin for the standalone weather. */ const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin" + + /** + * The BcSmartspaceDataProvider for the glanceable hub. + */ + const val GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN = "glanceable_hub_smartspace_data_plugin" } @BindsOptionalOf @@ -78,4 +83,8 @@ abstract class SmartspaceModule { abstract fun bindSmartspacePrecondition( lockscreenPrecondition: LockscreenPrecondition? ): SmartspacePrecondition? + + @BindsOptionalOf + @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN) + abstract fun optionalBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin? } diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt index 2fc0ec290a90..095d30ef55df 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt @@ -19,9 +19,9 @@ package com.android.systemui.smartspace.data.repository import android.app.smartspace.SmartspaceTarget import android.os.Parcelable import android.widget.RemoteViews +import com.android.systemui.communal.smartspace.CommunalSmartspaceController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.BcSmartspaceDataPlugin -import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -32,37 +32,37 @@ interface SmartspaceRepository { /** Whether [RemoteViews] are passed through smartspace targets. */ val isSmartspaceRemoteViewsEnabled: Boolean - /** Smartspace targets for the lockscreen surface. */ - val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> + /** Smartspace targets for the communal surface. */ + val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> } @SysUISingleton class SmartspaceRepositoryImpl @Inject constructor( - private val lockscreenSmartspaceController: LockscreenSmartspaceController, + private val communalSmartspaceController: CommunalSmartspaceController, ) : SmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener { override val isSmartspaceRemoteViewsEnabled: Boolean get() = android.app.smartspace.flags.Flags.remoteViews() - private val _lockscreenSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> = + private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> = MutableStateFlow(emptyList()) - override val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> = - _lockscreenSmartspaceTargets + override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> = + _communalSmartspaceTargets .onStart { - lockscreenSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl) + communalSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl) } .onCompletion { - lockscreenSmartspaceController.removeListener( + communalSmartspaceController.removeListener( listener = this@SmartspaceRepositoryImpl ) } override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) { targetsNullable?.let { targets -> - _lockscreenSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>() + _communalSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>() } - ?: run { _lockscreenSmartspaceTargets.value = emptyList() } + ?: run { _communalSmartspaceTargets.value = emptyList() } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt index 490994d805fe..fc474d2a1307 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt @@ -89,5 +89,12 @@ interface AccessPointController { * "wifi_start_connect_ssid" set as an extra */ fun onSettingsActivityTriggered(settingsIntent: Intent?) + + /** + * Called whenever a Wi-Fi scan is triggered. + * + * @param isScan Whether Wi-Fi scan is triggered or not. + */ + fun onWifiScan(isScan: Boolean) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java index 91ca148c93c9..3a31851bcb4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java @@ -213,6 +213,12 @@ public class AccessPointControllerImpl implements AccessPointController, } } + private void fireWifiScanCallback(boolean isScan) { + for (AccessPointCallback callback : mCallbacks) { + callback.onWifiScan(isScan); + } + } + void dump(PrintWriter pw) { IndentingPrintWriter ipw = new IndentingPrintWriter(pw); ipw.println("AccessPointControllerImpl:"); @@ -240,6 +246,14 @@ public class AccessPointControllerImpl implements AccessPointController, } @Override + public void onWifiEntriesChanged(@WifiPickerTracker.WifiEntriesChangedReason int reason) { + onWifiEntriesChanged(); + if (reason == WifiPickerTracker.WIFI_ENTRIES_CHANGED_REASON_SCAN_RESULTS) { + fireWifiScanCallback(false /* isScan */); + } + } + + @Override public void onNumSavedNetworksChanged() { // Do nothing } @@ -249,6 +263,11 @@ public class AccessPointControllerImpl implements AccessPointController, // Do nothing } + @Override + public void onScanRequested() { + fireWifiScanCallback(true /* isScan */); + } + private final WifiEntry.ConnectCallback mConnectCallback = new WifiEntry.ConnectCallback() { @Override public void onConnectResult(int status) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index 92391e7c76f0..e1e30e1d74f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder import android.graphics.Color import android.graphics.Rect +import android.util.Log import android.view.View +import android.view.ViewGroup import android.widget.FrameLayout import androidx.annotation.ColorInt import androidx.collection.ArrayMap @@ -220,7 +222,7 @@ object NotificationIconContainerViewBinder { notifyBindingFailures: (Collection<String>) -> Unit, viewStore: IconViewStore, bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit = { _, _ -> }, - ): Unit = coroutineScope { + ) { val iconSizeFlow: Flow<Int> = configuration.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size_sp, @@ -235,6 +237,21 @@ object NotificationIconContainerViewBinder { -> FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight) } + try { + bindIcons(view, layoutParams, notifyBindingFailures, viewStore, bindIcon) + } finally { + // Detach everything so that child SBIVs don't hold onto a reference to the container. + view.detachAllIcons() + } + } + + private suspend fun Flow<NotificationIconsViewData>.bindIcons( + view: NotificationIconContainer, + layoutParams: Flow<FrameLayout.LayoutParams>, + notifyBindingFailures: (Collection<String>) -> Unit, + viewStore: IconViewStore, + bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit, + ): Unit = coroutineScope { val failedBindings = mutableSetOf<String>() val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>() var prevIcons = NotificationIconsViewData() @@ -266,9 +283,17 @@ object NotificationIconContainerViewBinder { continue } failedBindings.remove(notifKey) - // The view might still be transiently added if it was just removed and added - // again - view.removeTransientView(sbiv) + (sbiv.parent as? ViewGroup)?.run { + if (this !== view) { + Log.wtf(TAG, "StatusBarIconView($notifKey) has an unexpected parent") + } + // If the container was re-inflated and re-bound, then SBIVs might still be + // attached to the prior view. + removeView(sbiv) + // The view might still be transiently added if it was just removed and + // added again. + removeTransientView(sbiv) + } view.addView(sbiv, idx) boundViewsByNotifKey.remove(notifKey)?.second?.cancel() boundViewsByNotifKey[notifKey] = @@ -351,7 +376,8 @@ object NotificationIconContainerViewBinder { fun iconView(key: String): StatusBarIconView? } - @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE + @ColorInt private const val DEFAULT_AOD_ICON_COLOR = Color.WHITE + private const val TAG = "NotifIconContainerViewBinder" } /** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt index adf6cca1ac65..625fdc1c12f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt @@ -20,6 +20,8 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import android.content.Context import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.res.R import com.android.systemui.statusbar.policy.SplitShadeStateController import javax.inject.Inject @@ -28,6 +30,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart @@ -39,7 +42,9 @@ class SharedNotificationContainerInteractor constructor( configurationRepository: ConfigurationRepository, private val context: Context, - private val splitShadeStateController: SplitShadeStateController + private val splitShadeStateController: SplitShadeStateController, + keyguardInteractor: KeyguardInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, ) { private val _topPosition = MutableStateFlow(0f) @@ -75,6 +80,19 @@ constructor( } .distinctUntilChanged() + /** + * The notification shelf can extend over the lock icon area if: + * * UDFPS supported. Ambient indication will always appear below + * * UDFPS not supported and ambient indication not visible, which will appear above lock icon + */ + val useExtraShelfSpace: Flow<Boolean> = + combine( + keyguardInteractor.ambientIndicationVisible, + deviceEntryUdfpsInteractor.isUdfpsSupported, + ) { ambientIndicationVisible, isUdfpsSupported -> + isUdfpsSupported || !ambientIndicationVisible + } + val isSplitShadeEnabled: Flow<Boolean> = configurationBasedDimensions .map { dimens: ConfigurationBasedDimensions -> dimens.useSplitShade } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index af56a3f51281..12927b87630e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -101,12 +101,13 @@ object SharedNotificationContainerBinder { launch { viewModel - .getMaxNotifications { space -> + .getMaxNotifications { space, extraShelfSpace -> + val shelfHeight = controller.getShelfHeight().toFloat() notificationStackSizeCalculator.computeMaxKeyguardNotifications( controller.getView(), space, - 0f, // Vertical space for shelf is already accounted for - controller.getShelfHeight().toFloat(), + if (extraShelfSpace) shelfHeight else 0f, + shelfHeight, ) } .collect { controller.setMaxDisplayedNotifications(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 9594bc3bfd86..eff91e55d9a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -229,7 +229,7 @@ constructor( * When expanding or when the user is interacting with the shade, keep the count stable; do not * emit a value. */ - fun getMaxNotifications(calculateSpace: (Float) -> Int): Flow<Int> { + fun getMaxNotifications(calculateSpace: (Float, Boolean) -> Int): Flow<Int> { val showLimitedNotifications = isOnLockscreenWithoutShade val showUnlimitedNotifications = combine( @@ -245,11 +245,17 @@ constructor( shadeInteractor.isUserInteracting, bounds, interactor.notificationStackChanged.onStart { emit(Unit) }, - ) { showLimitedNotifications, showUnlimitedNotifications, isUserInteracting, bounds, _ - -> + interactor.useExtraShelfSpace, + ) { flows -> + val showLimitedNotifications = flows[0] as Boolean + val showUnlimitedNotifications = flows[1] as Boolean + val isUserInteracting = flows[2] as Boolean + val bounds = flows[3] as NotificationContainerBounds + val useExtraShelfSpace = flows[5] as Boolean + if (!isUserInteracting) { if (showLimitedNotifications) { - emit(calculateSpace(bounds.bottom - bounds.top)) + emit(calculateSpace(bounds.bottom - bounds.top, useExtraShelfSpace)) } else if (showUnlimitedNotifications) { emit(-1) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 00e78a49ba19..0dabafbdecb0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -400,6 +400,21 @@ public class NotificationIconContainer extends ViewGroup { } } + /** + * Removes all child {@link StatusBarIconView} instances from this container, immediately and + * without animation. This should be called when tearing down this container so that external + * icon views are not holding onto a reference thru {@link View#getParent()}. + */ + public void detachAllIcons() { + boolean animsWereEnabled = mAnimationsEnabled; + boolean wasChangingPositions = mChangingViewPositions; + mAnimationsEnabled = false; + mChangingViewPositions = true; + removeAllViews(); + mChangingViewPositions = wasChangingPositions; + mAnimationsEnabled = animsWereEnabled; + } + public boolean areIconsOverflowing() { return mIsShowingOverflowDot; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt index e931384fd61e..65f68f9df3e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt @@ -22,6 +22,7 @@ import android.provider.Settings import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater +import android.view.View import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -39,6 +40,8 @@ import com.android.systemui.util.time.FakeSystemClock import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.eq import org.mockito.Mockito.verify @@ -73,13 +76,20 @@ class ContrastDialogDelegateTest : SysuiTestCase() { if (Looper.myLooper() == null) Looper.prepare() mContrastDialogDelegate = - ContrastDialogDelegate( - sysuiDialogFactory, - mainExecutor, - mockUiModeManager, - mockUserTracker, - mockSecureSettings - ) + ContrastDialogDelegate( + sysuiDialogFactory, + mainExecutor, + mockUiModeManager, + mockUserTracker, + mockSecureSettings + ) + + mContrastDialogDelegate.createDialog() + val viewCaptor = ArgumentCaptor.forClass(View::class.java) + verify(sysuiDialog).setView(viewCaptor.capture()) + whenever(sysuiDialog.requireViewById(anyInt()) as View?).then { + viewCaptor.value.requireViewById(it.getArgument(0)) + } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 687800714e05..459a74c82da4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -184,6 +184,13 @@ class KeyguardRootViewModelTest : SysuiTestCase() { } @Test + fun translationYInitialValueIsZero() = + testScope.runTest { + val translationY by collectLastValue(underTest.translationY) + assertThat(translationY).isEqualTo(0) + } + + @Test fun translationAndScaleFromBurnInNotDozing() = testScope.runTest { val translationX by collectLastValue(underTest.translationX) diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt index 0a8c0ab9817d..e4432f3038bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt @@ -31,18 +31,20 @@ import android.permission.PermissionGroupUsage import android.permission.PermissionManager import android.testing.AndroidTestingRunner import android.view.View +import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.animation.LaunchableView import com.android.systemui.appops.AppOpsController import com.android.systemui.plugins.ActivityStarter import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -56,12 +58,12 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -83,60 +85,48 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { private val TEST_INTENT = Intent("test_intent_action") } - @Mock - private lateinit var dialog: PrivacyDialogV2 - @Mock - private lateinit var permissionManager: PermissionManager - @Mock - private lateinit var packageManager: PackageManager - @Mock - private lateinit var privacyItemController: PrivacyItemController - @Mock - private lateinit var userTracker: UserTracker - @Mock - private lateinit var activityStarter: ActivityStarter - @Mock - private lateinit var privacyLogger: PrivacyLogger - @Mock - private lateinit var keyguardStateController: KeyguardStateController - @Mock - private lateinit var appOpsController: AppOpsController + @Mock private lateinit var dialog: PrivacyDialogV2 + @Mock private lateinit var permissionManager: PermissionManager + @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var privacyItemController: PrivacyItemController + @Mock private lateinit var userTracker: UserTracker + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var privacyLogger: PrivacyLogger + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var appOpsController: AppOpsController @Captor private lateinit var dialogDismissedCaptor: ArgumentCaptor<PrivacyDialogV2.OnDialogDismissed> - @Captor - private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback> - @Captor - private lateinit var intentCaptor: ArgumentCaptor<Intent> - @Mock - private lateinit var uiEventLogger: UiEventLogger - @Mock - private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + @Captor private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback> + @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent> + @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator private val backgroundExecutor = FakeExecutor(FakeSystemClock()) private val uiExecutor = FakeExecutor(FakeSystemClock()) private lateinit var controller: PrivacyDialogControllerV2 private var nextUid: Int = 0 - private val dialogProvider = object : PrivacyDialogControllerV2.DialogProvider { - var list: List<PrivacyDialogV2.PrivacyElement>? = null - var manageApp: ((String, Int, Intent) -> Unit)? = null - var closeApp: ((String, Int) -> Unit)? = null - var openPrivacyDashboard: (() -> Unit)? = null - - override fun makeDialog( - context: Context, - list: List<PrivacyDialogV2.PrivacyElement>, - manageApp: (String, Int, Intent) -> Unit, - closeApp: (String, Int) -> Unit, - openPrivacyDashboard: () -> Unit - ): PrivacyDialogV2 { - this.list = list - this.manageApp = manageApp - this.closeApp = closeApp - this.openPrivacyDashboard = openPrivacyDashboard - return dialog + private val dialogProvider = + object : PrivacyDialogControllerV2.DialogProvider { + var list: List<PrivacyDialogV2.PrivacyElement>? = null + var manageApp: ((String, Int, Intent) -> Unit)? = null + var closeApp: ((String, Int) -> Unit)? = null + var openPrivacyDashboard: (() -> Unit)? = null + + override fun makeDialog( + context: Context, + list: List<PrivacyDialogV2.PrivacyElement>, + manageApp: (String, Int, Intent) -> Unit, + closeApp: (String, Int) -> Unit, + openPrivacyDashboard: () -> Unit + ): PrivacyDialogV2 { + this.list = list + this.manageApp = manageApp + this.closeApp = closeApp + this.openPrivacyDashboard = openPrivacyDashboard + return dialog + } } - } @Before fun setUp() { @@ -144,7 +134,8 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { nextUid = 0 setUpDefaultMockResponses() - controller = PrivacyDialogControllerV2( + controller = + PrivacyDialogControllerV2( permissionManager, packageManager, privacyItemController, @@ -158,7 +149,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { uiEventLogger, dialogLaunchAnimator, dialogProvider - ) + ) } @After @@ -197,7 +188,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt()) backgroundExecutor.runAllReady() verify(packageManager, atLeastOnce()) - .getApplicationInfoAsUser(anyString(), anyInt(), anyInt()) + .getApplicationInfoAsUser(anyString(), anyInt(), anyInt()) } @Test @@ -208,20 +199,25 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { controller.showDialog(context) exhaustExecutors() - verify(dialogLaunchAnimator, never()).showFromView(any(), any(), any(), anyBoolean()) + verify(dialogLaunchAnimator, never()).show(any(), any(), anyBoolean()) verify(dialog).show() } @Test fun testShowDialogShowsDialogWithView() { - val view = View(context) + val parent = LinearLayout(context) + val view = + object : View(context), LaunchableView { + override fun setShouldBlockVisibilityChanges(block: Boolean) {} + } + parent.addView(view) val usage = createMockPermGroupUsage() `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context, view) exhaustExecutors() - verify(dialogLaunchAnimator).showFromView(dialog, view) + verify(dialogLaunchAnimator).show(eq(dialog), any(), anyBoolean()) verify(dialog, never()).show() } @@ -276,7 +272,8 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testSingleElementInList() { - val usage = createMockPermGroupUsage( + val usage = + createMockPermGroupUsage( packageName = TEST_PACKAGE_NAME, uid = generateUidForUser(USER_ID), permissionGroupName = PERM_CAMERA, @@ -285,7 +282,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { isPhoneCall = false, attributionTag = null, proxyLabel = TEST_PROXY_LABEL - ) + ) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context) @@ -304,33 +301,38 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { assertThat(list.get(0).isPhoneCall).isFalse() assertThat(list.get(0).isService).isFalse() assertThat(list.get(0).permGroupName).isEqualTo(PERM_CAMERA) - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID) + ) + ) + .isTrue() } } private fun isIntentEqual(actual: Intent, expected: Intent): Boolean { return actual.action == expected.action && - actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME) == + actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME) == expected.getStringExtra(Intent.EXTRA_PACKAGE_NAME) && - actual.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle == + actual.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle == expected.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle } @Test fun testTwoElementsDifferentType_sorted() { - val usage_camera = createMockPermGroupUsage( + val usage_camera = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_camera", permissionGroupName = PERM_CAMERA - ) - val usage_microphone = createMockPermGroupUsage( + ) + val usage_microphone = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_microphone", permissionGroupName = PERM_MICROPHONE - ) - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_microphone, usage_camera) - ) + ) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_microphone, usage_camera)) controller.showDialog(context) exhaustExecutors() @@ -343,17 +345,12 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testTwoElementsSameType_oneActive() { - val usage_active = createMockPermGroupUsage( - packageName = "${TEST_PACKAGE_NAME}_active", - isActive = true - ) - val usage_recent = createMockPermGroupUsage( - packageName = "${TEST_PACKAGE_NAME}_recent", - isActive = false - ) - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_recent, usage_active) - ) + val usage_active = + createMockPermGroupUsage(packageName = "${TEST_PACKAGE_NAME}_active", isActive = true) + val usage_recent = + createMockPermGroupUsage(packageName = "${TEST_PACKAGE_NAME}_recent", isActive = false) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_recent, usage_active)) controller.showDialog(context) exhaustExecutors() @@ -364,19 +361,20 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testTwoElementsSameType_twoActive() { - val usage_active = createMockPermGroupUsage( + val usage_active = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_active", isActive = true, lastAccessTimeMillis = 0L - ) - val usage_active_moreRecent = createMockPermGroupUsage( + ) + val usage_active_moreRecent = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_active_recent", isActive = true, lastAccessTimeMillis = 1L - ) - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_active, usage_active_moreRecent) - ) + ) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_active, usage_active_moreRecent)) controller.showDialog(context) exhaustExecutors() assertThat(dialogProvider.list).hasSize(2) @@ -386,24 +384,26 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testManyElementsSameType_bothRecent() { - val usage_recent = createMockPermGroupUsage( + val usage_recent = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_recent", isActive = false, lastAccessTimeMillis = 0L - ) - val usage_moreRecent = createMockPermGroupUsage( + ) + val usage_moreRecent = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_moreRecent", isActive = false, lastAccessTimeMillis = 1L - ) - val usage_mostRecent = createMockPermGroupUsage( + ) + val usage_mostRecent = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_mostRecent", isActive = false, lastAccessTimeMillis = 2L - ) - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_recent, usage_mostRecent, usage_moreRecent) - ) + ) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_recent, usage_mostRecent, usage_moreRecent)) controller.showDialog(context) exhaustExecutors() @@ -414,19 +414,12 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testMicAndCameraDisabled() { - val usage_camera = createMockPermGroupUsage( - permissionGroupName = PERM_CAMERA - ) - val usage_microphone = createMockPermGroupUsage( - permissionGroupName = PERM_MICROPHONE - ) - val usage_location = createMockPermGroupUsage( - permissionGroupName = PERM_LOCATION - ) - - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_camera, usage_location, usage_microphone) - ) + val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA) + val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE) + val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION) + + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_camera, usage_location, usage_microphone)) `when`(privacyItemController.micCameraAvailable).thenReturn(false) controller.showDialog(context) @@ -438,45 +431,29 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testLocationDisabled() { - val usage_camera = createMockPermGroupUsage( - permissionGroupName = PERM_CAMERA - ) - val usage_microphone = createMockPermGroupUsage( - permissionGroupName = PERM_MICROPHONE - ) - val usage_location = createMockPermGroupUsage( - permissionGroupName = PERM_LOCATION - ) - - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_camera, usage_location, usage_microphone) - ) + val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA) + val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE) + val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION) + + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_camera, usage_location, usage_microphone)) `when`(privacyItemController.locationAvailable).thenReturn(false) controller.showDialog(context) exhaustExecutors() assertThat(dialogProvider.list).hasSize(2) - dialogProvider.list?.forEach { - assertThat(it.type).isNotEqualTo(PrivacyType.TYPE_LOCATION) - } + dialogProvider.list?.forEach { assertThat(it.type).isNotEqualTo(PrivacyType.TYPE_LOCATION) } } @Test fun testAllIndicatorsAvailable() { - val usage_camera = createMockPermGroupUsage( - permissionGroupName = PERM_CAMERA - ) - val usage_microphone = createMockPermGroupUsage( - permissionGroupName = PERM_MICROPHONE - ) - val usage_location = createMockPermGroupUsage( - permissionGroupName = PERM_LOCATION - ) - - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_camera, usage_location, usage_microphone) - ) + val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA) + val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE) + val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION) + + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_camera, usage_location, usage_microphone)) `when`(privacyItemController.micCameraAvailable).thenReturn(true) `when`(privacyItemController.locationAvailable).thenReturn(true) @@ -488,19 +465,12 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testNoIndicatorsAvailable() { - val usage_camera = createMockPermGroupUsage( - permissionGroupName = PERM_CAMERA - ) - val usage_microphone = createMockPermGroupUsage( - permissionGroupName = PERM_MICROPHONE - ) - val usage_location = createMockPermGroupUsage( - permissionGroupName = PERM_LOCATION - ) - - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_camera, usage_location, usage_microphone) - ) + val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA) + val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE) + val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION) + + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_camera, usage_location, usage_microphone)) `when`(privacyItemController.micCameraAvailable).thenReturn(false) `when`(privacyItemController.locationAvailable).thenReturn(false) @@ -512,11 +482,9 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testNotCurrentUser() { - val usage_other = createMockPermGroupUsage( - uid = generateUidForUser(ENT_USER_ID + 1) - ) + val usage_other = createMockPermGroupUsage(uid = generateUidForUser(ENT_USER_ID + 1)) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) - .thenReturn(listOf(usage_other)) + .thenReturn(listOf(usage_other)) controller.showDialog(context) exhaustExecutors() @@ -559,9 +527,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { // Calls happen in val usage = createMockPermGroupUsage(uid = SYSTEM_UID, isPhoneCall = true) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) - `when`(userTracker.userProfiles).thenReturn(listOf( - UserInfo(ENT_USER_ID, "", 0) - )) + `when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(ENT_USER_ID, "", 0))) controller.showDialog(context) exhaustExecutors() @@ -577,8 +543,12 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { exhaustExecutors() dialogProvider.manageApp?.invoke(TEST_PACKAGE_NAME, USER_ID, TEST_INTENT) - verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS, - USER_ID, TEST_PACKAGE_NAME) + verify(uiEventLogger) + .log( + PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS, + USER_ID, + TEST_PACKAGE_NAME + ) } @Test @@ -589,8 +559,12 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { exhaustExecutors() dialogProvider.closeApp?.invoke(TEST_PACKAGE_NAME, USER_ID) - verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP, - USER_ID, TEST_PACKAGE_NAME) + verify(uiEventLogger) + .log( + PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP, + USER_ID, + TEST_PACKAGE_NAME + ) } @Test @@ -629,9 +603,13 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { exhaustExecutors() dialogProvider.list?.let { list -> - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID) + ) + ) + .isTrue() assertThat(list.get(0).isService).isFalse() } } @@ -648,45 +626,58 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { exhaustExecutors() dialogProvider.list?.let { list -> - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent( - TEST_PACKAGE_NAME, ENT_USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent( + TEST_PACKAGE_NAME, + ENT_USER_ID + ) + ) + ) + .isTrue() assertThat(list.get(0).isService).isFalse() } } @Test fun testDefaultIntentOnInvalidAttributionTag() { - val usage = createMockPermGroupUsage( + val usage = + createMockPermGroupUsage( attributionTag = "INVALID_ATTRIBUTION_TAG", proxyLabel = TEST_PROXY_LABEL - ) + ) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context) exhaustExecutors() dialogProvider.list?.let { list -> - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID) + ) + ) + .isTrue() assertThat(list.get(0).isService).isFalse() } } @Test fun testServiceIntentOnCorrectSubAttribution() { - val usage = createMockPermGroupUsage( + val usage = + createMockPermGroupUsage( attributionTag = TEST_ATTRIBUTION_TAG, attributionLabel = "TEST_LABEL" - ) + ) val activityInfo = createMockActivityInfo() val resolveInfo = createMockResolveInfo(activityInfo) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) - `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())) - .thenAnswer { resolveInfo } + `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer { + resolveInfo + } controller.showDialog(context) exhaustExecutors() @@ -694,57 +685,61 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { val navigationIntent = list.get(0).navigationIntent!! assertThat(navigationIntent.action).isEqualTo(Intent.ACTION_MANAGE_PERMISSION_USAGE) assertThat(navigationIntent.getStringExtra(Intent.EXTRA_PERMISSION_GROUP_NAME)) - .isEqualTo(PERM_CAMERA) + .isEqualTo(PERM_CAMERA) assertThat(navigationIntent.getStringArrayExtra(Intent.EXTRA_ATTRIBUTION_TAGS)) - .isEqualTo(arrayOf(TEST_ATTRIBUTION_TAG.toString())) + .isEqualTo(arrayOf(TEST_ATTRIBUTION_TAG.toString())) assertThat(navigationIntent.getBooleanExtra(Intent.EXTRA_SHOWING_ATTRIBUTION, false)) - .isTrue() + .isTrue() assertThat(list.get(0).isService).isTrue() } } @Test fun testDefaultIntentOnMissingAttributionLabel() { - val usage = createMockPermGroupUsage( - attributionTag = TEST_ATTRIBUTION_TAG - ) + val usage = createMockPermGroupUsage(attributionTag = TEST_ATTRIBUTION_TAG) val activityInfo = createMockActivityInfo() val resolveInfo = createMockResolveInfo(activityInfo) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) - `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())) - .thenAnswer { resolveInfo } + `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer { + resolveInfo + } controller.showDialog(context) exhaustExecutors() dialogProvider.list?.let { list -> - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID) + ) + ) + .isTrue() assertThat(list.get(0).isService).isFalse() } } @Test fun testDefaultIntentOnIncorrectPermission() { - val usage = createMockPermGroupUsage( - attributionTag = TEST_ATTRIBUTION_TAG - ) + val usage = createMockPermGroupUsage(attributionTag = TEST_ATTRIBUTION_TAG) - val activityInfo = createMockActivityInfo( - permission = "INCORRECT_PERMISSION" - ) + val activityInfo = createMockActivityInfo(permission = "INCORRECT_PERMISSION") val resolveInfo = createMockResolveInfo(activityInfo) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) - `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())) - .thenAnswer { resolveInfo } + `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer { + resolveInfo + } controller.showDialog(context) exhaustExecutors() dialogProvider.list?.let { list -> - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID) + ) + ) + .isTrue() assertThat(list.get(0).isService).isFalse() } } @@ -758,15 +753,18 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { `when`(appOpsController.isMicMuted).thenReturn(false) `when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) - .thenAnswer { FakeApplicationInfo(it.getArgument(0)) } + .thenAnswer { FakeApplicationInfo(it.getArgument(0)) } `when`(privacyItemController.locationAvailable).thenReturn(true) `when`(privacyItemController.micCameraAvailable).thenReturn(true) - `when`(userTracker.userProfiles).thenReturn(listOf( - UserInfo(USER_ID, "", 0), - UserInfo(ENT_USER_ID, "", UserInfo.FLAG_MANAGED_PROFILE) - )) + `when`(userTracker.userProfiles) + .thenReturn( + listOf( + UserInfo(USER_ID, "", 0), + UserInfo(ENT_USER_ID, "", UserInfo.FLAG_MANAGED_PROFILE) + ) + ) `when`(keyguardStateController.isUnlocked).thenReturn(true) } @@ -781,9 +779,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { return user * UserHandle.PER_USER_RANGE + nextUid++ } - private fun createMockResolveInfo( - activityInfo: ActivityInfo? = null - ): ResolveInfo { + private fun createMockResolveInfo(activityInfo: ActivityInfo? = null): ResolveInfo { val resolveInfo = mock(ResolveInfo::class.java) resolveInfo.activityInfo = activityInfo return resolveInfo @@ -822,4 +818,4 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { `when`(usage.proxyLabel).thenReturn(proxyLabel) return usage } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index 6dc7a064af15..b24b8773d600 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -63,13 +63,13 @@ import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.wifi.WifiUtils; import com.android.settingslib.wifi.dpp.WifiDppIntentHelper; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.res.R; import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.LocationController; @@ -738,6 +738,44 @@ public class InternetDialogControllerTest extends SysuiTestCase { } @Test + public void onWifiScan_isWifiEnabledFalse_callbackOnWifiScanFalse() { + reset(mInternetDialogCallback); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(false); + + mInternetDialogController.onWifiScan(true); + + verify(mInternetDialogCallback).onWifiScan(false); + } + + @Test + public void onWifiScan_isDeviceLockedTrue_callbackOnWifiScanFalse() { + reset(mInternetDialogCallback); + when(mKeyguardStateController.isUnlocked()).thenReturn(false); + + mInternetDialogController.onWifiScan(true); + + verify(mInternetDialogCallback).onWifiScan(false); + } + + @Test + public void onWifiScan_onWifiScanFalse_callbackOnWifiScanFalse() { + reset(mInternetDialogCallback); + + mInternetDialogController.onWifiScan(false); + + verify(mInternetDialogCallback).onWifiScan(false); + } + + @Test + public void onWifiScan_onWifiScanTrue_callbackOnWifiScanTrue() { + reset(mInternetDialogCallback); + + mInternetDialogController.onWifiScan(true); + + verify(mInternetDialogCallback).onWifiScan(true); + } + + @Test public void setMergedCarrierWifiEnabledIfNeed_carrierProvisionsEnabled_doNothing() { when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID)) .thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index 039e58a64eb5..916bb79d97b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -6,12 +6,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -33,9 +31,9 @@ import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -48,7 +46,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; @@ -613,66 +610,21 @@ public class InternetDialogTest extends SysuiTestCase { } @Test - public void showProgressBar_wifiDisabled_hideProgressBar() { - Mockito.reset(mHandler); - when(mInternetDialogController.isWifiEnabled()).thenReturn(false); - - mInternetDialog.showProgressBar(); - - assertThat(mInternetDialog.mIsProgressBarVisible).isFalse(); - verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong()); - } - - @Test - public void showProgressBar_deviceLocked_hideProgressBar() { - Mockito.reset(mHandler); - when(mInternetDialogController.isDeviceLocked()).thenReturn(true); - - mInternetDialog.showProgressBar(); - - assertThat(mInternetDialog.mIsProgressBarVisible).isFalse(); - verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong()); - } - - @Test - public void showProgressBar_wifiEnabledWithWifiEntry_showProgressBarThenHide() { - Mockito.reset(mHandler); - when(mInternetDialogController.isWifiEnabled()).thenReturn(true); + public void onWifiScan_isScanTrue_setProgressBarVisibleTrue() { + mInternetDialog.mIsProgressBarVisible = false; - mInternetDialog.showProgressBar(); + mInternetDialog.onWifiScan(true); - // Show progress bar assertThat(mInternetDialog.mIsProgressBarVisible).isTrue(); - - ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); - verify(mHandler).postDelayed(runnableCaptor.capture(), - eq(InternetDialog.PROGRESS_DELAY_MS)); - runnableCaptor.getValue().run(); - - // Then hide progress bar - assertThat(mInternetDialog.mIsProgressBarVisible).isFalse(); } @Test - public void showProgressBar_wifiEnabledWithoutWifiEntries_showProgressBarThenHideSearch() { - Mockito.reset(mHandler); - when(mInternetDialogController.isWifiEnabled()).thenReturn(true); - mInternetDialog.mConnectedWifiEntry = null; - mInternetDialog.mWifiEntriesCount = 0; + public void onWifiScan_isScanFalse_setProgressBarVisibleFalse() { + mInternetDialog.mIsProgressBarVisible = true; - mInternetDialog.showProgressBar(); - - // Show progress bar - assertThat(mInternetDialog.mIsProgressBarVisible).isTrue(); + mInternetDialog.onWifiScan(false); - ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); - verify(mHandler).postDelayed(runnableCaptor.capture(), - eq(InternetDialog.PROGRESS_DELAY_MS)); - runnableCaptor.getValue().run(); - - // Then hide searching sub-title only - assertThat(mInternetDialog.mIsProgressBarVisible).isTrue(); - assertThat(mInternetDialog.mIsSearchingHidden).isTrue(); + assertThat(mInternetDialog.mIsProgressBarVisible).isFalse(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 657f9127dc7e..e572dcca5a34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -23,6 +23,8 @@ import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; @@ -37,8 +39,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static kotlinx.coroutines.flow.FlowKt.emptyFlow; - import android.annotation.IdRes; import android.content.ContentResolver; import android.content.res.Configuration; @@ -86,6 +86,7 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.view.LongPressHandlingView; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlagsClassic; @@ -402,6 +403,10 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mPowerInteractor = keyguardInteractorDeps.getPowerInteractor(); when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn( StateFlowKt.MutableStateFlow(false)); + DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor = + mock(DeviceEntryUdfpsInteractor.class); + when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow()); + mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), new FakeDeviceProvisioningRepository(), @@ -418,7 +423,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { new SharedNotificationContainerInteractor( new FakeConfigurationRepository(), mContext, - new ResourcesSplitShadeStateController() + new ResourcesSplitShadeStateController(), + mKeyguardInteractor, + deviceEntryUdfpsInteractor ), mShadeRepository ) 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 5ffbe65d2c50..9d8b21464585 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -23,6 +23,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -54,6 +56,7 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor 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.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -235,6 +238,11 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mKeyguardSecurityModel, mSelectedUserInteractor, powerInteractor); + + DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor = + mock(DeviceEntryUdfpsInteractor.class); + when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow()); + mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), new FakeDeviceProvisioningRepository(), @@ -251,7 +259,9 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { new SharedNotificationContainerInteractor( configurationRepository, mContext, - new ResourcesSplitShadeStateController()), + new ResourcesSplitShadeStateController(), + keyguardInteractor, + deviceEntryUdfpsInteractor), shadeRepository ) ); 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 e723d7d0367b..eb5633b70f61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; import android.content.res.Resources; @@ -41,6 +42,7 @@ import com.android.systemui.SysuiTestCase; 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.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.flags.FeatureFlags; @@ -275,6 +277,10 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { ResourcesSplitShadeStateController splitShadeStateController = new ResourcesSplitShadeStateController(); + DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor = + mock(DeviceEntryUdfpsInteractor.class); + when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow()); + mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), deviceProvisioningRepository, @@ -291,7 +297,9 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { new SharedNotificationContainerInteractor( configurationRepository, mContext, - splitShadeStateController), + splitShadeStateController, + keyguardInteractor, + deviceEntryUdfpsInteractor), mShadeRepository ) ); 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 dff91ddf559f..f25ce0aa5278 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.bouncer.data.repository.FakeKeyguardBouncerRepositor 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.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -53,6 +54,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSe import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository import com.android.systemui.util.mockito.mock +import kotlinx.coroutines.flow.emptyFlow import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -83,6 +85,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { FromPrimaryBouncerTransitionInteractor @Mock lateinit var interactionJankMonitor: InteractionJankMonitor @Mock lateinit var mockDarkAnimator: ObjectAnimator + @Mock lateinit var deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor private lateinit var controller: StatusBarStateControllerImpl private lateinit var uiEventLogger: UiEventLoggerFake @@ -164,6 +167,8 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { mock(), powerInteractor ) + + whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(emptyFlow()) shadeInteractor = ShadeInteractorImpl( testScope.backgroundScope, @@ -181,7 +186,9 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { SharedNotificationContainerInteractor( configurationRepository, mContext, - ResourcesSplitShadeStateController() + ResourcesSplitShadeStateController(), + keyguardInteractor, + deviceEntryUdfpsInteractor, ), shadeRepository, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt index 3fef1d9832f0..5bc75e8b84c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt @@ -148,6 +148,24 @@ class AccessPointControllerImplTest : SysuiTestCase() { } @Test + fun onWifiEntriesChanged_reasonIsScanResults_fireWifiScanCallbackFalse() { + controller.addAccessPointCallback(callback) + + controller.onWifiEntriesChanged(WifiPickerTracker.WIFI_ENTRIES_CHANGED_REASON_SCAN_RESULTS) + + verify(callback).onWifiScan(false) + } + + @Test + fun onScanRequested_fireWifiScanCallbackTrue() { + controller.addAccessPointCallback(callback) + + controller.onScanRequested() + + verify(callback).onWifiScan(true) + } + + @Test fun testOnNumSavedNetworksChangedDoesntTriggerCallback() { controller.addAccessPointCallback(callback) controller.onNumSavedNetworksChanged() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index bfa03eed57a0..8cf64a5aa8fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -48,7 +48,6 @@ import android.os.SystemClock; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -56,6 +55,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.log.LogAssertKt; import com.android.systemui.statusbar.NotificationInteractionTracker; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.notification.NotifPipelineFlags; @@ -76,6 +76,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.util.time.FakeSystemClock; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -129,10 +130,6 @@ public class ShadeListBuilderTest extends SysuiTestCase { private Map<String, Integer> mNextIdMap = new ArrayMap<>(); private int mNextRank = 0; - private Log.TerribleFailureHandler mOldWtfHandler = null; - private Log.TerribleFailure mLastWtf = null; - private int mWtfCount = 0; - @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -1756,20 +1753,19 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addPreGroupFilter(filter); mListBuilder.addOnBeforeTransformGroupsListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the filter is invalidated exactly // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); // THEN an exception is NOT thrown directly, but a WTF IS logged. - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + LogAssertKt.assertLogsWtfs(() -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); } - @Test(expected = IllegalStateException.class) + @Test public void testOutOfOrderPreGroupFilterInvalidationThrowsAfterTooManyRuns() { // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage, NotifFilter filter = new PackageFilter(PACKAGE_1); @@ -1778,20 +1774,20 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addPreGroupFilter(filter); mListBuilder.addOnBeforeTransformGroupsListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the filter is invalidated more than // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + + // THEN an exception IS thrown. + addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - dispatchBuild(); - try { - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - } finally { - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - } - // THEN an exception IS thrown. + LogAssertKt.assertLogsWtfs(() -> { + Assert.assertThrows(IllegalStateException.class, () -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); + }); } @Test @@ -1803,26 +1799,30 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addPreGroupFilter(filter); mListBuilder.addOnBeforeTransformGroupsListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the filter is invalidated // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, the pipeline runs for a non-reentrant reason, // and then the filter is invalidated MAX_CONSECUTIVE_REENTRANT_REBUILDS times again, + + // THEN an exception is NOT thrown, but WTFs ARE logged. + addNotif(0, PACKAGE_2); + invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - // Note: dispatchBuild itself triggers a non-reentrant pipeline run. - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + LogAssertKt.assertLogsWtfs(() -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); - // THEN an exception is NOT thrown, but WTFs ARE logged. - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS * 2); + invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + LogAssertKt.assertLogsWtfs(() -> { + // Note: dispatchBuild itself triggers a non-reentrant pipeline run. + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); } @Test - public void testOutOfOrderPrompterInvalidationDoesNotThrowBeforeTooManyRuns() { + public void testOutOfOrderPromoterInvalidationDoesNotThrowBeforeTooManyRuns() { // GIVEN a NotifPromoter that gets invalidated during the sorting stage, NotifPromoter promoter = new IdPromoter(47); CountingInvalidator invalidator = new CountingInvalidator(promoter); @@ -1830,22 +1830,22 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addPromoter(promoter); mListBuilder.addOnBeforeSortListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the promoter is invalidated exactly // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, - addNotif(0, PACKAGE_1); - invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); // THEN an exception is NOT thrown directly, but a WTF IS logged. - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + addNotif(0, PACKAGE_1); + invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + + LogAssertKt.assertLogsWtfs(() -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); } - @Test(expected = IllegalStateException.class) - public void testOutOfOrderPrompterInvalidationThrowsAfterTooManyRuns() { + @Test + public void testOutOfOrderPromoterInvalidationThrowsAfterTooManyRuns() { // GIVEN a NotifPromoter that gets invalidated during the sorting stage, NotifPromoter promoter = new IdPromoter(47); CountingInvalidator invalidator = new CountingInvalidator(promoter); @@ -1853,20 +1853,20 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addPromoter(promoter); mListBuilder.addOnBeforeSortListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the promoter is invalidated more than // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + + // THEN an exception IS thrown. + addNotif(0, PACKAGE_1); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - dispatchBuild(); - try { - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - } finally { - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - } - // THEN an exception IS thrown. + LogAssertKt.assertLogsWtfs(() -> { + Assert.assertThrows(IllegalStateException.class, () -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); + }); } @Test @@ -1878,20 +1878,21 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.setComparators(singletonList(comparator)); mListBuilder.addOnBeforeRenderListListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the comparator is invalidated exactly // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + + // THEN an exception is NOT thrown directly, but a WTF IS logged. + addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - // THEN an exception is NOT thrown directly, but a WTF IS logged. - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + LogAssertKt.assertLogsWtfs(() -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); } - @Test(expected = IllegalStateException.class) + @Test public void testOutOfOrderComparatorInvalidationThrowsAfterTooManyRuns() { // GIVEN a NotifComparator that gets invalidated during the finalizing stage, NotifComparator comparator = new HypeComparator(PACKAGE_1); @@ -1900,16 +1901,20 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.setComparators(singletonList(comparator)); mListBuilder.addOnBeforeRenderListListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the comparator is invalidated more than // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + + // THEN an exception IS thrown. + addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - // THEN an exception IS thrown. + LogAssertKt.assertLogsWtfs(() -> { + Assert.assertThrows(IllegalStateException.class, () -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); + }); } @Test @@ -1921,20 +1926,21 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addFinalizeFilter(filter); mListBuilder.addOnBeforeRenderListListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the PreRenderFilter is invalidated exactly // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + + // THEN an exception is NOT thrown directly, but a WTF IS logged. + addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - // THEN an exception is NOT thrown directly, but a WTF IS logged. - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + LogAssertKt.assertLogsWtfs(() -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); } - @Test(expected = IllegalStateException.class) + @Test public void testOutOfOrderPreRenderFilterInvalidationThrowsAfterTooManyRuns() { // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage, NotifFilter filter = new PackageFilter(PACKAGE_1); @@ -1943,59 +1949,22 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addFinalizeFilter(filter); mListBuilder.addOnBeforeRenderListListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the PreRenderFilter is invalidated more than // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, - addNotif(0, PACKAGE_2); - invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - dispatchBuild(); - try { - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - } finally { - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - } // THEN an exception IS thrown. - } - - private void interceptWtfs() { - assertNull(mOldWtfHandler); - mLastWtf = null; - mWtfCount = 0; + addNotif(0, PACKAGE_2); + invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - mOldWtfHandler = Log.setWtfHandler((tag, e, system) -> { - Log.e("ShadeListBuilderTest", "Observed WTF: " + e); - mLastWtf = e; - mWtfCount++; + LogAssertKt.assertLogsWtfs(() -> { + Assert.assertThrows(IllegalStateException.class, () -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); }); } - private void expectNoWtfs() { - assertNull(expectWtfs(0)); - } - - private Log.TerribleFailure expectWtf() { - return expectWtfs(1); - } - - private Log.TerribleFailure expectWtfs(int expectedWtfCount) { - assertNotNull(mOldWtfHandler); - - Log.setWtfHandler(mOldWtfHandler); - mOldWtfHandler = null; - - Log.TerribleFailure wtf = mLastWtf; - int wtfCount = mWtfCount; - - mLastWtf = null; - mWtfCount = 0; - - assertEquals(expectedWtfCount, wtfCount); - return wtf; - } - @Test public void testStableOrdering() { mStabilityManager.setAllowEntryReordering(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt index a07b5705d171..327a07d6179f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt @@ -20,57 +20,86 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class SharedNotificationContainerInteractorTest : SysuiTestCase() { - private lateinit var configurationRepository: FakeConfigurationRepository - private lateinit var underTest: SharedNotificationContainerInteractor - - @Before - fun setUp() { - configurationRepository = FakeConfigurationRepository() - underTest = - SharedNotificationContainerInteractor( - configurationRepository, - mContext, - ResourcesSplitShadeStateController() - ) - } + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val configurationRepository = kosmos.fakeConfigurationRepository + private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository + private val underTest = kosmos.sharedNotificationContainerInteractor @Test - fun validateConfigValues() = runTest { - overrideResource(R.bool.config_use_split_notification_shade, true) - overrideResource(R.bool.config_use_large_screen_shade_header, false) - overrideResource(R.dimen.notification_panel_margin_horizontal, 0) - overrideResource(R.dimen.notification_panel_margin_bottom, 10) - overrideResource(R.dimen.notification_panel_margin_top, 10) - overrideResource(R.dimen.large_screen_shade_header_height, 0) - overrideResource(R.dimen.keyguard_split_shade_top_margin, 55) - - val dimens = collectLastValue(underTest.configurationBasedDimensions) - - configurationRepository.onAnyConfigurationChange() - runCurrent() - - val lastDimens = dimens()!! - - assertThat(lastDimens.useSplitShade).isTrue() - assertThat(lastDimens.useLargeScreenHeader).isFalse() - assertThat(lastDimens.marginHorizontal).isEqualTo(0) - assertThat(lastDimens.marginBottom).isGreaterThan(0) - assertThat(lastDimens.marginTop).isGreaterThan(0) - assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0) - assertThat(lastDimens.keyguardSplitShadeTopMargin).isEqualTo(55) - } + fun validateConfigValues() = + testScope.runTest { + overrideResource(R.bool.config_use_split_notification_shade, true) + overrideResource(R.bool.config_use_large_screen_shade_header, false) + overrideResource(R.dimen.notification_panel_margin_horizontal, 0) + overrideResource(R.dimen.notification_panel_margin_bottom, 10) + overrideResource(R.dimen.notification_panel_margin_top, 10) + overrideResource(R.dimen.large_screen_shade_header_height, 0) + overrideResource(R.dimen.keyguard_split_shade_top_margin, 55) + + val dimens = collectLastValue(underTest.configurationBasedDimensions) + + configurationRepository.onAnyConfigurationChange() + runCurrent() + + val lastDimens = dimens()!! + + assertThat(lastDimens.useSplitShade).isTrue() + assertThat(lastDimens.useLargeScreenHeader).isFalse() + assertThat(lastDimens.marginHorizontal).isEqualTo(0) + assertThat(lastDimens.marginBottom).isGreaterThan(0) + assertThat(lastDimens.marginTop).isGreaterThan(0) + assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0) + assertThat(lastDimens.keyguardSplitShadeTopMargin).isEqualTo(55) + } + + @Test + fun useExtraShelfSpaceIsTrueWithUdfps() = + testScope.runTest { + val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace) + + keyguardRepository.ambientIndicationVisible.value = true + fingerprintPropertyRepository.supportsUdfps() + + assertThat(useExtraShelfSpace).isEqualTo(true) + } + + @Test + fun useExtraShelfSpaceIsTrueWithRearFpsAndNoAmbientIndicationArea() = + testScope.runTest { + val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace) + + keyguardRepository.ambientIndicationVisible.value = false + fingerprintPropertyRepository.supportsRearFps() + + assertThat(useExtraShelfSpace).isEqualTo(true) + } + + @Test + fun useExtraShelfSpaceIsFalseWithRearFpsAndAmbientIndicationArea() = + testScope.runTest { + val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace) + + keyguardRepository.ambientIndicationVisible.value = true + fingerprintPropertyRepository.supportsRearFps() + + assertThat(useExtraShelfSpace).isEqualTo(false) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index f0205b3f5974..36a471238c8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -332,8 +332,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { fun maxNotificationsOnLockscreen() = testScope.runTest { var notificationCount = 10 - val maxNotifications by - collectLastValue(underTest.getMaxNotifications { notificationCount }) + val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount } + val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) showLockscreen() @@ -355,8 +355,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() = testScope.runTest { var notificationCount = 10 - val maxNotifications by - collectLastValue(underTest.getMaxNotifications { notificationCount }) + val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount } + val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) showLockscreen() @@ -390,7 +390,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun maxNotificationsOnShade() = testScope.runTest { - val maxNotifications by collectLastValue(underTest.getMaxNotifications { 10 }) + val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> 10 } + val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) // Show lockscreen with shade expanded showLockscreenWithShadeExpanded() 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 5b9b390eea2d..b217195000b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -29,6 +29,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -97,6 +99,7 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor 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.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FakeFeatureFlagsClassic; @@ -465,6 +468,10 @@ public class BubblesTest extends SysuiTestCase { ResourcesSplitShadeStateController splitShadeStateController = new ResourcesSplitShadeStateController(); + DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor = + mock(DeviceEntryUdfpsInteractor.class); + when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow()); + mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), @@ -481,7 +488,9 @@ public class BubblesTest extends SysuiTestCase { new SharedNotificationContainerInteractor( configurationRepository, mContext, - splitShadeStateController), + splitShadeStateController, + keyguardInteractor, + deviceEntryUdfpsInteractor), shadeRepository ) ); diff --git a/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt new file mode 100644 index 000000000000..b88f302cdfdd --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt @@ -0,0 +1,35 @@ +/* + * 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.graphics.drawable + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.PixelFormat + +/** + * Stub drawable that does nothing. It's to be used in tests as a mock drawable and checked for the + * same instance + */ +class TestStubDrawable : Drawable() { + + override fun draw(canvas: Canvas) = Unit + override fun setAlpha(alpha: Int) = Unit + override fun setColorFilter(colorFilter: ColorFilter?) = Unit + override fun getOpacity(): Int = PixelFormat.UNKNOWN + + override fun equals(other: Any?): Boolean = this === other +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 0e7c6625264c..c5d745a65e96 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -121,6 +121,8 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null) + override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false) + override fun setQuickSettingsVisible(isVisible: Boolean) { _isQuickSettingsVisible.value = isVisible } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt index 10f9346aba14..6ccb3bc2812e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt @@ -59,6 +59,17 @@ fun assertLogsWtf( ): TerribleFailureLog = assertLogsWtf(message = message, allowMultiple = allowMultiple) { loggingRunnable.run() } +fun assertLogsWtfs( + message: String = "Expected Log.wtf to be called once or more", + loggingBlock: () -> Unit, +): TerribleFailureLog = assertLogsWtf(message, allowMultiple = true, loggingBlock) + +@JvmOverloads +fun assertLogsWtfs( + message: String = "Expected Log.wtf to be called once or more", + loggingRunnable: Runnable, +): TerribleFailureLog = assertLogsWtfs(message) { loggingRunnable.run() } + /** The data passed to [TerribleFailureHandler.onTerribleFailure] */ data class TerribleFailureLog( val tag: String, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt index 1cb2587e4e99..6332c1a8010d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt @@ -19,9 +19,14 @@ package com.android.systemui.qs import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.InstanceIdSequenceFake import com.android.systemui.kosmos.Kosmos +import com.android.systemui.plugins.qs.QSFactory +import com.android.systemui.qs.tiles.di.NewQSTileFactory val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by Kosmos.Fixture { InstanceIdSequenceFake(0) } val Kosmos.uiEventLogger: UiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() } val Kosmos.qsEventLogger: QsEventLoggerFake by Kosmos.Fixture { QsEventLoggerFake(uiEventLogger, instanceIdSequenceFake) } + +var Kosmos.newQSTileFactory by Kosmos.Fixture<NewQSTileFactory>() +var Kosmos.qsTileFactory by Kosmos.Fixture<QSFactory>() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt new file mode 100644 index 000000000000..f01e3aaa7089 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.qs.external + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.customTileStatePersister: CustomTileStatePersister by + Kosmos.Fixture { fakeCustomTileStatePersister } +val Kosmos.fakeCustomTileStatePersister by Kosmos.Fixture { FakeCustomTileStatePersister() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt new file mode 100644 index 000000000000..f8ce707b0bb2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt @@ -0,0 +1,24 @@ +/* + * 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.qs.external + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +/** Returns mocks */ +var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by + Kosmos.Fixture { TileLifecycleManager.Factory { _, _ -> mock<TileLifecycleManager>() } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt new file mode 100644 index 000000000000..d93dd8d42721 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt @@ -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 com.android.systemui.qs.pipeline.data.model + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor + +val Kosmos.workTileRestoreProcessor by Kosmos.Fixture { WorkTileRestoreProcessor() } + +var Kosmos.restoreProcessors by + Kosmos.Fixture { + setOf( + workTileRestoreProcessor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt new file mode 100644 index 000000000000..009148266143 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt @@ -0,0 +1,38 @@ +/* + * 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.qs.pipeline.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeTileSpecRepository by Kosmos.Fixture { FakeTileSpecRepository() } +var Kosmos.tileSpecRepository: TileSpecRepository by Kosmos.Fixture { fakeTileSpecRepository } + +val Kosmos.fakeAutoAddRepository by Kosmos.Fixture { FakeAutoAddRepository() } +var Kosmos.autoAddRepository: AutoAddRepository by Kosmos.Fixture { fakeAutoAddRepository } + +val Kosmos.fakeRestoreRepository by Kosmos.Fixture { FakeQSSettingsRestoredRepository() } +var Kosmos.restoreRepository: QSSettingsRestoredRepository by + Kosmos.Fixture { fakeRestoreRepository } + +val Kosmos.fakeInstalledTilesRepository by + Kosmos.Fixture { FakeInstalledTilesComponentRepository() } +var Kosmos.installedTilesRepository: InstalledTilesComponentRepository by + Kosmos.Fixture { fakeInstalledTilesRepository } + +val Kosmos.fakeCustomTileAddedRepository by Kosmos.Fixture { FakeCustomTileAddedRepository() } +var Kosmos.customTileAddedRepository: CustomTileAddedRepository by + Kosmos.Fixture { fakeCustomTileAddedRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt new file mode 100644 index 000000000000..35f178b62a08 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt @@ -0,0 +1,26 @@ +/* + * 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.qs.pipeline.domain.autoaddable + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.pipeline.data.model.workTileRestoreProcessor +import com.android.systemui.settings.userTracker + +val Kosmos.workTileAutoAddable by + Kosmos.Fixture { WorkTileAutoAddable(userTracker, workTileRestoreProcessor) } + +var Kosmos.autoAddables by Kosmos.Fixture { setOf(workTileAutoAddable) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt index ebdd6fd7aac0..bf8f4da34d5b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt @@ -39,14 +39,18 @@ class FakeAutoAddable( return getFlow(userId).asStateFlow().filterNotNull() } - suspend fun sendRemoveSignal(userId: Int) { + fun sendRemoveSignal(userId: Int) { getFlow(userId).value = AutoAddSignal.Remove(spec) } - suspend fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) { + fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) { getFlow(userId).value = AutoAddSignal.Add(spec, position) } + fun sendRemoveTrackingSignal(userId: Int) { + getFlow(userId).value = AutoAddSignal.RemoveTracking(spec) + } + override val description: String get() = "FakeAutoAddable($spec)" } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt new file mode 100644 index 000000000000..5e8471c5575b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt @@ -0,0 +1,35 @@ +/* + * 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.qs.pipeline.domain.interactor + +import com.android.systemui.dump.dumpManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.qs.pipeline.data.repository.autoAddRepository +import com.android.systemui.qs.pipeline.domain.autoaddable.autoAddables +import com.android.systemui.qs.pipeline.shared.logging.qsLogger + +val Kosmos.autoAddInteractor by + Kosmos.Fixture { + AutoAddInteractor( + autoAddables, + autoAddRepository, + dumpManager, + qsLogger, + applicationCoroutineScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt new file mode 100644 index 000000000000..67df563ec5b0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt @@ -0,0 +1,52 @@ +/* + * 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.qs.pipeline.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.qs.external.customTileStatePersister +import com.android.systemui.qs.external.tileLifecycleManagerFactory +import com.android.systemui.qs.newQSTileFactory +import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository +import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository +import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository +import com.android.systemui.qs.pipeline.shared.logging.qsLogger +import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository +import com.android.systemui.qs.qsTileFactory +import com.android.systemui.settings.userTracker +import com.android.systemui.user.data.repository.userRepository + +val Kosmos.currentTilesInteractor: CurrentTilesInteractor by + Kosmos.Fixture { + CurrentTilesInteractorImpl( + tileSpecRepository, + installedTilesRepository, + userRepository, + customTileStatePersister, + { newQSTileFactory }, + qsTileFactory, + customTileAddedRepository, + tileLifecycleManagerFactory, + userTracker, + testDispatcher, + testDispatcher, + applicationCoroutineScope, + qsLogger, + pipelineFlagsRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt new file mode 100644 index 000000000000..55c23d41e548 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt @@ -0,0 +1,39 @@ +/* + * 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.qs.pipeline.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.qs.pipeline.data.model.restoreProcessors +import com.android.systemui.qs.pipeline.data.repository.autoAddRepository +import com.android.systemui.qs.pipeline.data.repository.restoreRepository +import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository +import com.android.systemui.qs.pipeline.shared.logging.qsLogger + +val Kosmos.restoreReconciliationInteractor by + Kosmos.Fixture { + RestoreReconciliationInteractor( + tileSpecRepository, + autoAddRepository, + restoreRepository, + restoreProcessors, + qsLogger, + applicationCoroutineScope, + testDispatcher, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt new file mode 100644 index 000000000000..961545aba4e8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * 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.qs.pipeline.shared + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.pipelineFlagsRepository by Kosmos.Fixture { QSPipelineFlagsRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt new file mode 100644 index 000000000000..7d52f5d8aa34 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.qs.pipeline.shared.logging + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +/** mock */ +var Kosmos.qsLogger: QSPipelineLogger by Kosmos.Fixture { mock<QSPipelineLogger>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt index 7494ccf32a2c..2ca338a3af9c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt @@ -72,6 +72,7 @@ class FakeUserTracker( onBeforeUserSwitching() onUserChanging() onUserChanged() + onProfileChanged() } fun onBeforeUserSwitching(userId: Int = _userId) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt new file mode 100644 index 000000000000..ffa86ff03ab1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.settings + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeUserTracker by Kosmos.Fixture { FakeUserTracker() } +var Kosmos.userTracker: UserTracker by Kosmos.Fixture { fakeUserTracker } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt index c8013ef96fa7..862e52d7703f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt @@ -10,12 +10,12 @@ class FakeSmartspaceRepository( override val isSmartspaceRemoteViewsEnabled = smartspaceRemoteViewsEnabled - private val _lockscreenSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> = + private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> = MutableStateFlow(emptyList()) - override val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> = - _lockscreenSmartspaceTargets + override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> = + _communalSmartspaceTargets - fun setLockscreenSmartspaceTargets(targets: List<SmartspaceTarget>) { - _lockscreenSmartspaceTargets.value = targets + fun setCommunalSmartspaceTargets(targets: List<SmartspaceTarget>) { + _communalSmartspaceTargets.value = targets } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt index 3403227f6d27..13d577bde711 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import android.content.applicationContext import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.policy.splitShadeStateController @@ -27,5 +29,7 @@ val Kosmos.sharedNotificationContainerInteractor by configurationRepository = configurationRepository, context = applicationContext, splitShadeStateController = splitShadeStateController, + keyguardInteractor = keyguardInteractor, + deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, ) } diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index b315f4a0f0c5..7fcef9c90812 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -76,6 +76,7 @@ import android.util.Size; import android.view.Surface; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl; import androidx.camera.extensions.impl.AutoPreviewExtenderImpl; import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl; @@ -94,6 +95,7 @@ import androidx.camera.extensions.impl.PreviewExtenderImpl; import androidx.camera.extensions.impl.PreviewExtenderImpl.ProcessorType; import androidx.camera.extensions.impl.PreviewImageProcessorImpl; import androidx.camera.extensions.impl.ProcessResultImpl; +import androidx.camera.extensions.impl.ProcessorImpl; import androidx.camera.extensions.impl.RequestUpdateProcessorImpl; import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl; import androidx.camera.extensions.impl.advanced.AutoAdvancedExtenderImpl; @@ -101,6 +103,7 @@ import androidx.camera.extensions.impl.advanced.BeautyAdvancedExtenderImpl; import androidx.camera.extensions.impl.advanced.BokehAdvancedExtenderImpl; import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImpl; import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl; +import androidx.camera.extensions.impl.advanced.EyesFreeVideographyAdvancedExtenderImpl; import androidx.camera.extensions.impl.advanced.HdrAdvancedExtenderImpl; import androidx.camera.extensions.impl.advanced.ImageProcessorImpl; import androidx.camera.extensions.impl.advanced.ImageReaderOutputConfigImpl; @@ -112,6 +115,8 @@ import androidx.camera.extensions.impl.advanced.RequestProcessorImpl; import androidx.camera.extensions.impl.advanced.SessionProcessorImpl; import androidx.camera.extensions.impl.advanced.SurfaceOutputConfigImpl; +import com.android.internal.camera.flags.Flags; + import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -135,22 +140,28 @@ public class CameraExtensionsProxyService extends Service { private static final String RESULTS_VERSION_PREFIX = "1.3"; // Support for various latency improvements private static final String LATENCY_VERSION_PREFIX = "1.4"; - private static final String[] ADVANCED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX, - ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX }; - private static final String[] SUPPORTED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX, - RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1", NON_INIT_VERSION_PREFIX}; + private static final String EFV_VERSION_PREFIX = "1.5"; + private static final String[] ADVANCED_VERSION_PREFIXES = {EFV_VERSION_PREFIX, + LATENCY_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX }; + private static final String[] SUPPORTED_VERSION_PREFIXES = {EFV_VERSION_PREFIX, + LATENCY_VERSION_PREFIX, RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1", + NON_INIT_VERSION_PREFIX}; private static final boolean EXTENSIONS_PRESENT = checkForExtensions(); private static final String EXTENSIONS_VERSION = EXTENSIONS_PRESENT ? (new ExtensionVersionImpl()).checkApiVersion(LATEST_VERSION) : null; private static final boolean ESTIMATED_LATENCY_API_SUPPORTED = checkForLatencyAPI(); private static final boolean LATENCY_IMPROVEMENTS_SUPPORTED = EXTENSIONS_PRESENT && - (EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX)); + (EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX) || + (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX))); + private static final boolean EFV_SUPPORTED = EXTENSIONS_PRESENT && + (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX)); private static final boolean ADVANCED_API_SUPPORTED = checkForAdvancedAPI(); private static final boolean INIT_API_SUPPORTED = EXTENSIONS_PRESENT && (!EXTENSIONS_VERSION.startsWith(NON_INIT_VERSION_PREFIX)); private static final boolean RESULT_API_SUPPORTED = EXTENSIONS_PRESENT && (EXTENSIONS_VERSION.startsWith(RESULTS_VERSION_PREFIX) || - EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX)); + EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX) || + EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX)); private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>(); private CameraManager mCameraManager; @@ -509,6 +520,167 @@ public class CameraExtensionsProxyService extends Service { */ public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension( int extensionType) { + if (Flags.concertMode()) { + if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) { + // Basic extensions are deprecated starting with extension version 1.5 + return new Pair<>(new PreviewExtenderImpl() { + @Override + public boolean isExtensionAvailable(String cameraId, + CameraCharacteristics cameraCharacteristics) { + return false; + } + + @Override + public void init(String cameraId, CameraCharacteristics cameraCharacteristics) { + + } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl getCaptureStage() { + return null; + } + + @Override + public ProcessorType getProcessorType() { + return null; + } + + @Override + public ProcessorImpl getProcessor() { + return null; + } + + @Nullable + @Override + public List<Pair<Integer, Size[]>> getSupportedResolutions() { + return null; + } + + @Override + public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics, + Context context) { } + + @Override + public void onDeInit() { } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() { + return null; + } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() { + return null; + } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() { + return null; + } + + @Override + public int onSessionType() { + return 0; + } + }, new ImageCaptureExtenderImpl() { + @Override + public boolean isExtensionAvailable(String cameraId, + CameraCharacteristics cameraCharacteristics) { + return false; + } + + @Override + public void init(String cameraId, + CameraCharacteristics cameraCharacteristics) { } + + @Override + public CaptureProcessorImpl getCaptureProcessor() { + return null; + } + + @Override + public + List<androidx.camera.extensions.impl.CaptureStageImpl> getCaptureStages() { + return null; + } + + @Override + public int getMaxCaptureStage() { + return 0; + } + + @Override + public List<Pair<Integer, Size[]>> getSupportedResolutions() { + return null; + } + + @Override + public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions( + Size captureSize) { + return null; + } + + @Override + public Range<Long> getEstimatedCaptureLatencyRange( + Size captureOutputSize) { + return null; + } + + @Override + public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() { + return null; + } + + @Override + public List<CaptureResult.Key> getAvailableCaptureResultKeys() { + return null; + } + + @Override + public boolean isCaptureProcessProgressAvailable() { + return false; + } + + @Override + public Pair<Long, Long> getRealtimeCaptureLatency() { + return null; + } + + @Override + public boolean isPostviewAvailable() { + return false; + } + + @Override + public void onInit(String cameraId, + CameraCharacteristics cameraCharacteristics, Context context) { } + + @Override + public void onDeInit() { } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() { + return null; + } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() { + return null; + } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() { + return null; + } + + @Override + public int onSessionType() { + return 0; + } + }); + } + } + switch (extensionType) { case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC: return new Pair<>(new AutoPreviewExtenderImpl(), @@ -533,6 +705,82 @@ public class CameraExtensionsProxyService extends Service { * @hide */ public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) { + if (Flags.concertMode()) { + if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) { + if (EFV_SUPPORTED) { + return new EyesFreeVideographyAdvancedExtenderImpl(); + } else { + return new AdvancedExtenderImpl() { + @Override + public boolean isExtensionAvailable(String cameraId, + Map<String, CameraCharacteristics> characteristicsMap) { + return false; + } + + @Override + public void init(String cameraId, + Map<String, CameraCharacteristics> characteristicsMap) { + + } + + @Override + public Range<Long> getEstimatedCaptureLatencyRange(String cameraId, + Size captureOutputSize, int imageFormat) { + return null; + } + + @Override + public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions( + String cameraId) { + return null; + } + + @Override + public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions( + String cameraId) { + return null; + } + + @Override + public Map<Integer, List<Size>> getSupportedPostviewResolutions( + Size captureSize) { + return null; + } + + @Override + public List<Size> getSupportedYuvAnalysisResolutions(String cameraId) { + return null; + } + + @Override + public SessionProcessorImpl createSessionProcessor() { + return null; + } + + @Override + public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() { + return null; + } + + @Override + public List<CaptureResult.Key> getAvailableCaptureResultKeys() { + return null; + } + + @Override + public boolean isCaptureProcessProgressAvailable() { + return false; + } + + @Override + public boolean isPostviewAvailable() { + return false; + } + }; + } + } + } + switch (extensionType) { case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC: return new AutoAdvancedExtenderImpl(); diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index d175713eb92f..513c09587026 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -16,6 +16,8 @@ package android.platform.test.ravenwood; +import static org.junit.Assert.fail; + import android.platform.test.annotations.IgnoreUnderRavenwood; import org.junit.Assume; @@ -36,6 +38,15 @@ public class RavenwoodRule implements TestRule { private static final boolean IS_UNDER_RAVENWOOD = RavenwoodRuleImpl.isUnderRavenwood(); + /** + * When probing is enabled, all tests will be unconditionally run under Ravenwood to detect + * cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}. + * + * This is typically helpful for internal maintainers discovering tests that had previously + * been ignored, but now have enough Ravenwood-supported functionality to be enabled. + */ + private static final boolean ENABLE_PROBE_IGNORED = false; // DO NOT SUBMIT WITH TRUE + private static final int SYSTEM_UID = 1000; private static final int NOBODY_UID = 9999; private static final int FIRST_APPLICATION_UID = 10000; @@ -97,26 +108,76 @@ public class RavenwoodRule implements TestRule { return IS_UNDER_RAVENWOOD; } + /** + * Test if the given {@link Description} has been marked with an {@link IgnoreUnderRavenwood} + * annotation, either at the method or class level. + */ + private static boolean hasIgnoreUnderRavenwoodAnnotation(Description description) { + if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) { + return true; + } else if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) { + return true; + } else { + return false; + } + } + @Override public Statement apply(Statement base, Description description) { + if (ENABLE_PROBE_IGNORED) { + return applyProbeIgnored(base, description); + } else { + return applyDefault(base, description); + } + } + + /** + * Run the given {@link Statement} with no special treatment. + */ + private Statement applyDefault(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { - if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) { - Assume.assumeFalse(IS_UNDER_RAVENWOOD); - } - if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) { + if (hasIgnoreUnderRavenwoodAnnotation(description)) { Assume.assumeFalse(IS_UNDER_RAVENWOOD); } - if (IS_UNDER_RAVENWOOD) { - RavenwoodRuleImpl.init(RavenwoodRule.this); - } + + RavenwoodRuleImpl.init(RavenwoodRule.this); try { base.evaluate(); } finally { - if (IS_UNDER_RAVENWOOD) { - RavenwoodRuleImpl.reset(RavenwoodRule.this); + RavenwoodRuleImpl.reset(RavenwoodRule.this); + } + } + }; + } + + /** + * Run the given {@link Statement} with probing enabled. All tests will be unconditionally + * run under Ravenwood to detect cases where a test is able to pass despite being marked as + * {@code IgnoreUnderRavenwood}. + */ + private Statement applyProbeIgnored(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + RavenwoodRuleImpl.init(RavenwoodRule.this); + try { + base.evaluate(); + } catch (Throwable t) { + if (hasIgnoreUnderRavenwoodAnnotation(description)) { + // This failure is expected, so eat the exception and report the + // assumption failure that test authors expect + Assume.assumeFalse(IS_UNDER_RAVENWOOD); } + throw t; + } finally { + RavenwoodRuleImpl.reset(RavenwoodRule.this); + } + + if (hasIgnoreUnderRavenwoodAnnotation(description) && IS_UNDER_RAVENWOOD) { + fail("Test was annotated with IgnoreUnderRavenwood, but it actually " + + "passed under Ravenwood; consider removing the annotation"); } } }; diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java index fb71e9d1ac6f..0ff6a1ad846b 100644 --- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java +++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java @@ -22,12 +22,10 @@ public class RavenwoodRuleImpl { } public static void init(RavenwoodRule rule) { - // Must be provided by impl to reference runtime internals - throw new UnsupportedOperationException(); + // No-op when running on a real device } public static void reset(RavenwoodRule rule) { - // Must be provided by impl to reference runtime internals - throw new UnsupportedOperationException(); + // No-op when running on a real device } } diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt index 9fcabd67f9dd..13908f1732e1 100644 --- a/ravenwood/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/ravenwood-annotation-allowed-classes.txt @@ -1,8 +1,16 @@ # Only classes listed here can use the Ravenwood annotations. com.android.internal.util.ArrayUtils +com.android.internal.os.BatteryStatsHistory +com.android.internal.os.BatteryStatsHistory$TraceDelegate +com.android.internal.os.BatteryStatsHistory$VarintParceler +com.android.internal.os.BatteryStatsHistoryIterator +com.android.internal.os.Clock com.android.internal.os.LongArrayMultiStateCounter com.android.internal.os.LongArrayMultiStateCounter$LongArrayContainer +com.android.internal.os.MonotonicClock +com.android.internal.os.PowerStats +com.android.internal.os.PowerStats$Descriptor android.util.AtomicFile android.util.DataUnit @@ -25,10 +33,15 @@ android.util.TimeUtils android.util.Xml android.os.BatteryConsumer +android.os.BatteryStats$HistoryItem +android.os.BatteryStats$HistoryStepDetails +android.os.BatteryStats$HistoryTag +android.os.BatteryStats$ProcessStateChange android.os.Binder android.os.Binder$IdentitySupplier android.os.Broadcaster android.os.BundleMerger +android.os.ConditionVariable android.os.FileUtils android.os.FileUtils$MemoryPipe android.os.Handler diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 0696807b3c8c..97d36d443620 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -209,6 +209,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final ComponentName mComponentName; int mGenericMotionEventSources; + int mObservedMotionEventSources; // the events pending events to be dispatched to this service final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>(); @@ -397,6 +398,19 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mNotificationTimeout = info.notificationTimeout; mIsDefault = (info.flags & DEFAULT) != 0; mGenericMotionEventSources = info.getMotionEventSources(); + if (android.view.accessibility.Flags.motionEventObserving()) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING) + == PackageManager.PERMISSION_GRANTED) { + mObservedMotionEventSources = info.getObservedMotionEventSources(); + } else { + Slog.e( + LOG_TAG, + "Observing motion events requires" + + " android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING."); + mObservedMotionEventSources = 0; + } + } if (supportsFlagForNotImportantViews(info)) { if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) { @@ -1599,7 +1613,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int displayId = displays[i].getDisplayId(); onDisplayRemoved(displayId); } - if (Flags.cleanupA11yOverlays()) { + if (com.android.server.accessibility.Flags.cleanupA11yOverlays()) { detachAllOverlays(); } } @@ -1919,6 +1933,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return (mGenericMotionEventSources & eventSourceWithoutClass) != 0; } + /** * Called by the invocation handler to notify the service that the * state of magnification has changed. diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 6cac6a47c77b..9ddc35ae240b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -198,6 +198,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo // State tracking for generic MotionEvents is display-agnostic so we only need one. private GenericMotionEventStreamState mGenericMotionEventStreamState; private int mCombinedGenericMotionEventSources = 0; + private int mCombinedMotionEventObservedSources = 0; private EventStreamState mKeyboardStreamState; @@ -525,16 +526,33 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } if ((mEnabledFeatures & FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS) != 0) { - addFirstEventHandler(displayId, new BaseEventStreamTransformation() { - @Override - public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, - int policyFlags) { - if (!anyServiceWantsGenericMotionEvent(rawEvent) - || !mAms.sendMotionEventToListeningServices(rawEvent)) { - super.onMotionEvent(event, rawEvent, policyFlags); - } - } - }); + addFirstEventHandler( + displayId, + new BaseEventStreamTransformation() { + @Override + public void onMotionEvent( + MotionEvent event, MotionEvent rawEvent, int policyFlags) { + boolean passAlongEvent = true; + if (anyServiceWantsGenericMotionEvent(event)) { + // Some service wants this event, so try to deliver it to at least + // one service. + if (mAms.sendMotionEventToListeningServices(event)) { + // A service accepted this event, so prevent it from passing + // down the stream by default. + passAlongEvent = false; + } + // However, if a service is observing these events instead of + // consuming them then ensure + // it is always passed along to the next stage of the event stream. + if (anyServiceWantsToObserveMotionEvent(event)) { + passAlongEvent = true; + } + } + if (passAlongEvent) { + super.onMotionEvent(event, rawEvent, policyFlags); + } + } + }); } if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 @@ -542,15 +560,14 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP) != 0) || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { final MagnificationGestureHandler magnificationGestureHandler = - createMagnificationGestureHandler(displayId, - displayContext); + createMagnificationGestureHandler(displayId, displayContext); addFirstEventHandler(displayId, magnificationGestureHandler); mMagnificationGestureHandler.put(displayId, magnificationGestureHandler); } if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { - MotionEventInjector injector = new MotionEventInjector( - mContext.getMainLooper(), mAms.getTraceManager()); + MotionEventInjector injector = + new MotionEventInjector(mContext.getMainLooper(), mAms.getTraceManager()); addFirstEventHandler(displayId, injector); mMotionEventInjectors.put(displayId, injector); } @@ -923,6 +940,20 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } } + private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) { + // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing + // touch exploration. + if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN) + && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { + return false; + } + final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK; + return (mCombinedGenericMotionEventSources + & mCombinedMotionEventObservedSources + & eventSourceWithoutClass) + != 0; + } + private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) { // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing // touch exploration. @@ -938,6 +969,10 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mCombinedGenericMotionEventSources = sources; } + public void setCombinedMotionEventObservedSources(int sources) { + mCombinedMotionEventObservedSources = sources; + } + /** * Keeps state of streams of events from all keyboard devices. */ diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 440e99632c86..edb41639514b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2825,8 +2825,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS; } int combinedGenericMotionEventSources = 0; + int combinedMotionEventObservedSources = 0; for (AccessibilityServiceConnection connection : userState.mBoundServices) { combinedGenericMotionEventSources |= connection.mGenericMotionEventSources; + combinedMotionEventObservedSources |= connection.mObservedMotionEventSources; } if (combinedGenericMotionEventSources != 0) { flags |= AccessibilityInputFilter.FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS; @@ -2845,6 +2847,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags); mInputFilter.setCombinedGenericMotionEventSources( combinedGenericMotionEventSources); + mInputFilter.setCombinedMotionEventObservedSources( + combinedMotionEventObservedSources); } else { if (mHasInputFilter) { mHasInputFilter = false; diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 34787a390d48..145303df7b0b 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -554,6 +554,10 @@ final class ContentCapturePerUserService if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service"); return; } + if (mRemoteService.getServiceInterface() == null) { + if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): remote service is dead or unbound"); + return; + } final ActivityEvent event = new ActivityEvent(activityId, componentName, type); if (mMaster.verbose) Slog.v(mTag, "onActivityEvent(): " + event); diff --git a/services/core/Android.bp b/services/core/Android.bp index 20a3b9ada85d..a0ccbf3acf0a 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -154,6 +154,7 @@ java_library_static { static_libs: [ "android.frameworks.location.altitude-V1-java", // AIDL + "android.frameworks.vibrator-V1-java", // AIDL "android.hardware.authsecret-V1.0-java", "android.hardware.authsecret-V1-java", "android.hardware.boot-V1.0-java", // HIDL @@ -165,7 +166,7 @@ java_library_static { "android.hardware.health-V1.0-java", // HIDL "android.hardware.health-V2.0-java", // HIDL "android.hardware.health-V2.1-java", // HIDL - "android.hardware.health-V2-java", // AIDL + "android.hardware.health-V3-java", // AIDL "android.hardware.health-translate-java", "android.hardware.light-V1-java", "android.hardware.security.rkp-V3-java", diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index f8f3d82556fa..ace2cfd8a307 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -33,6 +33,7 @@ import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.RequiresNoPermission; import android.annotation.SuppressLint; +import android.app.AlarmManager; import android.app.StatsManager; import android.app.usage.NetworkStatsManager; import android.bluetooth.BluetoothActivityEnergyInfo; @@ -427,13 +428,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig); mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig, mStats.getHistory()); - final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger( - com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration); - final long powerStatsAggregationPeriod = context.getResources().getInteger( - com.android.internal.R.integer.config_powerStatsAggregationPeriod); - mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, - aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore, - Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats); + mPowerStatsScheduler = createPowerStatsScheduler(mContext); PowerStatsExporter powerStatsExporter = new PowerStatsExporter(mPowerStatsStore, mPowerStatsAggregator); mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, @@ -445,6 +440,23 @@ public final class BatteryStatsService extends IBatteryStats.Stub mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config")); } + private PowerStatsScheduler createPowerStatsScheduler(Context context) { + final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger( + com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration); + final long powerStatsAggregationPeriod = context.getResources().getInteger( + com.android.internal.R.integer.config_powerStatsAggregationPeriod); + PowerStatsScheduler.AlarmScheduler alarmScheduler = + (triggerAtMillis, tag, onAlarmListener, aHandler) -> { + AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); + alarmManager.set(AlarmManager.ELAPSED_REALTIME, triggerAtMillis, tag, + onAlarmListener, aHandler); + }; + return new PowerStatsScheduler(mStats::schedulePowerStatsSampleCollection, + mPowerStatsAggregator, aggregatedPowerStatsSpanDuration, + powerStatsAggregationPeriod, mPowerStatsStore, alarmScheduler, Clock.SYSTEM_CLOCK, + mMonotonicClock, () -> mStats.getHistory().getStartTime(), mHandler); + } + private AggregatedPowerStatsConfig getAggregatedPowerStatsConfig() { AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU) diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 903cb7bcfaed..982076dd7c9a 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -30,7 +30,6 @@ import android.widget.WidgetFlags; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import java.util.ArrayList; import java.util.HashMap; @@ -164,12 +163,6 @@ final class CoreSettingsObserver extends ContentObserver { WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT)); sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>( - DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION, - SystemUiDeviceConfigFlags.KEY_REMOTEVIEWS_ADAPTER_CONVERSION, boolean.class, - SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT)); - - sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>( TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU, TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class, TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT)); diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index b1825380bbdc..32d5cf587e0c 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -152,7 +152,6 @@ public final class GameManagerService extends IGameManagerService.Stub { private static final String GAME_MODE_INTERVENTION_LIST_FILE_NAME = "game_mode_intervention.list"; - private final Context mContext; private final Object mLock = new Object(); private final Object mDeviceConfigLock = new Object(); @@ -184,6 +183,7 @@ public final class GameManagerService extends IGameManagerService.Stub { @GuardedBy("mUidObserverLock") private final Set<Integer> mForegroundGameUids = new HashSet<>(); private final GameManagerServiceSystemPropertiesWrapper mSysProps; + private float mGameDefaultFrameRateValue; @VisibleForTesting static class Injector { @@ -1559,6 +1559,10 @@ public final class GameManagerService extends IGameManagerService.Stub { mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false); Slog.v(TAG, "Game power mode OFF (game manager service start/restart)"); mPowerManagerInternal.setPowerMode(Mode.GAME, false); + + mGameDefaultFrameRateValue = (float) mSysProps.getInt( + PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE, 60); + Slog.v(TAG, "Game Default Frame Rate : " + mGameDefaultFrameRateValue); } private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) { @@ -2217,8 +2221,7 @@ public final class GameManagerService extends IGameManagerService.Stub { } if (gameDefaultFrameRate()) { gameDefaultFrameRate = isGameDefaultFrameRateEnabled - ? (float) mSysProps.getInt( - PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE, 0) : 0.0f; + ? mGameDefaultFrameRateValue : 0.0f; } return gameDefaultFrameRate; } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 44cb1367928d..290bb7e92c69 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -5106,7 +5106,7 @@ public class AudioService extends IAudioService.Stub private void setMasterMuteInternalNoCallerCheck( boolean mute, int flags, int userId, String eventSource) { if (DEBUG_VOL) { - Log.d(TAG, TextUtils.formatSimple("Master mute %s, %d, user=%d from %s", + Log.d(TAG, TextUtils.formatSimple("Master mute %s, flags 0x%x, userId=%d from %s", mute, flags, userId, eventSource)); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 5084b602bff2..578d9dc2aede 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -722,6 +722,7 @@ public class FaceService extends SystemService { if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) { if (virtualAt != -1) { //only virtual instance should be returned + Slog.i(TAG, "virtual hal is used"); return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt))); } else { Slog.e(TAG, "Could not find virtual interface while it is enabled"); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 5ce0c8b384ef..769554315b6e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -1057,6 +1057,7 @@ public class FingerprintService extends SystemService { if (Utils.isVirtualEnabled(getContext())) { if (virtualAt != -1) { //only virtual instance should be returned + Slog.i(TAG, "virtual hal is used"); return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt))); } else { Slog.e(TAG, "Could not find virtual interface while it is enabled"); diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java index 173c452fd8cb..850449595d74 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java @@ -330,8 +330,11 @@ import java.util.Objects; TextUtils.isEmpty(address) ? null : mBluetoothRouteController.getRouteIdForBluetoothAddress(address); - return createMediaRoute2Info( - routeId, audioDeviceInfo.getType(), audioDeviceInfo.getProductName(), address); + // We use the name from the port instead AudioDeviceInfo#getProductName because the latter + // replaces empty names with the name of the device (example: Pixel 8). In that case we want + // to derive a name ourselves from the type instead. + String deviceName = audioDeviceInfo.getPort().name(); + return createMediaRoute2Info(routeId, audioDeviceInfo.getType(), deviceName, address); } /** @@ -339,8 +342,8 @@ import java.util.Objects; * * @param routeId A route id, or null to use an id pre-defined for the given {@code type}. * @param audioDeviceInfoType The type as obtained from {@link AudioDeviceInfo#getType}. - * @param productName The product name as obtained from {@link - * AudioDeviceInfo#getProductName()}, or null to use a predefined name for the given {@code + * @param deviceName A human readable name to populate the route's {@link + * MediaRoute2Info#getName name}, or null to use a predefined name for the given {@code * type}. * @param address The type as obtained from {@link AudioDeviceInfo#getAddress()} or {@link * BluetoothDevice#getAddress()}. @@ -350,7 +353,7 @@ import java.util.Objects; private MediaRoute2Info createMediaRoute2Info( @Nullable String routeId, int audioDeviceInfoType, - @Nullable CharSequence productName, + @Nullable CharSequence deviceName, @Nullable String address) { SystemRouteInfo systemRouteInfo = AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.get(audioDeviceInfoType); @@ -359,7 +362,7 @@ import java.util.Objects; // earpiece. return null; } - CharSequence humanReadableName = productName; + CharSequence humanReadableName = deviceName; if (TextUtils.isEmpty(humanReadableName)) { humanReadableName = mContext.getResources().getText(systemRouteInfo.mNameResource); } diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index dcac8c98d19f..da017453ed8b 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -20,3 +20,17 @@ flag { description: "This flag controls the refactoring of NMS to NotificationAttentionHelper" bug: "291907312" } + +flag { + name: "cross_app_polite_notifications" + namespace: "systemui" + description: "This flag controls the cross-app effect of polite notifications" + bug: "270456865" +} + +flag { + name: "vibrate_while_unlocked" + namespace: "systemui" + description: "This flag controls the vibrate while unlocked setting of polite notifications" + bug: "270456865" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/DeletePackageAction.java b/services/core/java/com/android/server/pm/DeletePackageAction.java index 8ef6601f7684..31544d5308fb 100644 --- a/services/core/java/com/android/server/pm/DeletePackageAction.java +++ b/services/core/java/com/android/server/pm/DeletePackageAction.java @@ -16,17 +16,19 @@ package com.android.server.pm; +import android.annotation.NonNull; import android.os.UserHandle; final class DeletePackageAction { public final PackageSetting mDeletingPs; public final PackageSetting mDisabledPs; + @NonNull public final PackageRemovedInfo mRemovedInfo; public final int mFlags; public final UserHandle mUser; DeletePackageAction(PackageSetting deletingPs, PackageSetting disabledPs, - PackageRemovedInfo removedInfo, int flags, UserHandle user) { + @NonNull PackageRemovedInfo removedInfo, int flags, UserHandle user) { mDeletingPs = deletingPs; mDisabledPs = disabledPs; mRemovedInfo = removedInfo; diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 93836266d1f4..dcf921c90885 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -370,7 +370,7 @@ final class DeletePackageHelper { @GuardedBy("mPm.mInstallLock") public boolean deletePackageLIF(@NonNull String packageName, UserHandle user, boolean deleteCodeAndResources, @NonNull int[] allUserHandles, int flags, - PackageRemovedInfo outInfo, boolean writeSettings) { + @NonNull PackageRemovedInfo outInfo, boolean writeSettings) { final DeletePackageAction action; synchronized (mPm.mLock) { final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); @@ -410,8 +410,8 @@ final class DeletePackageHelper { * deleted, {@code null} otherwise. */ @Nullable - public static DeletePackageAction mayDeletePackageLocked( - PackageRemovedInfo outInfo, PackageSetting ps, @Nullable PackageSetting disabledPs, + public static DeletePackageAction mayDeletePackageLocked(@NonNull PackageRemovedInfo outInfo, + PackageSetting ps, @Nullable PackageSetting disabledPs, int flags, UserHandle user) { if (ps == null) { return null; @@ -460,12 +460,18 @@ final class DeletePackageHelper { } final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier(); - if (outInfo != null) { - // Remember which users are affected, before the installed states are modified - outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL) - ? ps.queryUsersInstalledOrHasData(allUserHandles) - : new int[]{userId}; - } + // Remember which users are affected, before the installed states are modified + outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL) + ? ps.queryUsersInstalledOrHasData(allUserHandles) + : new int[]{userId}; + outInfo.populateBroadcastUsers(ps); + outInfo.mDataRemoved = (flags & PackageManager.DELETE_KEEP_DATA) == 0; + outInfo.mRemovedPackage = ps.getPackageName(); + outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName; + outInfo.mIsStaticSharedLib = + ps.getPkg() != null && ps.getPkg().getStaticSharedLibraryName() != null; + outInfo.mIsExternal = ps.isExternalStorage(); + outInfo.mRemovedPackageVersionCode = ps.getVersionCode(); if ((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0) && userId != UserHandle.USER_ALL) { @@ -503,7 +509,8 @@ final class DeletePackageHelper { } } if (clearPackageStateAndReturn) { - mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, outInfo, flags); + mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, flags); + outInfo.mRemovedAppId = ps.getAppId(); mPm.scheduleWritePackageRestrictions(user); return; } @@ -529,12 +536,8 @@ final class DeletePackageHelper { // If the package removed had SUSPEND_APPS, unset any restrictions that might have been in // place for all affected users. - int[] affectedUserIds = (outInfo != null) ? outInfo.mRemovedUsers : null; - if (affectedUserIds == null) { - affectedUserIds = mPm.resolveUserIds(userId); - } final Computer snapshot = mPm.snapshotComputer(); - for (final int affectedUserId : affectedUserIds) { + for (final int affectedUserId : outInfo.mRemovedUsers) { if (hadSuspendAppsPermission.get(affectedUserId)) { mPm.unsuspendForSuspendingPackage(snapshot, packageName, affectedUserId); mPm.removeAllDistractingPackageRestrictions(snapshot, affectedUserId); @@ -542,24 +545,20 @@ final class DeletePackageHelper { } // Take a note whether we deleted the package for all users - if (outInfo != null) { - synchronized (mPm.mLock) { - outInfo.mRemovedForAllUsers = mPm.mPackages.get(ps.getPackageName()) == null; - } + synchronized (mPm.mLock) { + outInfo.mRemovedForAllUsers = mPm.mPackages.get(ps.getPackageName()) == null; } } @GuardedBy("mPm.mInstallLock") private void deleteInstalledPackageLIF(PackageSetting ps, boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles, - PackageRemovedInfo outInfo, boolean writeSettings) { + @NonNull PackageRemovedInfo outInfo, boolean writeSettings) { synchronized (mPm.mLock) { - if (outInfo != null) { - outInfo.mUid = ps.getAppId(); - outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList( - mPm.snapshotComputer(), ps, allUserHandles, - mPm.mSettings.getPackagesLocked()); - } + outInfo.mUid = ps.getAppId(); + outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList( + mPm.snapshotComputer(), ps, allUserHandles, + mPm.mSettings.getPackagesLocked()); } // Delete package data from internal structures and also remove data if flag is set @@ -567,7 +566,7 @@ final class DeletePackageHelper { ps, allUserHandles, outInfo, flags, writeSettings); // Delete application code and resources only for parent packages - if (deleteCodeAndResources && (outInfo != null)) { + if (deleteCodeAndResources) { outInfo.mArgs = new InstallArgs( ps.getPathString(), getAppDexInstructionSets( ps.getPrimaryCpuAbiLegacy(), ps.getSecondaryCpuAbiLegacy())); @@ -639,7 +638,7 @@ final class DeletePackageHelper { int flags = action.mFlags; final PackageSetting deletedPs = action.mDeletingPs; final PackageRemovedInfo outInfo = action.mRemovedInfo; - final boolean applyUserRestrictions = outInfo != null && (outInfo.mOrigUsers != null); + final boolean applyUserRestrictions = outInfo.mOrigUsers != null; final AndroidPackage deletedPkg = deletedPs.getPkg(); // Confirm if the system package has been updated // An updated system app can be deleted. This will also have to restore @@ -662,10 +661,8 @@ final class DeletePackageHelper { } } - if (outInfo != null) { - // Delete the updated package - outInfo.mIsRemovedPackageSystemUpdate = true; - } + // Delete the updated package + outInfo.mIsRemovedPackageSystemUpdate = true; if (disabledPs.getVersionCode() < deletedPs.getVersionCode() || disabledPs.getAppId() != deletedPs.getAppId()) { diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 83a6f10f0e2a..f1c062763183 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -4126,7 +4126,7 @@ final class InstallPackageHelper { null /* request */)) { mDeletePackageHelper.deletePackageLIF( parsedPackage.getPackageName(), null, true, - mPm.mUserManager.getUserIds(), 0, null, false); + mPm.mUserManager.getUserIds(), 0, new PackageRemovedInfo(), false); } } else if (newPkgVersionGreater || newSharedUserSetting) { // The application on /system is newer than the application on /data. diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2880f84c6445..c5b006c4c77d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3044,6 +3044,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } + @NonNull int[] resolveUserIds(int userId) { return (userId == UserHandle.USER_ALL) ? mUserManager.getUserIds() : new int[] { userId }; } diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 52b31319cc19..109d7ba1d29e 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -252,8 +252,7 @@ final class RemovePackageHelper { } } - public void clearPackageStateForUserLIF(PackageSetting ps, int userId, - PackageRemovedInfo outInfo, int flags) { + public void clearPackageStateForUserLIF(PackageSetting ps, int userId, int flags) { final AndroidPackage pkg; final SharedUserSetting sus; synchronized (mPm.mLock) { @@ -287,25 +286,12 @@ final class RemovePackageHelper { } mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), ps, pkg, sharedUserPkgs, userId); - - if (outInfo != null) { - if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { - outInfo.mDataRemoved = true; - } - outInfo.mRemovedPackage = ps.getPackageName(); - outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName; - outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null; - outInfo.mRemovedAppId = ps.getAppId(); - outInfo.mBroadcastUsers = outInfo.mRemovedUsers; - outInfo.mIsExternal = ps.isExternalStorage(); - outInfo.mRemovedPackageVersionCode = ps.getVersionCode(); - } } // Called to clean up disabled system packages public void removePackageData(final PackageSetting deletedPs, @NonNull int[] allUserHandles) { synchronized (mPm.mInstallLock) { - removePackageDataLIF(deletedPs, allUserHandles, /* outInfo= */ null, + removePackageDataLIF(deletedPs, allUserHandles, new PackageRemovedInfo(), /* flags= */ 0, /* writeSettings= */ false); } } @@ -318,20 +304,11 @@ final class RemovePackageHelper { */ @GuardedBy("mPm.mInstallLock") public void removePackageDataLIF(final PackageSetting deletedPs, @NonNull int[] allUserHandles, - PackageRemovedInfo outInfo, int flags, boolean writeSettings) { + @NonNull PackageRemovedInfo outInfo, int flags, boolean writeSettings) { String packageName = deletedPs.getPackageName(); if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs); // Retrieve object to delete permissions for shared user later on final AndroidPackage deletedPkg = deletedPs.getPkg(); - if (outInfo != null) { - outInfo.mRemovedPackage = packageName; - outInfo.mInstallerPackageName = deletedPs.getInstallSource().mInstallerPackageName; - outInfo.mIsStaticSharedLib = deletedPkg != null - && deletedPkg.getStaticSharedLibraryName() != null; - outInfo.populateBroadcastUsers(deletedPs); - outInfo.mIsExternal = deletedPs.isExternalStorage(); - outInfo.mRemovedPackageVersionCode = deletedPs.getVersionCode(); - } removePackageLI(deletedPs.getPackageName(), (flags & PackageManager.DELETE_CHATTY) != 0); if (!deletedPs.isSystem()) { @@ -355,9 +332,6 @@ final class RemovePackageHelper { mAppDataHelper.destroyAppDataLIF(resolvedPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); mAppDataHelper.destroyAppProfilesLIF(resolvedPkg.getPackageName()); - if (outInfo != null) { - outInfo.mDataRemoved = true; - } } int removedAppId = -1; @@ -373,9 +347,8 @@ final class RemovePackageHelper { mPm.mAppsFilter.removePackage(snapshot, snapshot.getPackageStateInternal(packageName)); removedAppId = mPm.mSettings.removePackageLPw(packageName); - if (outInfo != null) { - outInfo.mRemovedAppId = removedAppId; - } + outInfo.mRemovedAppId = removedAppId; + if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) { // If we don't have a disabled system package to reinstall, the package is // really gone and its permission state should be removed. @@ -403,8 +376,8 @@ final class RemovePackageHelper { mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL); }); } - } else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate - && outInfo.mRemovedUsers != null && !outInfo.mIsExternal) { + } else if (!deletedPs.isSystem() && !outInfo.mIsUpdate + && outInfo.mRemovedUsers != null && !deletedPs.isExternalStorage()) { // For non-system uninstalls with DELETE_KEEP_DATA, set the installed state to false // for affected users. This does not apply to app updates where the old apk is replaced // but the old data remains. @@ -424,7 +397,7 @@ final class RemovePackageHelper { // make sure to preserve per-user installed state if this removal was just // a downgrade of a system app to the factory package boolean installedStateChanged = false; - if (outInfo != null && outInfo.mOrigUsers != null && deletedPs.isSystem()) { + if (outInfo.mOrigUsers != null && deletedPs.isSystem()) { if (DEBUG_REMOVE) { Slog.d(TAG, "Propagating install state across downgrade"); } diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index 94495bf462f2..ec8af2ecd070 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -731,7 +731,7 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable ? PackageManager.DELETE_KEEP_DATA : 0; synchronized (mPm.mInstallLock) { mDeletePackageHelper.deletePackageLIF(pkg.getPackageName(), null, true, - mPm.mUserManager.getUserIds(), flags, null, + mPm.mUserManager.getUserIds(), flags, new PackageRemovedInfo(), true); } } diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index 70aa19ae9cef..b607502baada 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -256,13 +256,12 @@ public final class StorageEventHelper extends StorageEventListener { final AndroidPackage pkg = ps.getPkg(); final int deleteFlags = PackageManager.DELETE_KEEP_DATA; - final PackageRemovedInfo outInfo = new PackageRemovedInfo(); try (PackageFreezer freezer = mPm.freezePackageForDelete(ps.getPackageName(), UserHandle.USER_ALL, deleteFlags, "unloadPrivatePackagesInner", ApplicationExitInfo.REASON_OTHER)) { if (mDeletePackageHelper.deletePackageLIF(ps.getPackageName(), null, false, - userIds, deleteFlags, outInfo, false)) { + userIds, deleteFlags, new PackageRemovedInfo(), false)) { unloaded.add(pkg); } else { Slog.w(TAG, "Failed to unload " + ps.getPath()); diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java index abfe9debc7de..e1eb8f07dd2c 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java @@ -20,7 +20,6 @@ import android.annotation.Nullable; import android.os.ConditionVariable; import android.os.Handler; import android.os.PersistableBundle; -import android.util.FastImmutableArraySet; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -30,8 +29,9 @@ import com.android.internal.os.PowerStats; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.function.Consumer; -import java.util.stream.Stream; /** * Collects snapshots of power-related system statistics. @@ -246,8 +246,7 @@ public abstract class PowerStatsCollector { @GuardedBy("this") @SuppressWarnings("unchecked") - private volatile FastImmutableArraySet<Consumer<PowerStats>> mConsumerList = - new FastImmutableArraySet<Consumer<PowerStats>>(new Consumer[0]); + private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList(); public PowerStatsCollector(Handler handler, long throttlePeriodMs, Clock clock) { mHandler = handler; @@ -262,9 +261,13 @@ public abstract class PowerStatsCollector { @SuppressWarnings("unchecked") public void addConsumer(Consumer<PowerStats> consumer) { synchronized (this) { - mConsumerList = new FastImmutableArraySet<Consumer<PowerStats>>( - Stream.concat(mConsumerList.stream(), Stream.of(consumer)) - .toArray(Consumer[]::new)); + if (mConsumerList.contains(consumer)) { + return; + } + + List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList); + newList.add(consumer); + mConsumerList = Collections.unmodifiableList(newList); } } @@ -275,9 +278,9 @@ public abstract class PowerStatsCollector { @SuppressWarnings("unchecked") public void removeConsumer(Consumer<PowerStats> consumer) { synchronized (this) { - mConsumerList = new FastImmutableArraySet<Consumer<PowerStats>>( - mConsumerList.stream().filter(c -> c != consumer) - .toArray(Consumer[]::new)); + List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList); + newList.remove(consumer); + mConsumerList = Collections.unmodifiableList(newList); } } @@ -302,8 +305,9 @@ public abstract class PowerStatsCollector { if (stats == null) { return; } - for (Consumer<PowerStats> consumer : mConsumerList) { - consumer.accept(stats); + List<Consumer<PowerStats>> consumerList = mConsumerList; + for (int i = consumerList.size() - 1; i >= 0; i--) { + consumerList.get(i).accept(stats); } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java index 97d872a1a539..121a98bab37a 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java @@ -18,7 +18,6 @@ package com.android.server.power.stats; import android.annotation.DurationMillisLong; import android.app.AlarmManager; -import android.content.Context; import android.os.ConditionVariable; import android.os.Handler; import android.util.IndentingPrintWriter; @@ -30,6 +29,7 @@ import com.android.internal.os.MonotonicClock; import java.io.PrintWriter; import java.util.Calendar; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; /** * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in @@ -39,7 +39,7 @@ public class PowerStatsScheduler { private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1); private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1); - private final Context mContext; + private final AlarmScheduler mAlarmScheduler; private boolean mEnablePeriodicPowerStatsCollection; @DurationMillisLong private final long mAggregatedPowerStatsSpanDuration; @@ -49,24 +49,38 @@ public class PowerStatsScheduler { private final Clock mClock; private final MonotonicClock mMonotonicClock; private final Handler mHandler; - private final BatteryStatsImpl mBatteryStats; + private final Runnable mPowerStatsCollector; + private final Supplier<Long> mEarliestAvailableBatteryHistoryTimeMs; private final PowerStatsAggregator mPowerStatsAggregator; private long mLastSavedSpanEndMonotonicTime; - public PowerStatsScheduler(Context context, PowerStatsAggregator powerStatsAggregator, + /** + * External dependency on AlarmManager. + */ + public interface AlarmScheduler { + /** + * Should use AlarmManager to schedule an inexact, non-wakeup alarm. + */ + void scheduleAlarm(long triggerAtMillis, String tag, + AlarmManager.OnAlarmListener onAlarmListener, Handler handler); + } + + public PowerStatsScheduler(Runnable powerStatsCollector, + PowerStatsAggregator powerStatsAggregator, @DurationMillisLong long aggregatedPowerStatsSpanDuration, @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore, - Clock clock, MonotonicClock monotonicClock, Handler handler, - BatteryStatsImpl batteryStats) { - mContext = context; + AlarmScheduler alarmScheduler, Clock clock, MonotonicClock monotonicClock, + Supplier<Long> earliestAvailableBatteryHistoryTimeMs, Handler handler) { mPowerStatsAggregator = powerStatsAggregator; mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration; mPowerStatsAggregationPeriod = powerStatsAggregationPeriod; mPowerStatsStore = powerStatsStore; + mAlarmScheduler = alarmScheduler; mClock = clock; mMonotonicClock = monotonicClock; mHandler = handler; - mBatteryStats = batteryStats; + mPowerStatsCollector = powerStatsCollector; + mEarliestAvailableBatteryHistoryTimeMs = earliestAvailableBatteryHistoryTimeMs; } /** @@ -81,9 +95,8 @@ public class PowerStatsScheduler { } private void scheduleNextPowerStatsAggregation() { - AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); - alarmManager.set(AlarmManager.ELAPSED_REALTIME, - mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, "PowerStats", + mAlarmScheduler.scheduleAlarm(mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, + "PowerStats", () -> { schedulePowerStatsAggregation(); mHandler.post(this::scheduleNextPowerStatsAggregation); @@ -96,7 +109,7 @@ public class PowerStatsScheduler { @VisibleForTesting public void schedulePowerStatsAggregation() { // Catch up the power stats collectors - mBatteryStats.schedulePowerStatsSampleCollection(); + mPowerStatsCollector.run(); mHandler.post(this::aggregateAndStorePowerStats); } @@ -105,7 +118,7 @@ public class PowerStatsScheduler { long currentMonotonicTime = mMonotonicClock.monotonicTime(); long startTime = getLastSavedSpanEndMonotonicTime(); if (startTime < 0) { - startTime = mBatteryStats.getHistory().getStartTime(); + startTime = mEarliestAvailableBatteryHistoryTimeMs.get(); } long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration, mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index d903ad4d9f0d..6f2750767094 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -41,6 +41,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.graphics.Rect; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; @@ -2080,45 +2081,6 @@ public final class TvInputManagerService extends SystemService { } @Override - public void stopPlayback(IBinder sessionToken, int mode, int userId) { - final int callingUid = Binder.getCallingUid(); - final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, - userId, "stopPlayback"); - final long identity = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - try { - getSessionLocked(sessionToken, callingUid, resolvedUserId).stopPlayback( - mode); - } catch (RemoteException | SessionNotFoundException e) { - Slog.e(TAG, "error in stopPlayback(mode)", e); - } - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Override - public void startPlayback(IBinder sessionToken, int userId) { - final int callingUid = Binder.getCallingUid(); - final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, - userId, "stopPlayback"); - final long identity = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - try { - getSessionLocked(sessionToken, callingUid, resolvedUserId).startPlayback(); - } catch (RemoteException | SessionNotFoundException e) { - Slog.e(TAG, "error in startPlayback()", e); - } - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Override public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) { final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java new file mode 100644 index 000000000000..2eeb903bb551 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java @@ -0,0 +1,105 @@ +/* + * 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.vibrator; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.frameworks.vibrator.IVibratorControlService; +import android.frameworks.vibrator.IVibratorController; +import android.frameworks.vibrator.VibrationParam; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import java.util.Objects; + +/** + * Implementation of {@link IVibratorControlService} which allows the registration of + * {@link IVibratorController} to set and receive vibration params. + * + * @hide + */ +public final class VibratorControlService extends IVibratorControlService.Stub { + private static final String TAG = "VibratorControlService"; + + private final VibratorControllerHolder mVibratorControllerHolder; + private final Object mLock; + + public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, Object lock) { + mVibratorControllerHolder = vibratorControllerHolder; + mLock = lock; + } + + @Override + public void registerVibratorController(IVibratorController controller) + throws RemoteException { + synchronized (mLock) { + mVibratorControllerHolder.setVibratorController(controller); + } + } + + @Override + public void unregisterVibratorController(@NonNull IVibratorController controller) + throws RemoteException { + Objects.requireNonNull(controller); + + synchronized (mLock) { + if (mVibratorControllerHolder.getVibratorController() == null) { + Slog.w(TAG, "Received request to unregister IVibratorController = " + + controller + ", but no controller was previously registered. Request " + + "Ignored."); + return; + } + if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(), + controller.asBinder())) { + Slog.wtf(TAG, "Failed to unregister IVibratorController. The provided " + + "controller doesn't match the registered one. " + this); + return; + } + mVibratorControllerHolder.setVibratorController(null); + } + } + + @Override + public void setVibrationParams( + @SuppressLint("ArrayReturn") VibrationParam[] params, IVibratorController token) + throws RemoteException { + // TODO(b/305939964): Add set vibration implementation. + } + + @Override + public void clearVibrationParams(int types, IVibratorController token) throws RemoteException { + // TODO(b/305939964): Add clear vibration implementation. + } + + @Override + public void onRequestVibrationParamsComplete( + IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) + throws RemoteException { + // TODO(305942827): Cache the vibration params in VibrationScaler + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return this.VERSION; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return this.HASH; + } +} diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java new file mode 100644 index 000000000000..63e69db9480f --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java @@ -0,0 +1,70 @@ +/* + * 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.vibrator; + +import android.annotation.NonNull; +import android.frameworks.vibrator.IVibratorController; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +/** + * Holder class for {@link IVibratorController}. + * + * @hide + */ +public final class VibratorControllerHolder implements IBinder.DeathRecipient { + private static final String TAG = "VibratorControllerHolder"; + + private IVibratorController mVibratorController; + + public IVibratorController getVibratorController() { + return mVibratorController; + } + + /** + * Sets the {@link IVibratorController} in {@link VibratorControllerHolder} to the new + * controller. This will also take care of registering and unregistering death notifications + * for the cached {@link IVibratorController}. + */ + public void setVibratorController(IVibratorController controller) { + try { + if (mVibratorController != null) { + mVibratorController.asBinder().unlinkToDeath(this, 0); + } + mVibratorController = controller; + if (mVibratorController != null) { + mVibratorController.asBinder().linkToDeath(this, 0); + } + } catch (RemoteException e) { + Slog.wtf(TAG, "Failed to set IVibratorController: " + this, e); + } + } + + @Override + public void binderDied(@NonNull IBinder deadBinder) { + if (deadBinder == mVibratorController.asBinder()) { + setVibratorController(null); + } + } + + @Override + public void binderDied() { + // Should not be used as binderDied(IBinder who) is overridden. + Slog.wtf(TAG, "binderDied() called unexpectedly."); + } +} diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 7d4bd3baf613..fc824abd80f5 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -91,6 +91,8 @@ import java.util.function.Function; public class VibratorManagerService extends IVibratorManagerService.Stub { private static final String TAG = "VibratorManagerService"; private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service"; + private static final String VIBRATOR_CONTROL_SERVICE = + "android.frameworks.vibrator.IVibratorControlService/default"; private static final boolean DEBUG = false; private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); @@ -269,6 +271,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED); injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); + if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) { + injector.addService(VIBRATOR_CONTROL_SERVICE, + new VibratorControlService(new VibratorControllerHolder(), mLock)); + } + } /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */ diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 03d6c2cab828..ae10ce3690aa 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5149,8 +5149,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** @return the orientation of the display when it's rotation is ROTATION_0. */ int getNaturalOrientation() { - return mBaseDisplayWidth < mBaseDisplayHeight - ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; + final Configuration config = getConfiguration(); + if (config.windowConfiguration.getDisplayRotation() == ROTATION_0) { + return config.orientation; + } + final Rect frame = mDisplayPolicy.getDecorInsetsInfo( + ROTATION_0, mBaseDisplayWidth, mBaseDisplayHeight).mConfigFrame; + return frame.width() <= frame.height() ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; } void performLayout(boolean initial, boolean updateInputWindows) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a872fd0baaae..4b99432b2943 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -63,6 +63,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; +import static com.android.server.wm.ActivityRecord.State.PAUSING; import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission; import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS; @@ -1178,6 +1179,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.mRootWindowContainer.moveActivityToPinnedRootTask( pipActivity, null /* launchIntoPipHostActivity */, "moveActivityToPinnedRootTask", null /* transition */, entryBounds); + + // Continue the pausing process after potential task reparenting. + if (pipActivity.isState(PAUSING) && pipActivity.mPauseSchedulePendingForPip) { + pipActivity.getTask().schedulePauseActivity( + pipActivity, false /* userLeaving */, + false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip"); + } + effects |= TRANSACT_EFFECTS_LIFECYCLE; break; } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index fd5f7a3f5cfa..5721750fbf63 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -66,6 +66,7 @@ import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.os.Binder; import android.os.Build; +import android.os.DeadObjectException; import android.os.FactoryTest; import android.os.LocaleList; import android.os.Message; @@ -1675,6 +1676,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio @NonNull ClientTransactionItem transactionItem) { try { mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem); + } catch (DeadObjectException e) { + // Expected if the process has been killed. + Slog.w(TAG_CONFIGURATION, "Failed for dead process. ClientTransactionItem=" + + transactionItem + " owner=" + mOwner); } catch (Exception e) { Slog.e(TAG_CONFIGURATION, "Failed to schedule ClientTransactionItem=" + transactionItem + " owner=" + mOwner, e); diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig index 47c2a1b079f8..29e258cc90ff 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig @@ -5,4 +5,12 @@ flag { namespace: "display_manager" description: "Feature flag for dual display blocking" bug: "278667199" +} + +flag { + name: "enable_foldables_posture_based_closed_state" + namespace: "windowing_frontend" + description: "Enables smarter closed device state state for foldable devices" + bug: "309792734" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/services/manifest_services.xml b/services/manifest_services.xml index 76389154a885..eae159fe9e89 100644 --- a/services/manifest_services.xml +++ b/services/manifest_services.xml @@ -4,4 +4,9 @@ <version>1</version> <fqname>IAltitudeService/default</fqname> </hal> + <hal format="aidl"> + <name>android.frameworks.vibrator</name> + <version>1</version> + <fqname>IVibratorControlService/default</fqname> + </hal> </manifest> diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index 76d4d55f4d5d..9739e4b46063 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -2423,6 +2423,14 @@ public class GameManagerServiceTests { } })); + when(mSysPropsMock.getInt( + ArgumentMatchers.eq(PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE), + anyInt())).thenReturn(60); + when(mSysPropsMock.getBoolean( + ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED), + ArgumentMatchers.eq(true))).thenReturn(true); + gameManagerService.onBootCompleted(); + // Set up a game in the foreground. String[] packages = {mPackageName}; when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); @@ -2430,12 +2438,6 @@ public class GameManagerServiceTests { DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); // Toggle game default frame rate on. - when(mSysPropsMock.getInt( - ArgumentMatchers.eq(PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE), - anyInt())).thenReturn(60); - when(mSysPropsMock.getBoolean( - ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED), - ArgumentMatchers.eq(true))).thenReturn(true); gameManagerService.toggleGameDefaultFrameRate(true); // Verify that: diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp index 07197b1ab9df..05e0e8f4a4f7 100644 --- a/services/tests/powerstatstests/Android.bp +++ b/services/tests/powerstatstests/Android.bp @@ -3,6 +3,20 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +filegroup { + name: "power_stats_ravenwood_tests", + srcs: [ + "src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java", + "src/com/android/server/power/stats/AggregatedPowerStatsTest.java", + "src/com/android/server/power/stats/MultiStateStatsTest.java", + "src/com/android/server/power/stats/PowerStatsAggregatorTest.java", + "src/com/android/server/power/stats/PowerStatsCollectorTest.java", + "src/com/android/server/power/stats/PowerStatsSchedulerTest.java", + "src/com/android/server/power/stats/PowerStatsStoreTest.java", + "src/com/android/server/power/stats/PowerStatsUidResolverTest.java", + ], +} + android_test { name: "PowerStatsTests", @@ -12,8 +26,7 @@ android_test { ], exclude_srcs: [ - "src/com/android/server/power/stats/MultiStateStatsTest.java", - "src/com/android/server/power/stats/PowerStatsStoreTest.java", + ":power_stats_ravenwood_tests", ], static_libs: [ @@ -65,10 +78,12 @@ android_ravenwood_test { "modules-utils-binary-xml", "androidx.annotation_annotation", "androidx.test.rules", + "truth", + "mockito_ravenwood", ], srcs: [ - "src/com/android/server/power/stats/MultiStateStatsTest.java", - "src/com/android/server/power/stats/PowerStatsStoreTest.java", + ":power_stats_ravenwood_tests", + "src/com/android/server/power/stats/MockClock.java", ], auto_gen_config: true, } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java index 6d61dc8d31fa..af83be04db7d 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java @@ -35,7 +35,7 @@ import java.util.Objects; @RunWith(AndroidJUnit4.class) @SmallTest -public class AggregatePowerStatsProcessorTest { +public class AggregatedPowerStatsProcessorTest { @Test public void createPowerEstimationPlan_allDeviceStatesPresentInUidStates() { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java index e8f46b30fb8c..1b045c532759 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java @@ -22,12 +22,10 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import android.os.BatteryConsumer; -import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,11 +36,6 @@ import java.util.Arrays; @RunWith(AndroidJUnit4.class) @SmallTest public class MultiStateStatsTest { - - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() - .build(); - public static final int DIMENSION_COUNT = 2; @Test diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java index 67049871f396..2456636970fa 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.mock; import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.PersistableBundle; -import android.text.format.DateFormat; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; @@ -39,7 +38,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.text.ParseException; -import java.util.Calendar; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; import java.util.TimeZone; @@ -60,7 +60,7 @@ public class PowerStatsAggregatorTest { public void setup() throws ParseException { mHistory = new BatteryStatsHistory(32, 1024, mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock, - mMonotonicClock); + mMonotonicClock, mock(BatteryStatsHistory.TraceDelegate.class)); AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); config.trackPowerComponent(TEST_POWER_COMPONENT) @@ -179,9 +179,9 @@ public class PowerStatsAggregatorTest { @NonNull private static CharSequence formatDateTime(long timeInMillis) { - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); - cal.setTimeInMillis(timeInMillis); - return DateFormat.format("yyyy-MM-dd hh:mm:ss", cal); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + format.getCalendar().setTimeZone(TimeZone.getTimeZone("GMT")); + return format.format(new Date(timeInMillis)); } @Test diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java index 330f698277f8..17a7d3ecf9d3 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java @@ -22,6 +22,7 @@ import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.os.PersistableBundle; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -29,12 +30,18 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.PowerStats; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class PowerStatsCollectorTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + private final MockClock mMockClock = new MockClock(); private final HandlerThread mHandlerThread = new HandlerThread("test"); private Handler mHandler; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java index 7257a94cbb9a..beec66156fe4 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java @@ -24,26 +24,26 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import android.content.Context; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; +import android.platform.test.ravenwood.RavenwoodRule; -import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.MonotonicClock; -import com.android.internal.os.PowerProfile; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.TimeZone; import java.util.concurrent.TimeUnit; @@ -51,39 +51,46 @@ import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) public class PowerStatsSchedulerTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + private PowerStatsStore mPowerStatsStore; private Handler mHandler; private MockClock mClock = new MockClock(); private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock); - private MockBatteryStatsImpl mBatteryStats; private PowerStatsScheduler mPowerStatsScheduler; - private PowerProfile mPowerProfile; private PowerStatsAggregator mPowerStatsAggregator; private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; + private List<Long> mScheduledAlarms = new ArrayList<>(); + private boolean mPowerStatsCollectionOccurred; + + private static final int START_REALTIME = 7654321; @Before @SuppressWarnings("GuardedBy") - public void setup() { - final Context context = InstrumentationRegistry.getContext(); - + public void setup() throws IOException { TimeZone.setDefault(TimeZone.getTimeZone("UTC")); mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli(); - mClock.realtime = 7654321; + mClock.realtime = START_REALTIME; HandlerThread bgThread = new HandlerThread("bg thread"); bgThread.start(); - File systemDir = context.getCacheDir(); mHandler = new Handler(bgThread.getLooper()); mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig(); - mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig); - mPowerProfile = mock(PowerProfile.class); - when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0); - mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile); + mPowerStatsStore = new PowerStatsStore( + Files.createTempDirectory("PowerStatsSchedulerTest").toFile(), + mHandler, mAggregatedPowerStatsConfig); mPowerStatsAggregator = mock(PowerStatsAggregator.class); - mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, - TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock, - mMonotonicClock, mHandler, mBatteryStats); + mPowerStatsScheduler = new PowerStatsScheduler( + () -> mPowerStatsCollectionOccurred = true, + mPowerStatsAggregator, TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), + mPowerStatsStore, + ((triggerAtMillis, tag, onAlarmListener, handler) -> + mScheduledAlarms.add(triggerAtMillis)), + mClock, mMonotonicClock, () -> 12345L, mHandler); } @Test @@ -113,7 +120,7 @@ public class PowerStatsSchedulerTest { long endTimeWallClock = mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime); - assertThat(startTime).isEqualTo(7654321 + 123); + assertThat(startTime).isEqualTo(START_REALTIME + 123); assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30)); assertThat(Instant.ofEpochMilli(endTimeWallClock)) .isEqualTo(Instant.parse("2023-01-02T04:00:00Z")); @@ -142,11 +149,15 @@ public class PowerStatsSchedulerTest { }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class)); - mPowerStatsScheduler.schedulePowerStatsAggregation(); + mPowerStatsScheduler.start(/*enabled*/ true); ConditionVariable done = new ConditionVariable(); mHandler.post(done::open); done.block(); + assertThat(mPowerStatsCollectionOccurred).isTrue(); + assertThat(mScheduledAlarms).containsExactly( + START_REALTIME + TimeUnit.MINUTES.toMillis(90) + TimeUnit.HOURS.toMillis(1)); + verify(mPowerStatsAggregator, times(2)) .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class)); @@ -155,7 +166,7 @@ public class PowerStatsSchedulerTest { // Skip the first entry, which was placed in the store at the beginning of this test PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0); PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0); - assertThat(timeFrame1.startMonotonicTime).isEqualTo(7654321 + 123); + assertThat(timeFrame1.startMonotonicTime).isEqualTo(START_REALTIME + 123); assertThat(timeFrame2.startMonotonicTime) .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration); assertThat(Instant.ofEpochMilli(timeFrame2.startTime)) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 4a537dfaf8b3..36d55a48346e 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -621,10 +621,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { }); // TODO (b/291907312): remove feature flag - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_VISIT_RISKY_URIS); - mSetFlagsRule.disableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER, - Flags.FLAG_POLITE_NOTIFICATIONS, android.app.Flags.FLAG_MODES_API); + Flags.FLAG_POLITE_NOTIFICATIONS); initNMS(); } @@ -6274,21 +6272,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(personIcon3.getUri())); } - private PendingIntent getPendingIntentWithUri(Uri uri) { - return PendingIntent.getActivity(mContext, 0, - new Intent("action", uri), - PendingIntent.FLAG_IMMUTABLE); - } - @Test - public void testVisitUris_callStyle_ongoingCall() { + public void testVisitUris_callStyle() { Icon personIcon = Icon.createWithContentUri("content://media/person"); Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); Person callingPerson = new Person.Builder().setName("Someone") .setIcon(personIcon) .build(); - Uri hangUpUri = Uri.parse("content://intent/hangup"); - PendingIntent hangUpIntent = getPendingIntentWithUri(hangUpUri); + PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); Notification n = new Notification.Builder(mContext, "a") .setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent) .setVerificationIcon(verificationIcon)) @@ -6301,35 +6293,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(personIcon.getUri())); verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); - verify(visitor, times(1)).accept(eq(hangUpUri)); - } - - @Test - public void testVisitUris_callStyle_incomingCall() { - Icon personIcon = Icon.createWithContentUri("content://media/person"); - Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); - Person callingPerson = new Person.Builder().setName("Someone") - .setIcon(personIcon) - .build(); - Uri answerUri = Uri.parse("content://intent/answer"); - PendingIntent answerIntent = getPendingIntentWithUri(answerUri); - Uri declineUri = Uri.parse("content://intent/decline"); - PendingIntent declineIntent = getPendingIntentWithUri(declineUri); - Notification n = new Notification.Builder(mContext, "a") - .setStyle(Notification.CallStyle.forIncomingCall(callingPerson, declineIntent, - answerIntent) - .setVerificationIcon(verificationIcon)) - .setContentTitle("Calling...") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .build(); - - Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); - n.visitUris(visitor); - - verify(visitor, times(1)).accept(eq(personIcon.getUri())); - verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); - verify(visitor, times(1)).accept(eq(answerIntent.getIntent().getData())); - verify(visitor, times(1)).accept(eq(declineUri)); } @Test @@ -6381,74 +6344,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testVisitUris_wearableExtender() { Icon actionIcon = Icon.createWithContentUri("content://media/action"); Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction"); - Uri displayIntentUri = Uri.parse("content://intent/display"); - PendingIntent displayIntent = getPendingIntentWithUri(displayIntentUri); - Uri actionIntentUri = Uri.parse("content://intent/action"); - PendingIntent actionIntent = getPendingIntentWithUri(actionIntentUri); - Uri wearActionIntentUri = Uri.parse("content://intent/wear"); - PendingIntent wearActionIntent = getPendingIntentWithUri(wearActionIntentUri); + PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); Notification n = new Notification.Builder(mContext, "a") .setSmallIcon(android.R.drawable.sym_def_app_icon) - .addAction( - new Notification.Action.Builder(actionIcon, "Hey!", actionIntent).build()) - .extend(new Notification.WearableExtender() - .setDisplayIntent(displayIntent) - .addAction(new Notification.Action.Builder(wearActionIcon, "Wear!", - wearActionIntent) - .build())) + .addAction(new Notification.Action.Builder(actionIcon, "Hey!", intent).build()) + .extend(new Notification.WearableExtender().addAction( + new Notification.Action.Builder(wearActionIcon, "Wear!", intent).build())) .build(); Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); n.visitUris(visitor); verify(visitor).accept(eq(actionIcon.getUri())); - verify(visitor, times(1)).accept(eq(actionIntentUri)); verify(visitor).accept(eq(wearActionIcon.getUri())); - verify(visitor, times(1)).accept(eq(wearActionIntentUri)); - } - - @Test - public void testVisitUris_tvExtender() { - Uri contentIntentUri = Uri.parse("content://intent/content"); - PendingIntent contentIntent = getPendingIntentWithUri(contentIntentUri); - Uri deleteIntentUri = Uri.parse("content://intent/delete"); - PendingIntent deleteIntent = getPendingIntentWithUri(deleteIntentUri); - Notification n = new Notification.Builder(mContext, "a") - .extend( - new Notification.TvExtender() - .setContentIntent(contentIntent) - .setDeleteIntent(deleteIntent)) - .build(); - - Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); - n.visitUris(visitor); - - verify(visitor, times(1)).accept(eq(contentIntentUri)); - verify(visitor, times(1)).accept(eq(deleteIntentUri)); - } - - @Test - public void testVisitUris_carExtender() { - final String testParticipant = "testParticipant"; - Uri readPendingIntentUri = Uri.parse("content://intent/read"); - PendingIntent readPendingIntent = getPendingIntentWithUri(readPendingIntentUri); - Uri replyPendingIntentUri = Uri.parse("content://intent/reply"); - PendingIntent replyPendingIntent = getPendingIntentWithUri(replyPendingIntentUri); - final RemoteInput testRemoteInput = new RemoteInput.Builder("key").build(); - - Notification n = new Notification.Builder(mContext, "a") - .extend(new Notification.CarExtender().setUnreadConversation( - new Notification.CarExtender.Builder(testParticipant) - .setReadPendingIntent(readPendingIntent) - .setReplyAction(replyPendingIntent, testRemoteInput) - .build())) - .build(); - - Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); - n.visitUris(visitor); - - verify(visitor, times(1)).accept(eq(readPendingIntentUri)); - verify(visitor, times(1)).accept(eq(replyPendingIntentUri)); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java index ea948ca0e28b..44dbe385a144 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -146,10 +146,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { .put(Notification.Action.Builder.class, "extend") // Overwrites icon supplied to constructor. .put(Notification.BubbleMetadata.Builder.class, "setIcon") - // Overwrites intent supplied to constructor. - .put(Notification.BubbleMetadata.Builder.class, "setIntent") - // Overwrites intent supplied to constructor. - .put(Notification.BubbleMetadata.Builder.class, "setDeleteIntent") // Discards previously-added actions. .put(RemoteViews.class, "mergeRemoteViews") .build(); @@ -684,14 +680,14 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } if (clazz == Intent.class) { - return new Intent("action", generateUri(where.plus(Intent.class))); + // TODO(b/281044385): Are Intent Uris (new Intent(String,Uri)) relevant? + return new Intent("action"); } if (clazz == PendingIntent.class) { - // PendingIntent can have an Intent with a Uri. - Uri intentUri = generateUri(where.plus(PendingIntent.class)); - return PendingIntent.getActivity(mContext, 0, - new Intent("action", intentUri), + // PendingIntent can have an Intent with a Uri but those are inaccessible and + // not inspected. + return PendingIntent.getActivity(mContext, 0, new Intent("action"), PendingIntent.FLAG_IMMUTABLE); } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java new file mode 100644 index 000000000000..49efd1bdd92a --- /dev/null +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.RemoteException; + +import org.junit.Before; +import org.junit.Test; + +public class VibratorControlServiceTest { + + private VibratorControlService mVibratorControlService; + private final Object mLock = new Object(); + + @Before + public void setUp() throws Exception { + mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), mLock); + } + + @Test + public void testRegisterVibratorController() throws RemoteException { + FakeVibratorController fakeController = new FakeVibratorController(); + mVibratorControlService.registerVibratorController(fakeController); + + assertThat(fakeController.isLinkedToDeath).isTrue(); + } + + @Test + public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest() + throws RemoteException { + FakeVibratorController fakeController = new FakeVibratorController(); + mVibratorControlService.registerVibratorController(fakeController); + mVibratorControlService.unregisterVibratorController(fakeController); + assertThat(fakeController.isLinkedToDeath).isFalse(); + } + + @Test + public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest() + throws RemoteException { + FakeVibratorController fakeController1 = new FakeVibratorController(); + FakeVibratorController fakeController2 = new FakeVibratorController(); + mVibratorControlService.registerVibratorController(fakeController1); + + mVibratorControlService.unregisterVibratorController(fakeController2); + assertThat(fakeController1.isLinkedToDeath).isTrue(); + } +} diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java new file mode 100644 index 000000000000..79abe21a301d --- /dev/null +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.RemoteException; + +import org.junit.Before; +import org.junit.Test; + +public class VibratorControllerHolderTest { + + private final FakeVibratorController mFakeVibratorController = new FakeVibratorController(); + private VibratorControllerHolder mVibratorControllerHolder; + + @Before + public void setUp() throws Exception { + mVibratorControllerHolder = new VibratorControllerHolder(); + } + + @Test + public void testSetVibratorController_linksVibratorControllerToDeath() throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + assertThat(mVibratorControllerHolder.getVibratorController()) + .isEqualTo(mFakeVibratorController); + assertThat(mFakeVibratorController.isLinkedToDeath).isTrue(); + } + + @Test + public void testSetVibratorController_setControllerToNull_unlinksVibratorControllerToDeath() + throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + mVibratorControllerHolder.setVibratorController(null); + assertThat(mFakeVibratorController.isLinkedToDeath).isFalse(); + assertThat(mVibratorControllerHolder.getVibratorController()).isNull(); + } + + @Test + public void testBinderDied_withValidController_unlinksVibratorControllerToDeath() + throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + mVibratorControllerHolder.binderDied(mFakeVibratorController); + assertThat(mFakeVibratorController.isLinkedToDeath).isFalse(); + assertThat(mVibratorControllerHolder.getVibratorController()).isNull(); + } + + @Test + public void testBinderDied_withInvalidController_ignoresRequest() + throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + FakeVibratorController imposterVibratorController = new FakeVibratorController(); + mVibratorControllerHolder.binderDied(imposterVibratorController); + assertThat(mFakeVibratorController.isLinkedToDeath).isTrue(); + assertThat(mVibratorControllerHolder.getVibratorController()) + .isEqualTo(mFakeVibratorController); + } +} diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 4e9bbe0a28fe..d6b2116e2682 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -307,9 +307,10 @@ public class VibratorManagerServiceTest { @Override void addService(String name, IBinder service) { - Object serviceInstance = service; - mExternalVibratorService = - (VibratorManagerService.ExternalVibratorService) serviceInstance; + if (service instanceof VibratorManagerService.ExternalVibratorService) { + mExternalVibratorService = + (VibratorManagerService.ExternalVibratorService) service; + } } HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider( diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java new file mode 100644 index 000000000000..7e235870cedc --- /dev/null +++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 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.vibrator; + +import android.annotation.NonNull; +import android.frameworks.vibrator.IVibratorController; +import android.os.IBinder; +import android.os.RemoteException; + +/** + * Provides a fake implementation of {@link android.frameworks.vibrator.IVibratorController} for + * testing. + */ +public final class FakeVibratorController extends IVibratorController.Stub { + + public boolean isLinkedToDeath = false; + + @Override + public void requestVibrationParams(int i, long l, IBinder iBinder) throws RemoteException { + + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } + + @Override + public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { + super.linkToDeath(recipient, flags); + isLinkedToDeath = true; + } + + @Override + public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) { + isLinkedToDeath = false; + return super.unlinkToDeath(recipient, flags); + } +} diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp index 744cb63f72b3..8a79fe443179 100644 --- a/services/tests/voiceinteractiontests/Android.bp +++ b/services/tests/voiceinteractiontests/Android.bp @@ -44,6 +44,7 @@ android_test { "servicestests-core-utils", "servicestests-utils-mockito-extended", "truth", + "frameworks-base-testutils", ], libs: [ diff --git a/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java b/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java new file mode 100644 index 000000000000..656957c35a5d --- /dev/null +++ b/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java @@ -0,0 +1,180 @@ +/* + * 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.voiceinteraction; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.Manifest; +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.app.role.RoleManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.PermissionEnforcer; +import android.os.Process; +import android.os.test.FakePermissionEnforcer; +import android.platform.test.annotations.Presubmit; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.modules.utils.testing.ExtendedMockitoRule; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.permission.LegacyPermissionManagerInternal; +import com.android.server.pm.permission.PermissionManagerServiceInternal; +import com.android.server.wm.ActivityTaskManagerInternal; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.quality.Strictness; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class SetSandboxedTrainingDataAllowedTest { + + @Captor private ArgumentCaptor<Integer> mOpIdCaptor, mUidCaptor, mOpModeCaptor; + + @Mock + private AppOpsManager mAppOpsManager; + + @Mock + private VoiceInteractionManagerServiceImpl mVoiceInteractionManagerServiceImpl; + + private FakePermissionEnforcer mPermissionEnforcer = new FakePermissionEnforcer(); + + private Context mContext; + + private VoiceInteractionManagerService mVoiceInteractionManagerService; + private VoiceInteractionManagerService.VoiceInteractionManagerServiceStub + mVoiceInteractionManagerServiceStub; + + private ApplicationInfo mApplicationInfo = new ApplicationInfo(); + + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = + new ExtendedMockitoRule.Builder(this) + .setStrictness(Strictness.WARN) + .mockStatic(LocalServices.class) + .mockStatic(PermissionEnforcer.class) + .build(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = spy(ApplicationProvider.getApplicationContext()); + + doReturn(mPermissionEnforcer).when(() -> PermissionEnforcer.fromContext(any())); + doReturn(mock(PermissionManagerServiceInternal.class)).when( + () -> LocalServices.getService(PermissionManagerServiceInternal.class)); + doReturn(mock(ActivityManagerInternal.class)).when( + () -> LocalServices.getService(ActivityManagerInternal.class)); + doReturn(mock(UserManagerInternal.class)).when( + () -> LocalServices.getService(UserManagerInternal.class)); + doReturn(mock(ActivityTaskManagerInternal.class)).when( + () -> LocalServices.getService(ActivityTaskManagerInternal.class)); + doReturn(mock(LegacyPermissionManagerInternal.class)).when( + () -> LocalServices.getService(LegacyPermissionManagerInternal.class)); + doReturn(mock(RoleManager.class)).when(mContext).getSystemService(RoleManager.class); + doReturn(mAppOpsManager).when(mContext).getSystemService(Context.APP_OPS_SERVICE); + doReturn(mApplicationInfo).when(mVoiceInteractionManagerServiceImpl).getApplicationInfo(); + + mVoiceInteractionManagerService = new VoiceInteractionManagerService(mContext); + mVoiceInteractionManagerServiceStub = + mVoiceInteractionManagerService.new VoiceInteractionManagerServiceStub(); + mVoiceInteractionManagerServiceStub.mImpl = mVoiceInteractionManagerServiceImpl; + mPermissionEnforcer.grant(Manifest.permission.MANAGE_HOTWORD_DETECTION); + } + + @Test + public void setIsReceiveSandboxedTrainingDataAllowed_currentAndPreinstalledAssistant_setsOp() { + // Set application info so current app is the current and preinstalled assistant. + mApplicationInfo.uid = Process.myUid(); + mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed( + /* allowed= */ true); + + verify(mAppOpsManager).setUidMode(mOpIdCaptor.capture(), mUidCaptor.capture(), + mOpModeCaptor.capture()); + assertThat(mOpIdCaptor.getValue()).isEqualTo( + AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA); + assertThat(mOpModeCaptor.getValue()).isEqualTo(AppOpsManager.MODE_ALLOWED); + assertThat(mUidCaptor.getValue()).isEqualTo(Process.myUid()); + } + + @Test + public void setIsReceiveSandboxedTrainingDataAllowed_missingPermission_doesNotSetOp() { + // Set application info so current app is the current and preinstalled assistant. + mApplicationInfo.uid = Process.myUid(); + mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + // Simulate missing MANAGE_HOTWORD_DETECTION permission. + mPermissionEnforcer.revoke(Manifest.permission.MANAGE_HOTWORD_DETECTION); + + assertThrows(SecurityException.class, + () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed( + /* allowed= */ true)); + + verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); + } + + @Test + public void setIsReceiveSandboxedTrainingDataAllowed_notPreinstalledAssistant_doesNotSetOp() { + // Set application info so current app is not preinstalled assistant. + mApplicationInfo.uid = Process.myUid(); + mApplicationInfo.flags = ApplicationInfo.FLAG_INSTALLED; // Does not contain FLAG_SYSTEM. + + assertThrows(SecurityException.class, + () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed( + /* allowed= */ true)); + + verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); + } + + @Test + public void setIsReceiveSandboxedTrainingDataAllowed_notCurrentAssistant_doesNotSetOp() { + // Set application info so current app is not current assistant. + mApplicationInfo.uid = Process.SHELL_UID; // Set current assistant uid to shell UID. + mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + assertThrows(SecurityException.class, + () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed( + /* allowed= */ true)); + + verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 71d2504e1746..dfe79bf1e3e6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -993,7 +993,9 @@ public class DisplayContentTests extends WindowTestsBase { dc.getDisplayPolicy().getDecorInsetsInfo(ROTATION_0, dc.mBaseDisplayHeight, dc.mBaseDisplayWidth).mConfigFrame.set(0, 0, 1000, 990); dc.computeScreenConfiguration(config, ROTATION_0); + dc.onRequestedOverrideConfigurationChanged(config); assertEquals(Configuration.ORIENTATION_LANDSCAPE, config.orientation); + assertEquals(Configuration.ORIENTATION_LANDSCAPE, dc.getNaturalOrientation()); } @Test diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 3d2340cca378..72db7fecb8ac 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -2278,6 +2278,12 @@ public class UsageStatsService extends SystemService implements "Only the system or holders of the REPORT_USAGE_STATS" + " permission are allowed to call reportUserInteraction"); } + if (userId != UserHandle.getCallingUserId()) { + // Cross-user event reporting. + getContext().enforceCallingPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "Caller doesn't have INTERACT_ACROSS_USERS_FULL permission"); + } } else { if (!isCallingUidSystem()) { throw new SecurityException("Only system is allowed to call" @@ -2287,7 +2293,8 @@ public class UsageStatsService extends SystemService implements // Verify if this package exists before reporting an event for it. if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) { - throw new IllegalArgumentException("Package " + packageName + "not exist!"); + throw new IllegalArgumentException("Package " + packageName + + " does not exist!"); } final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime()); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index b214591610de..6e4f13a75625 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -2273,9 +2273,9 @@ public class VoiceInteractionManagerService extends SystemService { private boolean isCallerPreinstalledAssistant() { return mImpl != null - && mImpl.mInfo.getServiceInfo().applicationInfo.uid == Binder.getCallingUid() - && (mImpl.mInfo.getServiceInfo().applicationInfo.isSystemApp() - || mImpl.mInfo.getServiceInfo().applicationInfo.isUpdatedSystemApp()); + && mImpl.getApplicationInfo().uid == Binder.getCallingUid() + && (mImpl.getApplicationInfo().isSystemApp() + || mImpl.getApplicationInfo().isUpdatedSystemApp()); } private void setImplLocked(VoiceInteractionManagerServiceImpl impl) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 3c4b58fa2b69..7e0cbad1e828 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -40,6 +40,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; @@ -540,6 +541,10 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return mInfo.getSupportsLocalInteraction(); } + public ApplicationInfo getApplicationInfo() { + return mInfo.getServiceInfo().applicationInfo; + } + public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) { if (DEBUG) { Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index b356fde53417..326b6f5af613 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1508,8 +1508,14 @@ public class SubscriptionManager { public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { if (listener == null) return; - addOnSubscriptionsChangedListener( - new HandlerExecutor(new Handler(listener.getCreatorLooper())), listener); + Looper looper = listener.getCreatorLooper(); + if (looper == null) { + throw new RuntimeException( + "Can't create handler inside thread " + Thread.currentThread() + + " that has not called Looper.prepare()"); + } + + addOnSubscriptionsChangedListener(new HandlerExecutor(new Handler(looper)), listener); } /** diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp index 15a6a207d6d1..6fcdf1c77704 100644 --- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp +++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp @@ -27,4 +27,7 @@ android_test { name: "AaptSymlinkTest", sdk_version: "current", use_resource_processor: false, + compile_data: [ + "targets/*", + ], } diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java index 068dfe8f3d11..a1356237e5a4 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java @@ -100,6 +100,10 @@ public class LongArrayMultiStateCounter_host { mLastStateChangeTimestampMs = timestampMs; } + public void setValue(int state, long[] values) { + System.arraycopy(values, 0, mStates[state].mCounter, 0, mArrayLength); + } + public void updateValue(long[] values, long timestampMs) { if (mEnabled || mLastUpdateTimestampMs < mLastStateChangeTimestampMs) { if (timestampMs < mLastStateChangeTimestampMs) { @@ -306,6 +310,11 @@ public class LongArrayMultiStateCounter_host { return getInstance(instanceId).mArrayLength; } + public static void native_setValues(long instanceId, int state, long containerInstanceId) { + getInstance(instanceId).setValue(state, + LongArrayContainer_host.getInstance(containerInstanceId)); + } + public static void native_updateValues(long instanceId, long containerInstanceId, long timestampMs) { getInstance(instanceId).updateValue( diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java index 3bcabcb01c5e..d63bff6f4da3 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java @@ -343,6 +343,28 @@ public class Parcel_host { p.mPos += length; p.updateSize(); } + public static int nativeCompareData(long thisNativePtr, long otherNativePtr) { + var a = getInstance(thisNativePtr); + var b = getInstance(otherNativePtr); + if ((a.mSize == b.mSize) && Arrays.equals(a.mBuffer, b.mBuffer)) { + return 0; + } else { + return -1; + } + } + public static boolean nativeCompareDataInRange( + long ptrA, int offsetA, long ptrB, int offsetB, int length) { + var a = getInstance(ptrA); + var b = getInstance(ptrB); + if (offsetA < 0 || offsetA + length > a.mSize) { + throw new IllegalArgumentException(); + } + if (offsetB < 0 || offsetB + length > b.mSize) { + throw new IllegalArgumentException(); + } + return Arrays.equals(Arrays.copyOfRange(a.mBuffer, offsetA, offsetA + length), + Arrays.copyOfRange(b.mBuffer, offsetB, offsetB + length)); + } public static void nativeAppendFrom( long thisNativePtr, long otherNativePtr, int srcOffset, int length) { var dst = getInstance(thisNativePtr); |