diff options
167 files changed, 4505 insertions, 1659 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 72e66450d339..e0cc143f74ee 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -4124,6 +4124,10 @@ public class JobSchedulerService extends com.android.server.SystemService if (namespace.isEmpty()) { throw new IllegalArgumentException("namespace cannot be empty"); } + if (namespace.length() > 1000) { + throw new IllegalArgumentException( + "namespace cannot be more than 1000 characters"); + } namespace = namespace.intern(); } return namespace; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index e2ef00525902..b5ee895a5a01 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4433,6 +4433,21 @@ public class ActivityManager { } /** + * Similar to {@link #forceStopPackageAsUser(String, int)} but will also stop the package even + * when the user is in the stopping state. + * + * @hide + */ + @RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES) + public void forceStopPackageAsUserEvenWhenStopping(String packageName, @UserIdInt int userId) { + try { + getService().forceStopPackageEvenWhenStopping(packageName, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets the current locales of the device. Calling app must have the permission * {@code android.permission.CHANGE_CONFIGURATION} and * {@code android.permission.WRITE_SETTINGS}. diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 99ef315676c4..e15e08fc0ef0 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -102,6 +102,43 @@ interface IActivityManager { void registerUidObserver(in IUidObserver observer, int which, int cutpoint, String callingPackage); void unregisterUidObserver(in IUidObserver observer); + + /** + * Registers a UidObserver with a uid filter. + * + * @param observer The UidObserver implementation to register. + * @param which A bitmask of events to observe. See ActivityManager.UID_OBSERVER_*. + * @param cutpoint The cutpoint for onUidStateChanged events. When the state crosses this + * threshold in either direction, onUidStateChanged will be called. + * @param callingPackage The name of the calling package. + * @param uids A list of uids to watch. If all uids are to be watched, use + * registerUidObserver instead. + * @throws RemoteException + * @return Returns A binder token identifying the UidObserver registration. + */ + IBinder registerUidObserverForUids(in IUidObserver observer, int which, int cutpoint, + String callingPackage, in int[] uids); + + /** + * Adds a uid to the list of uids that a UidObserver will receive updates about. + * + * @param observerToken The binder token identifying the UidObserver registration. + * @param callingPackage The name of the calling package. + * @param uid The uid to watch. + * @throws RemoteException + */ + void addUidToObserver(in IBinder observerToken, String callingPackage, int uid); + + /** + * Removes a uid from the list of uids that a UidObserver will receive updates about. + * + * @param observerToken The binder token identifying the UidObserver registration. + * @param callingPackage The name of the calling package. + * @param uid The uid to stop watching. + * @throws RemoteException + */ + void removeUidFromObserver(in IBinder observerToken, String callingPackage, int uid); + boolean isUidActive(int uid, String callingPackage); @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)") @@ -299,6 +336,7 @@ interface IActivityManager { boolean registerForegroundServiceObserver(in IForegroundServiceObserver callback); @UnsupportedAppUsage void forceStopPackage(in String packageName, int userId); + void forceStopPackageEvenWhenStopping(in String packageName, int userId); boolean killPids(in int[] pids, in String reason, boolean secure); @UnsupportedAppUsage List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags); diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index 2b1558937d21..4d308d90ce2d 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -259,6 +259,14 @@ interface IWallpaperManager { boolean lockScreenWallpaperExists(); /** + * Return true if there is a static wallpaper on the specified screen. With which=FLAG_LOCK, + * always return false if the lock screen doesn't run its own wallpaper engine. + * + * @hide + */ + boolean isStaticWallpaper(int which); + + /** * Temporary method for project b/197814683. * Return true if the lockscreen wallpaper always uses a WallpaperService, not a static image. * @hide diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index e53680f23a9f..588d289ee451 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2887,8 +2887,9 @@ public class Notification implements Parcelable visitor.accept(person.getIconUri()); } - final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) - extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + final RemoteInputHistoryItem[] history = extras.getParcelableArray( + Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + RemoteInputHistoryItem.class); if (history != null) { for (int i = 0; i < history.length; i++) { RemoteInputHistoryItem item = history[i]; @@ -2900,7 +2901,8 @@ public class Notification implements Parcelable } if (isStyle(MessagingStyle.class) && extras != null) { - final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); + final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, + Parcelable.class); if (!ArrayUtils.isEmpty(messages)) { for (MessagingStyle.Message message : MessagingStyle.Message .getMessagesFromBundleArray(messages)) { @@ -2913,7 +2915,8 @@ public class Notification implements Parcelable } } - final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); + final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES, + Parcelable.class); if (!ArrayUtils.isEmpty(historic)) { for (MessagingStyle.Message message : MessagingStyle.Message .getMessagesFromBundleArray(historic)) { @@ -2928,11 +2931,11 @@ public class Notification implements Parcelable } if (isStyle(CallStyle.class) & extras != null) { - Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON); + Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class); if (callPerson != null) { visitor.accept(callPerson.getIconUri()); } - visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON)); + visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class)); } if (mBubbleMetadata != null) { @@ -3407,7 +3410,7 @@ public class Notification implements Parcelable * separate object, replace it with the field's version to avoid holding duplicate copies. */ private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { - if (original != null && extras.getParcelable(extraName) != null) { + if (original != null && extras.getParcelable(extraName, Parcelable.class) != null) { extras.putParcelable(extraName, original); } } @@ -7084,7 +7087,8 @@ public class Notification implements Parcelable */ public boolean hasImage() { if (isStyle(MessagingStyle.class) && extras != null) { - final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); + final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, + Parcelable.class); if (!ArrayUtils.isEmpty(messages)) { for (MessagingStyle.Message m : MessagingStyle.Message .getMessagesFromBundleArray(messages)) { @@ -8286,15 +8290,18 @@ public class Notification implements Parcelable protected void restoreFromExtras(Bundle extras) { super.restoreFromExtras(extras); - mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); - if (mUser == null) { + Person user = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); + if (user == null) { CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); mUser = new Person.Builder().setName(displayName).build(); + } else { + mUser = user; } mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); - Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); + Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class); mMessages = Message.getMessagesFromBundleArray(messages); - Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); + Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES, + Parcelable.class); mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); @@ -11962,7 +11969,8 @@ public class Notification implements Parcelable if (b == null) { return null; } - Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); + Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES, + Parcelable.class); String[] messages = null; if (parcelableMessages != null) { String[] tmp = new String[parcelableMessages.length]; @@ -12299,7 +12307,7 @@ public class Notification implements Parcelable @Nullable private static <T extends Parcelable> T[] getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass) { - final Parcelable[] array = bundle.getParcelableArray(key); + final Parcelable[] array = bundle.getParcelableArray(key, Parcelable.class); final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass(); if (arrayClass.isInstance(array) || array == null) { return (T[]) array; diff --git a/core/java/android/app/ProcessMemoryState.java b/core/java/android/app/ProcessMemoryState.java index 2c58603ecdb9..c4caa4512acc 100644 --- a/core/java/android/app/ProcessMemoryState.java +++ b/core/java/android/app/ProcessMemoryState.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.IntDef; import android.os.Parcel; import android.os.Parcelable; @@ -24,19 +25,132 @@ import android.os.Parcelable; * {@hide} */ public final class ProcessMemoryState implements Parcelable { + /** + * The type of the component this process is hosting; + * this means not hosting any components (cached). + */ + public static final int HOSTING_COMPONENT_TYPE_EMPTY = + AppProtoEnums.HOSTING_COMPONENT_TYPE_EMPTY; + + /** + * The type of the component this process is hosting; + * this means it's a system process. + */ + public static final int HOSTING_COMPONENT_TYPE_SYSTEM = + AppProtoEnums.HOSTING_COMPONENT_TYPE_SYSTEM; + + /** + * The type of the component this process is hosting; + * this means it's a persistent process. + */ + public static final int HOSTING_COMPONENT_TYPE_PERSISTENT = + AppProtoEnums.HOSTING_COMPONENT_TYPE_PERSISTENT; + + /** + * The type of the component this process is hosting; + * this means it's hosting a backup/restore agent. + */ + public static final int HOSTING_COMPONENT_TYPE_BACKUP = + AppProtoEnums.HOSTING_COMPONENT_TYPE_BACKUP; + + /** + * The type of the component this process is hosting; + * this means it's hosting an instrumentation. + */ + public static final int HOSTING_COMPONENT_TYPE_INSTRUMENTATION = + AppProtoEnums.HOSTING_COMPONENT_TYPE_INSTRUMENTATION; + + /** + * The type of the component this process is hosting; + * this means it's hosting an activity. + */ + public static final int HOSTING_COMPONENT_TYPE_ACTIVITY = + AppProtoEnums.HOSTING_COMPONENT_TYPE_ACTIVITY; + + /** + * The type of the component this process is hosting; + * this means it's hosting a broadcast receiver. + */ + public static final int HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER = + AppProtoEnums.HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER; + + /** + * The type of the component this process is hosting; + * this means it's hosting a content provider. + */ + public static final int HOSTING_COMPONENT_TYPE_PROVIDER = + AppProtoEnums.HOSTING_COMPONENT_TYPE_PROVIDER; + + /** + * The type of the component this process is hosting; + * this means it's hosting a started service. + */ + public static final int HOSTING_COMPONENT_TYPE_STARTED_SERVICE = + AppProtoEnums.HOSTING_COMPONENT_TYPE_STARTED_SERVICE; + + /** + * The type of the component this process is hosting; + * this means it's hosting a foreground service. + */ + public static final int HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE = + AppProtoEnums.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE; + + /** + * The type of the component this process is hosting; + * this means it's being bound via a service binding. + */ + public static final int HOSTING_COMPONENT_TYPE_BOUND_SERVICE = + AppProtoEnums.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; + + /** + * The type of the component this process is hosting. + * @hide + */ + @IntDef(flag = true, prefix = { "HOSTING_COMPONENT_TYPE_" }, value = { + HOSTING_COMPONENT_TYPE_EMPTY, + HOSTING_COMPONENT_TYPE_SYSTEM, + HOSTING_COMPONENT_TYPE_PERSISTENT, + HOSTING_COMPONENT_TYPE_BACKUP, + HOSTING_COMPONENT_TYPE_INSTRUMENTATION, + HOSTING_COMPONENT_TYPE_ACTIVITY, + HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER, + HOSTING_COMPONENT_TYPE_PROVIDER, + HOSTING_COMPONENT_TYPE_STARTED_SERVICE, + HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE, + HOSTING_COMPONENT_TYPE_BOUND_SERVICE, + }) + public @interface HostingComponentType {} + public final int uid; public final int pid; public final String processName; public final int oomScore; public final boolean hasForegroundServices; + /** + * The types of the components this process is hosting at the moment this snapshot is taken. + * + * Its value is the combination of {@link HostingComponentType}. + */ + public final int mHostingComponentTypes; + + /** + * The historical types of the components this process is or was hosting since it's born. + * + * Its value is the combination of {@link HostingComponentType}. + */ + public final int mHistoricalHostingComponentTypes; + public ProcessMemoryState(int uid, int pid, String processName, int oomScore, - boolean hasForegroundServices) { + boolean hasForegroundServices, int hostingComponentTypes, + int historicalHostingComponentTypes) { this.uid = uid; this.pid = pid; this.processName = processName; this.oomScore = oomScore; this.hasForegroundServices = hasForegroundServices; + this.mHostingComponentTypes = hostingComponentTypes; + this.mHistoricalHostingComponentTypes = historicalHostingComponentTypes; } private ProcessMemoryState(Parcel in) { @@ -45,6 +159,8 @@ public final class ProcessMemoryState implements Parcelable { processName = in.readString(); oomScore = in.readInt(); hasForegroundServices = in.readInt() == 1; + mHostingComponentTypes = in.readInt(); + mHistoricalHostingComponentTypes = in.readInt(); } public static final @android.annotation.NonNull Creator<ProcessMemoryState> CREATOR = new Creator<ProcessMemoryState>() { @@ -71,5 +187,7 @@ public final class ProcessMemoryState implements Parcelable { parcel.writeString(processName); parcel.writeInt(oomScore); parcel.writeInt(hasForegroundServices ? 1 : 0); + parcel.writeInt(mHostingComponentTypes); + parcel.writeInt(mHistoricalHostingComponentTypes); } } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 70c42d8f3b8a..1603cd9e2c81 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -658,15 +658,8 @@ public class WallpaperManager { return currentWallpaper; } } - if (returnDefault) { - Bitmap defaultWallpaper = mDefaultWallpaper; - if (defaultWallpaper == null || defaultWallpaper.isRecycled()) { - defaultWallpaper = getDefaultWallpaper(context, which); - synchronized (this) { - mDefaultWallpaper = defaultWallpaper; - } - } - return defaultWallpaper; + if (returnDefault || (which == FLAG_LOCK && isStaticWallpaper(FLAG_LOCK))) { + return getDefaultWallpaper(context, which); } return null; } @@ -705,7 +698,7 @@ public class WallpaperManager { } // If user wallpaper is unavailable, may be the default one instead. if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0) - && returnDefault) { + && (returnDefault || (which == FLAG_LOCK && isStaticWallpaper(FLAG_LOCK)))) { InputStream is = openDefaultWallpaper(context, which); if (is != null) { try { @@ -769,18 +762,39 @@ public class WallpaperManager { } private Bitmap getDefaultWallpaper(Context context, @SetWallpaperFlags int which) { - InputStream is = openDefaultWallpaper(context, which); - if (is != null) { - try { - BitmapFactory.Options options = new BitmapFactory.Options(); - return BitmapFactory.decodeStream(is, null, options); - } catch (OutOfMemoryError e) { + Bitmap defaultWallpaper = mDefaultWallpaper; + if (defaultWallpaper == null || defaultWallpaper.isRecycled()) { + defaultWallpaper = null; + try (InputStream is = openDefaultWallpaper(context, which)) { + if (is != null) { + BitmapFactory.Options options = new BitmapFactory.Options(); + defaultWallpaper = BitmapFactory.decodeStream(is, null, options); + } + } catch (OutOfMemoryError | IOException e) { Log.w(TAG, "Can't decode stream", e); - } finally { - IoUtils.closeQuietly(is); } } - return null; + synchronized (this) { + mDefaultWallpaper = defaultWallpaper; + } + return defaultWallpaper; + } + + /** + * Return true if there is a static wallpaper on the specified screen. + * With {@code which=}{@link #FLAG_LOCK}, always return false if the lockscreen doesn't run + * its own wallpaper engine. + */ + private boolean isStaticWallpaper(@SetWallpaperFlags int which) { + if (mService == null) { + Log.w(TAG, "WallpaperService not running"); + throw new RuntimeException(new DeadSystemException()); + } + try { + return mService.isStaticWallpaper(which); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } } @@ -882,21 +896,14 @@ public class WallpaperManager { * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} * can still access the real wallpaper on all versions. </li> * </ul> - * <br> * - * Retrieve the current system wallpaper; if - * no wallpaper is set, the system built-in static wallpaper is returned. - * This is returned as an - * abstract Drawable that you can install in a View to display whatever - * wallpaper the user has currently set. * <p> - * This method can return null if there is no system wallpaper available, if - * wallpapers are not supported in the current user, or if the calling app is not - * permitted to access the system wallpaper. + * Equivalent to {@link #getDrawable(int)} with {@code which=}{@link #FLAG_SYSTEM}. + * </p> + * + * @return A Drawable object for the requested wallpaper. * - * @return Returns a Drawable object that will draw the system wallpaper, - * or {@code null} if no system wallpaper exists or if the calling application - * is not able to access the wallpaper. + * @see #getDrawable(int) * * @throws SecurityException as described in the note */ @@ -919,23 +926,29 @@ public class WallpaperManager { * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} * can still access the real wallpaper on all versions. </li> * </ul> - * <br> * - * Retrieve the requested wallpaper; if - * no wallpaper is set, the requested built-in static wallpaper is returned. - * This is returned as an - * abstract Drawable that you can install in a View to display whatever - * wallpaper the user has currently set. * <p> - * This method can return null if the requested wallpaper is not available, if - * wallpapers are not supported in the current user, or if the calling app is not - * permitted to access the requested wallpaper. + * Retrieve the requested wallpaper for the specified wallpaper type if the wallpaper is not + * a live wallpaper. This method should not be used to display the user wallpaper on an app: + * {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WALLPAPER} should be used instead. + * </p> + * <p> + * When called with {@code which=}{@link #FLAG_SYSTEM}, + * if there is a live wallpaper on home screen, the built-in default wallpaper is returned. + * </p> + * <p> + * When called with {@code which=}{@link #FLAG_LOCK}, if there is a live wallpaper + * on lock screen, or if the lock screen and home screen share the same wallpaper engine, + * {@code null} is returned. + * </p> + * <p> + * {@link #getWallpaperInfo(int)} can be used to determine whether there is a live wallpaper + * on a specified screen type. + * </p> * - * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws + * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws * IllegalArgumentException if an invalid wallpaper is requested. - * @return Returns a Drawable object that will draw the requested wallpaper, - * or {@code null} if the requested wallpaper does not exist or if the calling application - * is not able to access the wallpaper. + * @return A Drawable object for the requested wallpaper. * * @throws SecurityException as described in the note */ @@ -943,7 +956,8 @@ public class WallpaperManager { @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable getDrawable(@SetWallpaperFlags int which) { final ColorManagementProxy cmProxy = getColorManagementProxy(); - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); + boolean returnDefault = which != FLAG_LOCK; + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, returnDefault, which, cmProxy); if (bm != null) { Drawable dr = new BitmapDrawable(mContext.getResources(), bm); dr.setDither(false); @@ -1175,15 +1189,14 @@ public class WallpaperManager { * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} * can still access the real wallpaper on all versions. </li> * </ul> - * <br> * - * Retrieve the current system wallpaper; if there is no wallpaper set, - * a null pointer is returned. This is returned as an - * abstract Drawable that you can install in a View to display whatever - * wallpaper the user has currently set. + * <p> + * Equivalent to {@link #getDrawable()}. + * </p> + * + * @return A Drawable object for the requested wallpaper. * - * @return Returns a Drawable object that will draw the wallpaper or a - * null pointer if wallpaper is unset. + * @see #getDrawable() * * @throws SecurityException as described in the note */ @@ -1206,31 +1219,23 @@ public class WallpaperManager { * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} * can still access the real wallpaper on all versions. </li> * </ul> - * <br> * - * Retrieve the requested wallpaper; if there is no wallpaper set, - * a null pointer is returned. This is returned as an - * abstract Drawable that you can install in a View to display whatever - * wallpaper the user has currently set. + * <p> + * Equivalent to {@link #getDrawable(int)}. + * </p> * - * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws + * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws * IllegalArgumentException if an invalid wallpaper is requested. - * @return Returns a Drawable object that will draw the wallpaper or a null pointer if - * wallpaper is unset. + * @return A Drawable object for the requested wallpaper. + * + * @see #getDrawable(int) * * @throws SecurityException as described in the note */ @Nullable @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable peekDrawable(@SetWallpaperFlags int which) { - final ColorManagementProxy cmProxy = getColorManagementProxy(); - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); - if (bm != null) { - Drawable dr = new BitmapDrawable(mContext.getResources(), bm); - dr.setDither(false); - return dr; - } - return null; + return getDrawable(which); } /** @@ -1246,19 +1251,14 @@ public class WallpaperManager { * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} * can still access the real wallpaper on all versions. </li> * </ul> - * <br> * - * Like {@link #getDrawable()}, but the returned Drawable has a number - * of limitations to reduce its overhead as much as possible. It will - * never scale the wallpaper (only centering it if the requested bounds - * do match the bitmap bounds, which should not be typical), doesn't - * allow setting an alpha, color filter, or other attributes, etc. The - * bounds of the returned drawable will be initialized to the same bounds - * as the wallpaper, so normally you will not need to touch it. The - * drawable also assumes that it will be used in a context running in - * the same density as the screen (not in density compatibility mode). + * <p> + * Equivalent to {@link #getFastDrawable(int)} with {@code which=}{@link #FLAG_SYSTEM}. + * </p> * - * @return Returns a Drawable object that will draw the wallpaper. + * @return A Drawable object for the requested wallpaper. + * + * @see #getFastDrawable(int) * * @throws SecurityException as described in the note */ @@ -1295,7 +1295,8 @@ public class WallpaperManager { * * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws * IllegalArgumentException if an invalid wallpaper is requested. - * @return Returns a Drawable object that will draw the wallpaper. + * @return An optimized Drawable object for the requested wallpaper, or {@code null} + * in some cases as specified in {@link #getDrawable(int)}. * * @throws SecurityException as described in the note */ @@ -1303,7 +1304,8 @@ public class WallpaperManager { @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable getFastDrawable(@SetWallpaperFlags int which) { final ColorManagementProxy cmProxy = getColorManagementProxy(); - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); + boolean returnDefault = which != FLAG_LOCK; + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, returnDefault, which, cmProxy); if (bm != null) { return new FastBitmapDrawable(bm); } @@ -1323,13 +1325,14 @@ public class WallpaperManager { * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} * can still access the real wallpaper on all versions. </li> * </ul> - * <br> * - * Like {@link #getFastDrawable()}, but if there is no wallpaper set, - * a null pointer is returned. + * <p> + * Equivalent to {@link #getFastDrawable()}. + * </p> + * + * @return An optimized Drawable object for the requested wallpaper. * - * @return Returns an optimized Drawable object that will draw the - * wallpaper or a null pointer if these is none. + * @see #getFastDrawable() * * @throws SecurityException as described in the note */ @@ -1352,31 +1355,29 @@ public class WallpaperManager { * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} * can still access the real wallpaper on all versions. </li> * </ul> - * <br> * - * Like {@link #getFastDrawable()}, but if there is no wallpaper set, - * a null pointer is returned. + * <p> + * Equivalent to {@link #getFastDrawable(int)}. + * </p> * * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws * IllegalArgumentException if an invalid wallpaper is requested. - * @return Returns an optimized Drawable object that will draw the - * wallpaper or a null pointer if these is none. + * @return An optimized Drawable object for the requested wallpaper. * * @throws SecurityException as described in the note */ @Nullable @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable peekFastDrawable(@SetWallpaperFlags int which) { - final ColorManagementProxy cmProxy = getColorManagementProxy(); - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); - if (bm != null) { - return new FastBitmapDrawable(bm); - } - return null; + return getFastDrawable(which); } /** - * Whether the wallpaper supports Wide Color Gamut or not. + * Whether the wallpaper supports Wide Color Gamut or not. This is only meant to be used by + * ImageWallpaper, and will always return false if the wallpaper for the specified screen + * is not an ImageWallpaper. This will also return false when called with {@link #FLAG_LOCK} if + * the lock and home screen share the same wallpaper engine. + * * @param which The wallpaper whose image file is to be retrieved. Must be a single * defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}. * @return true when supported. @@ -1422,7 +1423,7 @@ public class WallpaperManager { } /** - * Like {@link #getDrawable()} but returns a Bitmap. + * Like {@link #getDrawable(int)} but returns a Bitmap. * * @param hardware Asks for a hardware backed bitmap. * @param which Specifies home or lock screen @@ -1445,7 +1446,7 @@ public class WallpaperManager { } /** - * Like {@link #getDrawable()} but returns a Bitmap for the provided user. + * Like {@link #getDrawable(int)} but returns a Bitmap for the provided user. * * @param which Specifies home or lock screen * @hide @@ -1453,12 +1454,29 @@ public class WallpaperManager { @TestApi @Nullable public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) { + boolean returnDefault = which != FLAG_LOCK; + return getBitmapAsUser(userId, hardware, which, returnDefault); + } + + /** + * Overload of {@link #getBitmapAsUser(int, boolean, int)} with a returnDefault argument. + * + * @param returnDefault If true, return the default static wallpaper if no custom static + * wallpaper is set on the specified screen. + * If false, return {@code null} in that case. + * @hide + */ + @Nullable + public Bitmap getBitmapAsUser(int userId, boolean hardware, + @SetWallpaperFlags int which, boolean returnDefault) { final ColorManagementProxy cmProxy = getColorManagementProxy(); - return sGlobals.peekWallpaperBitmap(mContext, true, which, userId, hardware, cmProxy); + return sGlobals.peekWallpaperBitmap(mContext, returnDefault, + which, userId, hardware, cmProxy); } /** * Peek the dimensions of system wallpaper of the user without decoding it. + * Equivalent to {@link #peekBitmapDimensions(int)} with {@code which=}{@link #FLAG_SYSTEM}. * * @return the dimensions of system wallpaper * @hide @@ -1472,16 +1490,45 @@ public class WallpaperManager { /** * Peek the dimensions of given wallpaper of the user without decoding it. * - * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or - * {@link #FLAG_LOCK}. - * @return the dimensions of system wallpaper + * <p> + * When called with {@code which=}{@link #FLAG_SYSTEM}, if there is a live wallpaper on + * home screen, the built-in default wallpaper dimensions are returned. + * </p> + * <p> + * When called with {@code which=}{@link #FLAG_LOCK}, if there is a live wallpaper + * on lock screen, or if the lock screen and home screen share the same wallpaper engine, + * {@code null} is returned. + * </p> + * <p> + * {@link #getWallpaperInfo(int)} can be used to determine whether there is a live wallpaper + * on a specified screen type. + * </p> + * + * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}. + * @return the dimensions of specified wallpaper * @hide */ @TestApi @Nullable public Rect peekBitmapDimensions(@SetWallpaperFlags int which) { + boolean returnDefault = which != FLAG_LOCK; + return peekBitmapDimensions(which, returnDefault); + } + + /** + * Overload of {@link #peekBitmapDimensions(int)} with a returnDefault argument. + * + * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}. + * @param returnDefault If true, always return the default static wallpaper dimensions + * if no custom static wallpaper is set on the specified screen. + * If false, always return {@code null} in that case. + * @return the dimensions of specified wallpaper + * @hide + */ + @Nullable + public Rect peekBitmapDimensions(@SetWallpaperFlags int which, boolean returnDefault) { checkExactlyOneWallpaperFlagSet(which); - return sGlobals.peekWallpaperDimensions(mContext, true /* returnDefault */, which, + return sGlobals.peekWallpaperDimensions(mContext, returnDefault, which, mContext.getUserId()); } @@ -2865,21 +2912,62 @@ public class WallpaperManager { } } - // Check if the package exists - if (cn != null) { - try { - final PackageManager packageManager = context.getPackageManager(); - packageManager.getPackageInfo(cn.getPackageName(), - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); - } catch (PackageManager.NameNotFoundException e) { - cn = null; + if (!isComponentExist(context, cn)) { + cn = null; + } + + return cn; + } + + /** + * Return {@link ComponentName} of the CMF default wallpaper, or + * {@link #getDefaultWallpaperComponent(Context)} if none is defined. + * + * @hide + */ + public static ComponentName getCmfDefaultWallpaperComponent(Context context) { + ComponentName cn = null; + String[] cmfWallpaperMap = context.getResources().getStringArray( + com.android.internal.R.array.cmf_default_wallpaper_component); + if (cmfWallpaperMap == null || cmfWallpaperMap.length == 0) { + Log.d(TAG, "No CMF wallpaper config"); + return getDefaultWallpaperComponent(context); + } + + for (String entry : cmfWallpaperMap) { + String[] cmfWallpaper; + if (!TextUtils.isEmpty(entry)) { + cmfWallpaper = entry.split(","); + if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals( + cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) { + cn = ComponentName.unflattenFromString(cmfWallpaper[1]); + break; + } } } + if (!isComponentExist(context, cn)) { + cn = null; + } + return cn; } + private static boolean isComponentExist(Context context, ComponentName cn) { + if (cn == null) { + return false; + } + try { + final PackageManager packageManager = context.getPackageManager(); + packageManager.getPackageInfo(cn.getPackageName(), + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + return true; + } + /** * Register a callback for lock wallpaper observation. Only the OS may use this. * diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 56f6f8206d30..d4e231b70c3e 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2565,9 +2565,9 @@ public class PackageInstaller { * Sets the state of permissions for the package at installation. * <p/> * Granting any runtime permissions require the - * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be - * held by the caller. Revoking runtime permissions is not allowed, even during app update - * sessions. + * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS + * INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be held by the caller. Revoking runtime + * permissions is not allowed, even during app update sessions. * <p/> * Holders without the permission are allowed to change the following special permissions: * <p/> diff --git a/core/java/android/nfc/NfcAntennaInfo.java b/core/java/android/nfc/NfcAntennaInfo.java index d54fcd2ed5b3..b002ca21e8e3 100644 --- a/core/java/android/nfc/NfcAntennaInfo.java +++ b/core/java/android/nfc/NfcAntennaInfo.java @@ -85,8 +85,8 @@ public final class NfcAntennaInfo implements Parcelable { this.mDeviceHeight = in.readInt(); this.mDeviceFoldable = in.readByte() != 0; this.mAvailableNfcAntennas = new ArrayList<>(); - in.readParcelableList(this.mAvailableNfcAntennas, - AvailableNfcAntenna.class.getClassLoader()); + in.readTypedList(this.mAvailableNfcAntennas, + AvailableNfcAntenna.CREATOR); } public static final @NonNull Parcelable.Creator<NfcAntennaInfo> CREATOR = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8cdb568b407c..73c29d4058cd 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3036,9 +3036,7 @@ public final class Settings { public void destroy() { try { - // If this process is the system server process, mArray is the same object as - // the memory int array kept inside SettingsProvider, so skipping the close() - if (!Settings.isInSystemServer() && !mArray.isClosed()) { + if (!mArray.isClosed()) { mArray.close(); } } catch (IOException e) { @@ -3218,8 +3216,9 @@ public final class Settings { @UnsupportedAppUsage public String getStringForUser(ContentResolver cr, String name, final int userHandle) { final boolean isSelf = (userHandle == UserHandle.myUserId()); + final boolean useCache = isSelf && !isInSystemServer(); boolean needsGenerationTracker = false; - if (isSelf) { + if (useCache) { synchronized (NameValueCache.this) { final GenerationTracker generationTracker = mGenerationTrackers.get(name); if (generationTracker != null) { @@ -3365,9 +3364,12 @@ public final class Settings { } } } else { - if (LOCAL_LOGV) Log.i(TAG, "call-query of user " + userHandle - + " by " + UserHandle.myUserId() - + " so not updating cache"); + if (DEBUG || LOCAL_LOGV) { + Log.i(TAG, "call-query of user " + userHandle + + " by " + UserHandle.myUserId() + + (isInSystemServer() ? " in system_server" : "") + + " so not updating cache"); + } } return value; } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 90e8cedab622..4b761c105803 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -389,7 +389,7 @@ public class VoiceInteractionService extends Service { // It's still guaranteed to have been stopped. // This helps with cases where the voice interaction implementation is changed // by the user. - safelyShutdownAllHotwordDetectors(); + safelyShutdownAllHotwordDetectors(true); } /** @@ -715,7 +715,7 @@ public class VoiceInteractionService extends Service { synchronized (mLock) { if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) { // Allow only one concurrent recognition via the APIs. - safelyShutdownAllHotwordDetectors(); + safelyShutdownAllHotwordDetectors(false); } else { for (HotwordDetector detector : mActiveDetectors) { if (detector.isUsingSandboxedDetectionService() @@ -878,7 +878,7 @@ public class VoiceInteractionService extends Service { synchronized (mLock) { if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) { // Allow only one concurrent recognition via the APIs. - safelyShutdownAllHotwordDetectors(); + safelyShutdownAllHotwordDetectors(false); } else { for (HotwordDetector detector : mActiveDetectors) { if (!detector.isUsingSandboxedDetectionService()) { @@ -1062,11 +1062,14 @@ public class VoiceInteractionService extends Service { return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null; } - private void safelyShutdownAllHotwordDetectors() { + private void safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector) { synchronized (mLock) { mActiveDetectors.forEach(detector -> { try { - detector.destroy(); + if (detector != mActiveVisualQueryDetector.getInitializationDelegate() + || shouldShutDownVisualQueryDetector) { + detector.destroy(); + } } catch (Exception ex) { Log.i(TAG, "exception destroying HotwordDetector", ex); } @@ -1116,6 +1119,8 @@ public class VoiceInteractionService extends Service { pw.println(); }); } + pw.println("Available Model Enrollment Applications:"); + pw.println(" " + mKeyphraseEnrollmentInfo); } } } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 8d84e4413635..230f51191ee8 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -184,6 +184,7 @@ public abstract class WallpaperService extends Service { private static final long DIMMING_ANIMATION_DURATION_MS = 300L; + @GuardedBy("itself") private final ArrayMap<IBinder, IWallpaperEngineWrapper> mActiveEngines = new ArrayMap<>(); private Handler mBackgroundHandler; @@ -2514,10 +2515,12 @@ public abstract class WallpaperService extends Service { // if they are visible, so we need to toggle the state to get their attention. if (!mEngine.mDestroyed) { mEngine.detach(); - for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { - if (engineWrapper.mEngine != null && engineWrapper.mEngine.mVisible) { - engineWrapper.mEngine.doVisibilityChanged(false); - engineWrapper.mEngine.doVisibilityChanged(true); + synchronized (mActiveEngines) { + for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { + if (engineWrapper.mEngine != null && engineWrapper.mEngine.mVisible) { + engineWrapper.mEngine.doVisibilityChanged(false); + engineWrapper.mEngine.doVisibilityChanged(true); + } } } } @@ -2699,7 +2702,9 @@ public abstract class WallpaperService extends Service { IWallpaperEngineWrapper engineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType, isPreview, reqWidth, reqHeight, padding, displayId, which); - mActiveEngines.put(windowToken, engineWrapper); + synchronized (mActiveEngines) { + mActiveEngines.put(windowToken, engineWrapper); + } if (DEBUG) { Slog.v(TAG, "IWallpaperServiceWrapper Attaching window token " + windowToken); } @@ -2708,7 +2713,10 @@ public abstract class WallpaperService extends Service { @Override public void detach(IBinder windowToken) { - IWallpaperEngineWrapper engineWrapper = mActiveEngines.remove(windowToken); + IWallpaperEngineWrapper engineWrapper; + synchronized (mActiveEngines) { + engineWrapper = mActiveEngines.remove(windowToken); + } if (engineWrapper == null) { Log.w(TAG, "Engine for window token " + windowToken + " already detached"); return; @@ -2734,10 +2742,12 @@ public abstract class WallpaperService extends Service { public void onDestroy() { Trace.beginSection("WPMS.onDestroy"); super.onDestroy(); - for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { - engineWrapper.destroy(); + synchronized (mActiveEngines) { + for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { + engineWrapper.destroy(); + } + mActiveEngines.clear(); } - mActiveEngines.clear(); if (mBackgroundThread != null) { // onDestroy might be called without a previous onCreate if WallpaperService was // instantiated manually. While this is a misuse of the API, some things break @@ -2768,14 +2778,18 @@ public abstract class WallpaperService extends Service { @Override protected void dump(FileDescriptor fd, PrintWriter out, String[] args) { out.print("State of wallpaper "); out.print(this); out.println(":"); - for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { - Engine engine = engineWrapper.mEngine; - if (engine == null) { - Slog.w(TAG, "Engine for wrapper " + engineWrapper + " not attached"); - continue; - } - out.print(" Engine "); out.print(engine); out.println(":"); - engine.dump(" ", fd, out, args); + synchronized (mActiveEngines) { + for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { + Engine engine = engineWrapper.mEngine; + if (engine == null) { + Slog.w(TAG, "Engine for wrapper " + engineWrapper + " not attached"); + continue; + } + out.print(" Engine "); + out.print(engine); + out.println(":"); + engine.dump(" ", fd, out, args); + } } } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 705a2ce05bc6..6af160c04b4b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -9845,10 +9845,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * <p><b>Note:</b> Setting the mode as {@link #IMPORTANT_FOR_AUTOFILL_NO} or * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} does not guarantee the view (and its - * children) will be always be considered not important; for example, when the user explicitly - * makes an autofill request, all views are considered important. See - * {@link #isImportantForAutofill()} for more details about how the View's importance for - * autofill is used. + * children) will not be used for autofill purpose; for example, when the user explicitly + * makes an autofill request, all views are included in the ViewStructure, and starting in + * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} the system uses other factors along + * with importance to determine the autofill behavior. See {@link #isImportantForAutofill()} + * for more details about how the View's importance for autofill is used. * * @param mode {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, {@link #IMPORTANT_FOR_AUTOFILL_YES}, * {@link #IMPORTANT_FOR_AUTOFILL_NO}, {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}, @@ -9894,21 +9895,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <li>otherwise, it returns {@code false}. * </ol> * - * <p>When a view is considered important for autofill: - * <ul> - * <li>The view might automatically trigger an autofill request when focused on. - * <li>The contents of the view are included in the {@link ViewStructure} used in an autofill - * request. - * </ul> - * - * <p>On the other hand, when a view is considered not important for autofill: - * <ul> - * <li>The view never automatically triggers autofill requests, but it can trigger a manual - * request through {@link AutofillManager#requestAutofill(View)}. - * <li>The contents of the view are not included in the {@link ViewStructure} used in an - * autofill request, unless the request has the - * {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag. - * </ul> + * <p> The behavior of importances depends on Android version: + * <ol> + * <li>For {@link android.os.Build.VERSION_CODES#TIRAMISU} and below: + * <ol> + * <li>When a view is considered important for autofill: + * <ol> + * <li>The view might automatically trigger an autofill request when focused on. + * <li>The contents of the view are included in the {@link ViewStructure} used in an + * autofill request. + * </ol> + * <li>On the other hand, when a view is considered not important for autofill: + * <ol> + * <li>The view never automatically triggers autofill requests, but it can trigger a + * manual request through {@link AutofillManager#requestAutofill(View)}. + * <li>The contents of the view are not included in the {@link ViewStructure} used in + * an autofill request, unless the request has the + * {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag. + * </ol> + * </ol> + * <li>For {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above: + * <ol> + * <li>The system uses importance, along with other view properties and other optimization + * factors, to determine if a view should trigger autofill on focus. + * <li>The contents of {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, + * {@link #IMPORTANT_FOR_AUTOFILL_YES}, {@link #IMPORTANT_FOR_AUTOFILL_NO}, + * {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}, and + * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} views will be included in the + * {@link ViewStructure} used in an autofill request. + * </ol> + * </ol> * * @return whether the view is considered important for autofill. * diff --git a/core/java/android/view/autofill/AutofillClientController.java b/core/java/android/view/autofill/AutofillClientController.java index 3a8e8027b88e..2f7adaa29fed 100644 --- a/core/java/android/view/autofill/AutofillClientController.java +++ b/core/java/android/view/autofill/AutofillClientController.java @@ -19,6 +19,7 @@ package android.view.autofill; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; +import android.app.ActivityOptions; import android.app.Application; import android.content.ComponentName; import android.content.Intent; @@ -486,8 +487,11 @@ public final class AutofillClientController implements AutofillManager.AutofillC public void autofillClientAuthenticate(int authenticationId, IntentSender intent, Intent fillInIntent, boolean authenticateInline) { try { + ActivityOptions activityOptions = ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); mActivity.startIntentSenderForResult(intent, AUTO_FILL_AUTH_WHO_PREFIX, - authenticationId, fillInIntent, 0, 0, null); + authenticationId, fillInIntent, 0, 0, activityOptions.toBundle()); } catch (IntentSender.SendIntentException e) { Log.e(TAG, "authenticate() failed for intent:" + intent, e); } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index a6e9d4d2094f..5d121ad2957f 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1464,6 +1464,13 @@ public final class AutofillManager { } synchronized (mLock) { + if (mAllTrackedViews.contains(id)) { + // The id is tracked and will not trigger pre-fill request again. + return; + } + + // Add the id as tracked to avoid triggering fill request again and again. + mAllTrackedViews.add(id); if (mTrackedViews != null) { // To support the fill dialog can show for the autofillable Views in // different pages but in the same Activity. We need to reset the @@ -4064,11 +4071,6 @@ public final class AutofillManager { } void checkViewState(AutofillId id) { - if (mAllTrackedViews.contains(id)) { - return; - } - // Add the id as tracked to avoid triggering fill request again and again. - mAllTrackedViews.add(id); if (mHasNewTrackedView) { return; } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 3165654d806d..c28950662fb3 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -726,6 +726,12 @@ public class RemoteViews implements Parcelable, Filter { mActions.get(i).visitUris(visitor); } } + if (mLandscape != null) { + mLandscape.visitUris(visitor); + } + if (mPortrait != null) { + mPortrait.visitUris(visitor); + } } private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java index 6bc7ac6cd7d9..1f7640d97b4d 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java @@ -149,11 +149,13 @@ public final class AccessibilityStatsLogUtils { * * @param mode The activated magnification mode. * @param duration The duration in milliseconds during the magnification is activated. + * @param scale The last magnification scale for the activation */ - public static void logMagnificationUsageState(int mode, long duration) { + public static void logMagnificationUsageState(int mode, long duration, float scale) { FrameworkStatsLog.write(FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED, convertToLoggingMagnificationMode(mode), - duration); + duration, + convertToLoggingMagnificationScale(scale)); } /** @@ -254,4 +256,8 @@ public final class AccessibilityStatsLogUtils { return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_UNKNOWN_MODE; } } + + private static int convertToLoggingMagnificationScale(float scale) { + return (int) (scale * 100); + } } diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index c1445035ca33..f2776353fd1b 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -305,10 +305,17 @@ public class LatencyTracker { private final SparseArray<ActionProperties> mActionPropertiesMap = new SparseArray<>(); @GuardedBy("mLock") private boolean mEnabled; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = + this::updateProperties; // Wrapping this in a holder class achieves lazy loading behavior private static final class SLatencyTrackerHolder { - private static final LatencyTracker sLatencyTracker = new LatencyTracker(); + private static final LatencyTracker sLatencyTracker; + + static { + sLatencyTracker = new LatencyTracker(); + sLatencyTracker.startListeningForLatencyTrackerConfigChanges(); + } } public static LatencyTracker getInstance(Context context) { @@ -319,31 +326,16 @@ public class LatencyTracker { * Constructor for LatencyTracker * * <p>This constructor is only visible for test classes to inject their own consumer callbacks + * + * @param startListeningForPropertyChanges If set, constructor will register for device config + * property updates prior to returning. If not set, + * {@link #startListeningForLatencyTrackerConfigChanges} must be called + * to start listening. */ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) @VisibleForTesting public LatencyTracker() { mEnabled = DEFAULT_ENABLED; - - final Context context = ActivityThread.currentApplication(); - if (context != null - && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) { - // Post initialization to the background in case we're running on the main thread. - BackgroundThread.getHandler().post(() -> this.updateProperties( - DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER))); - DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER, - BackgroundThread.getExecutor(), this::updateProperties); - } else { - if (DEBUG) { - if (context == null) { - Log.d(TAG, "No application for " + ActivityThread.currentActivityThread()); - } else { - Log.d(TAG, "Initialized the LatencyTracker." - + " (No READ_DEVICE_CONFIG permission to change configs)" - + " enabled=" + mEnabled + ", package=" + context.getPackageName()); - } - } - } } private void updateProperties(DeviceConfig.Properties properties) { @@ -366,6 +358,54 @@ public class LatencyTracker { } /** + * Test method to start listening to {@link DeviceConfig} properties changes. + * + * <p>During testing, a {@link LatencyTracker} it is desired to stop and start listening for + * config updates. + * + * <p>This is not used for production usages of this class outside of testing as we are + * using a single static object. + */ + @VisibleForTesting + public void startListeningForLatencyTrackerConfigChanges() { + final Context context = ActivityThread.currentApplication(); + if (context != null + && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) { + // Post initialization to the background in case we're running on the main thread. + BackgroundThread.getHandler().post(() -> this.updateProperties( + DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER))); + DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER, + BackgroundThread.getExecutor(), mOnPropertiesChangedListener); + } else { + if (DEBUG) { + if (context == null) { + Log.d(TAG, "No application for " + ActivityThread.currentActivityThread()); + } else { + synchronized (mLock) { + Log.d(TAG, "Initialized the LatencyTracker." + + " (No READ_DEVICE_CONFIG permission to change configs)" + + " enabled=" + mEnabled + ", package=" + context.getPackageName()); + } + } + } + } + } + + /** + * Test method to stop listening to {@link DeviceConfig} properties changes. + * + * <p>During testing, a {@link LatencyTracker} it is desired to stop and start listening for + * config updates. + * + * <p>This is not used for production usages of this class outside of testing as we are + * using a single static object. + */ + @VisibleForTesting + public void stopListeningForLatencyTrackerConfigChanges() { + DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); + } + + /** * A helper method to translate action type to name. * * @param atomsProtoAction the action type defined in AtomsProto.java diff --git a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java index 8192ffd10230..f4e9e3064f3d 100644 --- a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java +++ b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java @@ -105,6 +105,13 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGr } if (recyclerView.requestChildRectangleOnScreen(anchor, input, true)) { + if (anchor.getParent() == null) { + // BUG(b/239050369): Check if the tracked anchor view is still attached. + Log.w(TAG, "Bug: anchor view " + anchor + " is detached after scrolling"); + resultConsumer.accept(result); // empty result + return; + } + int scrolled = prevAnchorTop - anchor.getTop(); // inverse of movement mScrollDelta += scrolled; // view.top-- is equivalent to parent.scrollY++ result.scrollDelta = mScrollDelta; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 17d84021816f..984e2cae68dc 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1392,9 +1392,6 @@ <!-- Number of notifications to keep in the notification service historical archive --> <integer name="config_notificationServiceArchiveSize">100</integer> - <!-- List of packages that will be able to use full screen intent in notifications by default --> - <string-array name="config_useFullScreenIntentPackages" translatable="false" /> - <!-- Allow the menu hard key to be disabled in LockScreen on some devices --> <bool name="config_disableMenuKeyInLockScreen">false</bool> @@ -1820,6 +1817,16 @@ specified --> <string name="default_wallpaper_component" translatable="false">@null</string> + <!-- CMF colors to default wallpaper component map, the component with color matching the device + color will be the cmf default wallpapers. The default wallpaper will be default wallpaper + component if not specified. + + E.g. for SLV color, and com.android.example/com.android.example.SlVDefaultWallpaper + <item>SLV,com.android.example/com.android.example.SlVDefaultWallpaper</item> --> + <string-array name="cmf_default_wallpaper_component" translatable="false"> + <!-- Add packages here --> + </string-array> + <!-- By default a product has no distinct default lock wallpaper --> <item name="default_lock_wallpaper" type="drawable">@null</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e3697bba3f95..5dcf284e29fd 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2020,7 +2020,6 @@ <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" /> <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" /> <java-symbol type="integer" name="config_notificationServiceArchiveSize" /> - <java-symbol type="array" name="config_useFullScreenIntentPackages" /> <java-symbol type="integer" name="config_previousVibrationsDumpLimit" /> <java-symbol type="integer" name="config_defaultVibrationAmplitude" /> <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" /> @@ -2110,6 +2109,7 @@ <java-symbol type="string" name="data_usage_rapid_body" /> <java-symbol type="string" name="data_usage_rapid_app_body" /> <java-symbol type="string" name="default_wallpaper_component" /> + <java-symbol type="array" name="cmf_default_wallpaper_component" /> <java-symbol type="string" name="device_storage_monitor_notification_channel" /> <java-symbol type="string" name="dlg_ok" /> <java-symbol type="string" name="dump_heap_notification" /> diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 31c5a761ac1f..963014e0bb50 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -24,6 +24,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -33,6 +37,8 @@ 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; import android.os.Binder; import android.os.Looper; @@ -58,6 +64,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; /** * Tests for RemoteViews. @@ -703,4 +710,61 @@ public class RemoteViewsTest { return null; } } + + @Test + public void visitUris() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + + final Uri imageUri = Uri.parse("content://media/image"); + final Icon icon1 = Icon.createWithContentUri("content://media/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://media/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://media/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://media/icon4"); + views.setImageViewUri(R.id.image, imageUri); + views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUri)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + + @Test + public void visitUris_separateOrientation() { + final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://landscape/image"); + final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4"); + landscape.setImageViewUri(R.id.image, imageUriL); + landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews portrait = new RemoteViews(mPackage, 33); + final Uri imageUriP = Uri.parse("content://portrait/image"); + final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1"); + final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2"); + final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3"); + final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4"); + portrait.setImageViewUri(R.id.image, imageUriP); + portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P); + + RemoteViews views = new RemoteViews(landscape, portrait); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriP)); + verify(visitor, times(1)).accept(eq(icon1P.getUri())); + verify(visitor, times(1)).accept(eq(icon2P.getUri())); + verify(visitor, times(1)).accept(eq(icon3P.getUri())); + verify(visitor, times(1)).accept(eq(icon4P.getUri())); + } } diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java index 645324d57ea9..584ad205a55d 100644 --- a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java @@ -28,12 +28,12 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.provider.DeviceConfig; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; import com.android.internal.util.LatencyTracker.ActionProperties; import com.google.common.truth.Expect; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -48,7 +48,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -@SmallTest @RunWith(AndroidJUnit4.class) public class LatencyTrackerTest { private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__"; @@ -65,6 +64,11 @@ public class LatencyTrackerTest { mLatencyTracker = FakeLatencyTracker.create(); } + @After + public void tearDown() { + mLatencyTracker.stopListeningForLatencyTrackerConfigChanges(); + } + @Test public void testCujsMapToEnumsCorrectly() { List<Field> actions = getAllActionFields(); diff --git a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java index 61e976bee35e..76e69bf35aaf 100644 --- a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java +++ b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java @@ -25,8 +25,6 @@ import android.provider.DeviceConfig; import android.util.Log; import android.util.SparseArray; -import androidx.annotation.Nullable; - import com.android.internal.annotations.GuardedBy; import com.google.common.collect.ImmutableMap; @@ -51,15 +49,17 @@ public final class FakeLatencyTracker extends LatencyTracker { private final List<String> mPerfettoTraceNamesTriggered; private final AtomicReference<SparseArray<ActionProperties>> mLastPropertiesUpdate = new AtomicReference<>(); - @Nullable - @GuardedBy("mLock") - private Callable<Boolean> mShouldClosePropertiesUpdatedCallable = null; + private final AtomicReference<Callable<Boolean>> mShouldClosePropertiesUpdatedCallable = + new AtomicReference<>(); private final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable(); public static FakeLatencyTracker create() throws Exception { Log.i(TAG, "create"); disableForAllActions(); + Log.i(TAG, "done disabling all actions"); FakeLatencyTracker fakeLatencyTracker = new FakeLatencyTracker(); + Log.i(TAG, "done creating tracker object"); + fakeLatencyTracker.startListeningForLatencyTrackerConfigChanges(); // always return the fake in the disabled state and let the client control the desired state fakeLatencyTracker.waitForGlobalEnabledState(false); fakeLatencyTracker.waitForAllPropertiesEnableState(false); @@ -131,27 +131,25 @@ public final class FakeLatencyTracker extends LatencyTracker { @Override public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) { Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties); + mLastPropertiesUpdate.set(actionProperties); - synchronized (mLock) { - if (mShouldClosePropertiesUpdatedCallable != null) { - try { - boolean shouldClosePropertiesUpdated = - mShouldClosePropertiesUpdatedCallable.call(); - Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result=" - + shouldClosePropertiesUpdated); - if (shouldClosePropertiesUpdated) { - Log.i(TAG, "shouldClosePropertiesUpdatedCallable=true, opening condition"); - mShouldClosePropertiesUpdatedCallable = null; - mDeviceConfigPropertiesUpdated.open(); - } - } catch (Exception e) { - Log.e(TAG, "exception when calling callable", e); - throw new RuntimeException(e); + Callable<Boolean> shouldClosePropertiesUpdated = + mShouldClosePropertiesUpdatedCallable.get(); + if (shouldClosePropertiesUpdated != null) { + try { + boolean result = shouldClosePropertiesUpdated.call(); + Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result=" + result); + if (result) { + mShouldClosePropertiesUpdatedCallable.set(null); + mDeviceConfigPropertiesUpdated.open(); } - } else { - Log.i(TAG, "no conditional callable set, opening condition"); - mDeviceConfigPropertiesUpdated.open(); + } catch (Exception e) { + Log.e(TAG, "exception when calling callable", e); + throw new RuntimeException(e); } + } else { + Log.i(TAG, "no conditional callable set, opening condition"); + mDeviceConfigPropertiesUpdated.open(); } } @@ -175,107 +173,82 @@ public final class FakeLatencyTracker extends LatencyTracker { public void waitForAllPropertiesEnableState(boolean enabledState) throws Exception { Log.i(TAG, "waitForAllPropertiesEnableState: enabledState=" + enabledState); - synchronized (mLock) { - Log.i(TAG, "closing condition"); - mDeviceConfigPropertiesUpdated.close(); - // Update the callable to only close the properties updated condition when all the - // desired properties have been updated. The DeviceConfig callbacks may happen multiple - // times so testing the resulting updates is required. - mShouldClosePropertiesUpdatedCallable = () -> { - Log.i(TAG, "verifying if last properties update has all properties enable=" - + enabledState); - SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); - if (newProperties != null) { - for (int i = 0; i < newProperties.size(); i++) { - if (newProperties.get(i).isEnabled() != enabledState) { - return false; - } + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + waitForPropertiesCondition(() -> { + Log.i(TAG, "verifying if last properties update has all properties enable=" + + enabledState); + SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); + if (newProperties != null) { + for (int i = 0; i < newProperties.size(); i++) { + if (newProperties.get(i).isEnabled() != enabledState) { + return false; } } - return true; - }; - if (mShouldClosePropertiesUpdatedCallable.call()) { - return; } - } - Log.i(TAG, "waiting for condition"); - mDeviceConfigPropertiesUpdated.block(); + return true; + }); } public void waitForMatchingActionProperties(ActionProperties actionProperties) throws Exception { Log.i(TAG, "waitForMatchingActionProperties: actionProperties=" + actionProperties); - synchronized (mLock) { - Log.i(TAG, "closing condition"); - mDeviceConfigPropertiesUpdated.close(); - // Update the callable to only close the properties updated condition when all the - // desired properties have been updated. The DeviceConfig callbacks may happen multiple - // times so testing the resulting updates is required. - mShouldClosePropertiesUpdatedCallable = () -> { - Log.i(TAG, "verifying if last properties update contains matching property =" - + actionProperties); - SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); - if (newProperties != null) { - if (newProperties.size() > 0) { - return newProperties.get(actionProperties.getAction()).equals( - actionProperties); - } + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + waitForPropertiesCondition(() -> { + Log.i(TAG, "verifying if last properties update contains matching property =" + + actionProperties); + SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); + if (newProperties != null) { + if (newProperties.size() > 0) { + return newProperties.get(actionProperties.getAction()).equals( + actionProperties); } - return false; - }; - if (mShouldClosePropertiesUpdatedCallable.call()) { - return; } - } - Log.i(TAG, "waiting for condition"); - mDeviceConfigPropertiesUpdated.block(); + return false; + }); } public void waitForActionEnabledState(int action, boolean enabledState) throws Exception { Log.i(TAG, "waitForActionEnabledState:" + " action=" + action + ", enabledState=" + enabledState); - synchronized (mLock) { - Log.i(TAG, "closing condition"); - mDeviceConfigPropertiesUpdated.close(); - // Update the callable to only close the properties updated condition when all the - // desired properties have been updated. The DeviceConfig callbacks may happen multiple - // times so testing the resulting updates is required. - mShouldClosePropertiesUpdatedCallable = () -> { - Log.i(TAG, "verifying if last properties update contains action=" + action - + ", enabledState=" + enabledState); - SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); - if (newProperties != null) { - if (newProperties.size() > 0) { - return newProperties.get(action).isEnabled() == enabledState; - } + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + waitForPropertiesCondition(() -> { + Log.i(TAG, "verifying if last properties update contains action=" + action + + ", enabledState=" + enabledState); + SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); + if (newProperties != null) { + if (newProperties.size() > 0) { + return newProperties.get(action).isEnabled() == enabledState; } - return false; - }; - if (mShouldClosePropertiesUpdatedCallable.call()) { - return; } - } - Log.i(TAG, "waiting for condition"); - mDeviceConfigPropertiesUpdated.block(); + return false; + }); } public void waitForGlobalEnabledState(boolean enabledState) throws Exception { Log.i(TAG, "waitForGlobalEnabledState: enabledState=" + enabledState); - synchronized (mLock) { - Log.i(TAG, "closing condition"); - mDeviceConfigPropertiesUpdated.close(); - // Update the callable to only close the properties updated condition when all the - // desired properties have been updated. The DeviceConfig callbacks may happen multiple - // times so testing the resulting updates is required. - mShouldClosePropertiesUpdatedCallable = () -> { - //noinspection deprecation - return isEnabled() == enabledState; - }; - if (mShouldClosePropertiesUpdatedCallable.call()) { - return; - } + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + waitForPropertiesCondition(() -> { + //noinspection deprecation + return isEnabled() == enabledState; + }); + } + + public void waitForPropertiesCondition(Callable<Boolean> shouldClosePropertiesUpdatedCallable) + throws Exception { + mShouldClosePropertiesUpdatedCallable.set(shouldClosePropertiesUpdatedCallable); + mDeviceConfigPropertiesUpdated.close(); + if (!shouldClosePropertiesUpdatedCallable.call()) { + Log.i(TAG, "waiting for mDeviceConfigPropertiesUpdated condition"); + mDeviceConfigPropertiesUpdated.block(); } - Log.i(TAG, "waiting for condition"); - mDeviceConfigPropertiesUpdated.block(); + Log.i(TAG, "waitForPropertiesCondition: returning"); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index af52350f5b48..c654c1b0d431 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -337,6 +337,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return isTaskInSplitScreen(taskId) && isSplitScreenVisible(); } + /** Check whether the task is the single-top root or the root of one of the stages. */ + public boolean isTaskRootOrStageRoot(int taskId) { + return mStageCoordinator.isRootOrStageRoot(taskId); + } + public @SplitPosition int getSplitPosition(int taskId) { return mStageCoordinator.getSplitPosition(taskId); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index b8373f3548ca..749549d1ca55 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -377,6 +377,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return STAGE_TYPE_UNDEFINED; } + boolean isRootOrStageRoot(int taskId) { + if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskId) { + return true; + } + return mMainStage.isRootTaskId(taskId) || mSideStage.isRootTaskId(taskId); + } + boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition, WindowContainerTransaction wct) { prepareEnterSplitScreen(wct, task, stagePosition); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 18b09b090794..e2e9270cd6cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -286,6 +286,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } } + boolean isRootTaskId(int taskId) { + return mRootTaskInfo != null && mRootTaskInfo.taskId == taskId; + } + void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY, boolean immediately) { if (mSplitDecorManager != null && mRootTaskInfo != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index dfad8b901a1b..21dca95d056a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -37,12 +37,8 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; -import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_RELAUNCH; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; @@ -93,7 +89,6 @@ import android.view.Choreographer; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowManager; -import android.view.WindowManager.TransitionType; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Transformation; @@ -346,9 +341,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { continue; } final boolean isTask = change.getTaskInfo() != null; + final int mode = change.getMode(); boolean isSeamlessDisplayChange = false; - if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) { + if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) { if (info.getType() == TRANSIT_CHANGE) { final int anim = getRotationAnimationHint(change, info, mDisplayController); isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS; @@ -364,7 +360,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - if (change.getMode() == TRANSIT_CHANGE) { + if (mode == TRANSIT_CHANGE) { // If task is child task, only set position in parent and update crop when needed. if (isTask && change.getParent() != null && info.getChange(change.getParent()).getTaskInfo() != null) { @@ -413,8 +409,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // Hide the invisible surface directly without animating it if there is a display // rotation animation playing. - if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType( - change.getMode())) { + if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType(mode)) { startTransaction.hide(change.getLeash()); continue; } @@ -430,13 +425,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition); if (a != null) { if (isTask) { - final @TransitionType int type = info.getType(); - final boolean isOpenOrCloseTransition = type == TRANSIT_OPEN - || type == TRANSIT_CLOSE - || type == TRANSIT_TO_FRONT - || type == TRANSIT_TO_BACK; final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0; - if (isOpenOrCloseTransition && !isTranslucent + if (!isTranslucent && TransitionUtil.isOpenOrCloseMode(mode) + && TransitionUtil.isOpenOrCloseMode(info.getType()) && wallpaperTransit == WALLPAPER_TRANSITION_NONE) { // Use the overview background as the background for the animation final Context uiContext = ActivityThread.currentActivityThread() @@ -461,7 +452,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { backgroundColorForTransition); if (!isTask && a.hasExtension()) { - if (!TransitionUtil.isOpeningType(change.getMode())) { + if (!TransitionUtil.isOpeningType(mode)) { // Can screenshot now (before startTransaction is applied) edgeExtendWindow(change, a, startTransaction, finishTransaction); } else { @@ -472,7 +463,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - final Rect clipRect = TransitionUtil.isClosingType(change.getMode()) + final Rect clipRect = TransitionUtil.isClosingType(mode) ? new Rect(mRotator.getEndBoundsInStartRotation(change)) : new Rect(change.getEndAbsBounds()); clipRect.offsetTo(0, 0); 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 fda943d7dc02..ab27c55f20dc 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 @@ -501,7 +501,9 @@ public class Transitions implements RemoteCallable<Transitions>, */ private static void setupAnimHierarchy(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { - boolean isOpening = isOpeningType(info.getType()); + final int type = info.getType(); + final boolean isOpening = isOpeningType(type); + final boolean isClosing = isClosingType(type); for (int i = 0; i < info.getRootCount(); ++i) { t.show(info.getRoot(i).getLeash()); } @@ -554,7 +556,13 @@ public class Transitions implements RemoteCallable<Transitions>, layer = zSplitLine + numChanges - i; } } else { // CHANGE or other - layer = zSplitLine + numChanges - i; + if (isClosing) { + // Put below CLOSE mode. + layer = zSplitLine - i; + } else { + // Put above CLOSE mode. + layer = zSplitLine + numChanges - i; + } } t.setLayer(leash, layer); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java index 7f5d0359515e..402b0ce7c87c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java @@ -68,6 +68,12 @@ public class TransitionUtil { return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK; } + /** Returns {@code true} if the transition is opening or closing mode. */ + public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) { + return mode == TRANSIT_OPEN || mode == TRANSIT_CLOSE + || mode == TRANSIT_TO_FRONT || mode == TRANSIT_TO_BACK; + } + /** Returns {@code true} if the transition has a display change. */ public static boolean hasDisplayChange(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 2bb3cceb257f..39fb7936747e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; -import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.util.SparseArray; @@ -186,8 +185,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); - final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback( - windowDecoration, taskInfo); + final DragPositioningCallback dragPositioningCallback = + new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController, + null /* disallowedAreaForEndBounds */); final CaptionTouchEventListener touchEventListener = new CaptionTouchEventListener(taskInfo, dragPositioningCallback); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); @@ -198,17 +198,6 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { setupCaptionColor(taskInfo, windowDecoration); } - private FluidResizeTaskPositioner createDragPositioningCallback( - CaptionWindowDecoration windowDecoration, RunningTaskInfo taskInfo) { - final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width(); - final int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId) - .stableInsets().top; - final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth, - statusBarHeight); - return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, - mDisplayController, disallowedAreaForEndBounds); - } - private class CaptionTouchEventListener implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 0b821844e46c..54babce13205 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -760,6 +760,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; + if (mSplitScreenController.isPresent() + && mSplitScreenController.get().isTaskRootOrStageRoot(taskInfo.taskId)) { + return false; + } return DesktopModeStatus.isProto2Enabled() && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD && mDisplayController.getDisplayContext(taskInfo.displayId) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java index 9bcb77f03abd..9f79d212a7b9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java @@ -21,6 +21,8 @@ import android.graphics.Rect; import android.view.SurfaceControl; import android.window.WindowContainerTransaction; +import androidx.annotation.Nullable; + import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -42,24 +44,24 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { private final Rect mRepositionTaskBounds = new Rect(); // If a task move (not resize) finishes in this region, the positioner will not attempt to // finalize the bounds there using WCT#setBounds - private final Rect mDisallowedAreaForEndBounds = new Rect(); + private final Rect mDisallowedAreaForEndBounds; private boolean mHasDragResized; private int mCtrlType; FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, - DisplayController displayController, Rect disallowedAreaForEndBounds) { + DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds) { this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds, dragStartListener -> {}, SurfaceControl.Transaction::new); } FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, - DisplayController displayController, Rect disallowedAreaForEndBounds, + DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds, DragPositioningCallbackUtility.DragStartListener dragStartListener, Supplier<SurfaceControl.Transaction> supplier) { mTaskOrganizer = taskOrganizer; mWindowDecoration = windowDecoration; mDisplayController = displayController; - mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds); + mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds); mDragStartListener = dragStartListener; mTransactionSupplier = supplier; } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 78ee81921f25..c85ffd459bd8 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -38,6 +38,7 @@ import com.android.credentialmanager.createflow.RequestDisplayInfo import com.android.credentialmanager.getflow.GetCredentialUiState import com.android.credentialmanager.getflow.findAutoSelectEntry import com.android.credentialmanager.common.ProviderActivityState +import com.android.credentialmanager.createflow.isFlowAutoSelectable /** * Client for interacting with Credential Manager. Also holds data inputs from it. @@ -113,21 +114,30 @@ class CredentialManagerRepo( val providerDisableListUiState = getCreateProviderDisableListInitialUiState() val requestDisplayInfoUiState = getCreateRequestDisplayInfoInitialUiState(originName)!! + val createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState( + enabledProviders = providerEnableListUiState, + disabledProviders = providerDisableListUiState, + defaultProviderIdPreferredByApp = + requestDisplayInfoUiState.appPreferredDefaultProviderId, + defaultProviderIdsSetByUser = + requestDisplayInfoUiState.userSetDefaultProviderIds, + requestDisplayInfo = requestDisplayInfoUiState, + isOnPasskeyIntroStateAlready = false, + isPasskeyFirstUse = isPasskeyFirstUse, + )!! + val isFlowAutoSelectable = isFlowAutoSelectable(createCredentialUiState) UiState( - createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState( - enabledProviders = providerEnableListUiState, - disabledProviders = providerDisableListUiState, - defaultProviderIdPreferredByApp = - requestDisplayInfoUiState.appPreferredDefaultProviderId, - defaultProviderIdsSetByUser = - requestDisplayInfoUiState.userSetDefaultProviderIds, - requestDisplayInfo = requestDisplayInfoUiState, - isOnPasskeyIntroStateAlready = false, - isPasskeyFirstUse = isPasskeyFirstUse, - )!!, + createCredentialUiState = createCredentialUiState, getCredentialUiState = null, cancelRequestState = cancelUiRequestState, isInitialRender = isNewActivity, + isAutoSelectFlow = isFlowAutoSelectable, + providerActivityState = + if (isFlowAutoSelectable) ProviderActivityState.READY_TO_LAUNCH + else ProviderActivityState.NOT_APPLICABLE, + selectedEntry = + if (isFlowAutoSelectable) createCredentialUiState.activeEntry?.activeEntryInfo + else null, ) } RequestInfo.TYPE_GET -> { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index e96e536c00d5..cf962d1d94aa 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -34,6 +34,7 @@ import com.android.credentialmanager.common.DialogState import com.android.credentialmanager.common.ProviderActivityResult import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.createflow.ActiveEntry +import com.android.credentialmanager.createflow.isFlowAutoSelectable import com.android.credentialmanager.createflow.CreateCredentialUiState import com.android.credentialmanager.createflow.CreateScreenState import com.android.credentialmanager.getflow.GetCredentialUiState @@ -238,7 +239,8 @@ class CredentialSelectorViewModel( getCredentialUiState = uiState.getCredentialUiState?.copy( currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS, isNoAccount = isNoAccount, - ) + ), + isInitialRender = true, ) } @@ -262,30 +264,39 @@ class CredentialSelectorViewModel( /***** Create Flow Callbacks *****/ /**************************************************************************/ fun createFlowOnConfirmIntro() { + userConfigRepo.setIsPasskeyFirstUse(false) val prevUiState = uiState.createCredentialUiState if (prevUiState == null) { Log.d(Constants.LOG_TAG, "Encountered unexpected null create ui state") onInternalError() return } - val newUiState = CreateFlowUtils.toCreateCredentialUiState( - enabledProviders = prevUiState.enabledProviders, - disabledProviders = prevUiState.disabledProviders, - defaultProviderIdPreferredByApp = - prevUiState.requestDisplayInfo.appPreferredDefaultProviderId, - defaultProviderIdsSetByUser = - prevUiState.requestDisplayInfo.userSetDefaultProviderIds, - requestDisplayInfo = prevUiState.requestDisplayInfo, + val newScreenState = CreateFlowUtils.toCreateScreenState( + createOptionSize = prevUiState.sortedCreateOptionsPairs.size, isOnPasskeyIntroStateAlready = true, - isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse() + requestDisplayInfo = prevUiState.requestDisplayInfo, + remoteEntry = prevUiState.remoteEntry, + isPasskeyFirstUse = true, ) - if (newUiState == null) { - Log.d(Constants.LOG_TAG, "Unable to update create ui state") + if (newScreenState == null) { + Log.d(Constants.LOG_TAG, "Unexpected: couldn't resolve new screen state") onInternalError() return } - uiState = uiState.copy(createCredentialUiState = newUiState) - userConfigRepo.setIsPasskeyFirstUse(false) + val newCreateCredentialUiState = prevUiState.copy( + currentScreenState = newScreenState, + ) + val isFlowAutoSelectable = isFlowAutoSelectable(newCreateCredentialUiState) + uiState = uiState.copy( + createCredentialUiState = newCreateCredentialUiState, + isAutoSelectFlow = isFlowAutoSelectable, + providerActivityState = + if (isFlowAutoSelectable) ProviderActivityState.READY_TO_LAUNCH + else ProviderActivityState.NOT_APPLICABLE, + selectedEntry = + if (isFlowAutoSelectable) newCreateCredentialUiState.activeEntry?.activeEntryInfo + else null, + ) } fun createFlowOnMoreOptionsSelectedOnCreationSelection() { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index b81339e85bcc..c64ebda2eb5c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -487,6 +487,7 @@ class CreateFlowUtils { createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId = appPreferredDefaultProviderId, userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), + isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed, ) is CreatePublicKeyCredentialRequest -> { newRequestDisplayInfoFromPasskeyJson( @@ -497,6 +498,7 @@ class CreateFlowUtils { createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId = appPreferredDefaultProviderId, userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), + isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed, ) } is CreateCustomCredentialRequest -> { @@ -515,6 +517,7 @@ class CreateFlowUtils { createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId = appPreferredDefaultProviderId, userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), + isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed, ) } else -> null @@ -602,7 +605,7 @@ class CreateFlowUtils { ) } - private fun toCreateScreenState( + fun toCreateScreenState( createOptionSize: Int, isOnPasskeyIntroStateAlready: Boolean, requestDisplayInfo: RequestDisplayInfo, @@ -661,8 +664,14 @@ class CreateFlowUtils { passwordCount = createEntry.getPasswordCredentialCount(), passkeyCount = createEntry.getPublicKeyCredentialCount(), totalCredentialCount = createEntry.getTotalCredentialCount(), - lastUsedTime = createEntry.lastUsedTime, - footerDescription = createEntry.description?.toString() + lastUsedTime = createEntry.lastUsedTime ?: Instant.MIN, + footerDescription = createEntry.description?.toString(), + // TODO(b/281065680): replace with official library constant once available + allowAutoSelect = + it.slice.items.firstOrNull { + it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" + + "SELECT_ALLOWED") + }?.text == "true", )) } return result.sortedWith( @@ -694,6 +703,7 @@ class CreateFlowUtils { preferImmediatelyAvailableCredentials: Boolean, appPreferredDefaultProviderId: String?, userSetDefaultProviderIds: Set<String>, + isAutoSelectRequest: Boolean ): RequestDisplayInfo? { val json = JSONObject(requestJson) var passkeyUsername = "" @@ -716,6 +726,7 @@ class CreateFlowUtils { preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId, userSetDefaultProviderIds, + isAutoSelectRequest, ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt index bd4375f4513e..a5998faa68ad 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt @@ -39,14 +39,16 @@ fun ModalBottomSheet( onDismiss: () -> Unit, isInitialRender: Boolean, onInitialRenderComplete: () -> Unit, + isAutoSelectFlow: Boolean, ) { val scope = rememberCoroutineScope() val state = rememberModalBottomSheetState( - initialValue = ModalBottomSheetValue.Hidden, + initialValue = if (isAutoSelectFlow) ModalBottomSheetValue.Expanded + else ModalBottomSheetValue.Hidden, skipHalfExpanded = true ) val sysUiController = rememberSystemUiController() - if (state.targetValue == ModalBottomSheetValue.Hidden) { + if (state.targetValue == ModalBottomSheetValue.Hidden || isAutoSelectFlow) { setTransparentSystemBarsColor(sysUiController) } else { setBottomSheetSystemBarsColor(sysUiController) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index a378f1c16753..a3087cf8004c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -166,6 +166,7 @@ fun CreateCredentialScreen( }, onDismiss = viewModel::onUserCancel, isInitialRender = viewModel.uiState.isInitialRender, + isAutoSelectFlow = viewModel.uiState.isAutoSelectFlow, onInitialRenderComplete = viewModel::onInitialRenderComplete, ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index fe1ce1b60413..cf7a943f2c66 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -34,6 +34,21 @@ data class CreateCredentialUiState( val foundCandidateFromUserDefaultProvider: Boolean, ) +internal fun isFlowAutoSelectable( + uiState: CreateCredentialUiState +): Boolean { + return uiState.requestDisplayInfo.isAutoSelectRequest && + // Even if the flow is auto selectable, still allow passkey intro screen to show once if + // applicable. + uiState.currentScreenState != CreateScreenState.PASSKEY_INTRO && + uiState.currentScreenState != CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO && + uiState.remoteEntry == null && + uiState.sortedCreateOptionsPairs.size == 1 && + uiState.activeEntry?.activeEntryInfo?.let { + it is CreateOptionInfo && it.allowAutoSelect + } ?: false +} + internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean { return state.sortedCreateOptionsPairs.isNotEmpty() || (!state.requestDisplayInfo.preferImmediatelyAvailableCredentials && @@ -72,8 +87,9 @@ class CreateOptionInfo( val passwordCount: Int?, val passkeyCount: Int?, val totalCredentialCount: Int?, - val lastUsedTime: Instant?, + val lastUsedTime: Instant, val footerDescription: String?, + val allowAutoSelect: Boolean, ) : BaseEntry( providerId, entryKey, @@ -107,6 +123,8 @@ data class RequestDisplayInfo( val preferImmediatelyAvailableCredentials: Boolean, val appPreferredDefaultProviderId: String?, val userSetDefaultProviderIds: Set<String>, + // Whether the given CreateCredentialRequest allows auto select. + val isAutoSelectRequest: Boolean, ) /** diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 40c99ee07d69..4183a5211556 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -149,6 +149,7 @@ fun GetCredentialScreen( }, onDismiss = viewModel::onUserCancel, isInitialRender = viewModel.uiState.isInitialRender, + isAutoSelectFlow = viewModel.uiState.isAutoSelectFlow, onInitialRenderComplete = viewModel::onInitialRenderComplete, ) } diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp index af3e2102e430..d778febe9faf 100644 --- a/packages/CtsShim/build/Android.bp +++ b/packages/CtsShim/build/Android.bp @@ -43,6 +43,9 @@ android_app { compile_multilib: "both", jni_libs: ["libshim_jni"], + // Explicitly uncompress native libs rather than letting the build system doing it and destroy the + // v2/v3 signature. + use_embedded_native_libs: true, uses_libs: ["android.test.runner"], @@ -124,6 +127,9 @@ android_app { compile_multilib: "both", jni_libs: ["libshim_jni"], + // Explicitly uncompress native libs rather than letting the build system doing it and destroy the + // v2/v3 signature. + use_embedded_native_libs: true, uses_libs: ["android.test.runner"], } diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java index ac9cdacec598..e034254e64ec 100644 --- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java +++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java @@ -95,9 +95,6 @@ public class QrDecorateView extends View { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - if (!isLaidOut()) { - return; - } if (mMaskBitmap == null) { mMaskBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); mMaskCanvas = new Canvas(mMaskBitmap); diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml index f050a1ef0f39..c85449d0c570 100644 --- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml +++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml @@ -83,12 +83,5 @@ android:contentDescription="@string/data_connection_roaming" android:visibility="gone" /> </FrameLayout> - <ImageView - android:id="@+id/mobile_roaming_large" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/stat_sys_roaming_large" - android:contentDescription="@string/data_connection_roaming" - android:visibility="gone" /> </com.android.keyguard.AlphaOptimizedLinearLayout> </merge> diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml index 8a66f50d13b1..13425c9259de 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml @@ -34,6 +34,7 @@ android:layout_height="wrap_content"> <EditText android:id="@+id/keyboard_shortcuts_search" + android:layout_gravity="center_vertical|start" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="24dp" @@ -54,58 +55,62 @@ <ImageView android:id="@+id/keyboard_shortcuts_search_cancel" + android:layout_gravity="center_vertical|end" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="end" - android:layout_marginTop="24dp" - android:layout_marginBottom="24dp" android:layout_marginEnd="49dp" android:padding="16dp" android:contentDescription="@string/keyboard_shortcut_clear_text" android:src="@drawable/ic_shortcutlist_search_button_cancel" /> </FrameLayout> - <LinearLayout + <HorizontalScrollView android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal"> - <View - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_marginStart="37dp"/> - - <Button - android:id="@+id/shortcut_system" - android:layout_width="120dp" + android:scrollbars="none"> + <LinearLayout + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="12dp" - style="@style/ShortCutButton" - android:text="@string/keyboard_shortcut_search_category_system"/> + android:gravity="center_vertical" + android:orientation="horizontal"> + <View + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="37dp"/> - <Button - android:id="@+id/shortcut_input" - android:layout_width="120dp" - android:layout_height="wrap_content" - android:layout_marginStart="12dp" - style="@style/ShortCutButton" - android:text="@string/keyboard_shortcut_search_category_input"/> + <Button + android:id="@+id/shortcut_system" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="12dp" + style="@style/ShortCutButton" + android:text="@string/keyboard_shortcut_search_category_system"/> - <Button - android:id="@+id/shortcut_open_apps" - android:layout_width="120dp" - android:layout_height="wrap_content" - android:layout_marginStart="12dp" - style="@style/ShortCutButton" - android:text="@string/keyboard_shortcut_search_category_open_apps"/> + <Button + android:id="@+id/shortcut_input" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="12dp" + style="@style/ShortCutButton" + android:text="@string/keyboard_shortcut_search_category_input"/> - <Button - android:id="@+id/shortcut_specific_app" - android:layout_width="120dp" - android:layout_height="wrap_content" - android:layout_marginStart="12dp" - style="@style/ShortCutButton" - android:text="@string/keyboard_shortcut_search_category_current_app"/> - </LinearLayout> + <Button + android:id="@+id/shortcut_open_apps" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="12dp" + style="@style/ShortCutButton" + android:text="@string/keyboard_shortcut_search_category_open_apps"/> + + <Button + android:id="@+id/shortcut_specific_app" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="12dp" + style="@style/ShortCutButton" + android:text="@string/keyboard_shortcut_search_category_current_app"/> + </LinearLayout> + </HorizontalScrollView> <TextView android:id="@+id/shortcut_search_no_result" diff --git a/packages/SystemUI/res/layout/mobile_signal_group.xml b/packages/SystemUI/res/layout/mobile_signal_group.xml index 5552020f22cb..bfd079b59054 100644 --- a/packages/SystemUI/res/layout/mobile_signal_group.xml +++ b/packages/SystemUI/res/layout/mobile_signal_group.xml @@ -77,11 +77,4 @@ android:contentDescription="@string/data_connection_roaming" android:visibility="gone" /> </FrameLayout> - <ImageView - android:id="@+id/mobile_roaming_large" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/stat_sys_roaming_large" - android:contentDescription="@string/data_connection_roaming" - android:visibility="gone" /> </com.android.keyguard.AlphaOptimizedLinearLayout> diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index aade71a83c28..be5bb07089dd 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -320,7 +320,6 @@ public class Dependency { @Inject @Main Lazy<Looper> mMainLooper; @Inject @Main Lazy<Handler> mMainHandler; @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler; - @Nullable @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail; @Inject @Main Lazy<Executor> mMainExecutor; @Inject @Background Lazy<Executor> mBackgroundExecutor; diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 85788456d1cb..70c39df2a610 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -31,9 +31,6 @@ import android.os.Process; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; -import android.util.ArrayMap; -import android.util.Dumpable; -import android.util.DumpableContainer; import android.util.Log; import android.util.TimingsTraceLog; import android.view.SurfaceControl; @@ -57,18 +54,12 @@ import javax.inject.Provider; * Application class for SystemUI. */ public class SystemUIApplication extends Application implements - SystemUIAppComponentFactory.ContextInitializer, DumpableContainer { + SystemUIAppComponentFactory.ContextInitializer { public static final String TAG = "SystemUIService"; private static final boolean DEBUG = false; private BootCompleteCacheImpl mBootCompleteCache; - private DumpManager mDumpManager; - - /** - * Map of dumpables added externally. - */ - private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>(); /** * Hold a reference on the stuff we start. @@ -233,7 +224,7 @@ public class SystemUIApplication extends Application implements } } - mDumpManager = mSysUIComponent.createDumpManager(); + DumpManager dumpManager = mSysUIComponent.createDumpManager(); Log.v(TAG, "Starting SystemUI services for user " + Process.myUserHandle().getIdentifier() + "."); @@ -267,7 +258,7 @@ public class SystemUIApplication extends Application implements notifyBootCompleted(mServices[i]); } - mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]); + dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]); } mSysUIComponent.getInitController().executePostInitTasks(); log.traceEnd(); @@ -342,36 +333,6 @@ public class SystemUIApplication extends Application implements return startable; } - // TODO(b/217567642): add unit tests? There doesn't seem to be a SystemUiApplicationTest... - @Override - public boolean addDumpable(Dumpable dumpable) { - String name = dumpable.getDumpableName(); - if (mDumpables.containsKey(name)) { - // This is normal because SystemUIApplication is an application context that is shared - // among multiple components - if (DEBUG) { - Log.d(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable" - + " with that name (" + name + "): " + mDumpables.get(name)); - } - return false; - } - if (DEBUG) Log.d(TAG, "addDumpable(): adding '" + name + "' = " + dumpable); - mDumpables.put(name, dumpable); - - // TODO(b/217567642): replace com.android.systemui.dump.Dumpable by - // com.android.util.Dumpable and get rid of the intermediate lambda - mDumpManager.registerDumpable(dumpable.getDumpableName(), dumpable::dump); - return true; - } - - // TODO(b/217567642): implement - @Override - public boolean removeDumpable(Dumpable dumpable) { - Log.w(TAG, "removeDumpable(" + dumpable + "): not implemented"); - - return false; - } - @Override public void onConfigurationChanged(Configuration newConfig) { if (mServicesStarted) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt index b72801d3b5fe..8edccf166efe 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt @@ -21,7 +21,7 @@ constructor( fun enable(onPanelInteraction: Runnable) { if (action == null) { action = Action(onPanelInteraction) - shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged) + shadeExpansionStateManager.addShadeExpansionListener(this::onPanelExpansionChanged) } else { Log.e(TAG, "Already enabled") } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt index c83166385ac6..096d94144480 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt @@ -29,6 +29,8 @@ import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.LogContextInteractor import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl +import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor +import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.util.concurrency.ThreadFactory import dagger.Binds @@ -65,6 +67,11 @@ interface BiometricsModule { @SysUISingleton fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor + @Binds + @SysUISingleton + fun providesSideFpsOverlayInteractor(impl: SideFpsOverlayInteractorImpl): + SideFpsOverlayInteractor + companion object { /** Background [Executor] for HAL related operations. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt index 33fb36c15c2d..c43722f2a0bb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt @@ -57,13 +57,8 @@ interface FingerprintPropertyRepository { /** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */ val sensorType: StateFlow<FingerprintSensorType> - /** The primary sensor location relative to the default display. */ - val sensorLocation: StateFlow<SensorLocationInternal> - - // TODO(b/251476085): don't implement until we need it, but expose alternative locations as - // a map of display id -> location or similar. /** The sensor location relative to each physical display. */ - // val sensorLocations<Map<String, SensorLocationInternal>> + val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> } @SysUISingleton @@ -104,15 +99,19 @@ constructor( MutableStateFlow(FingerprintSensorType.UNKNOWN) override val sensorType = _sensorType.asStateFlow() - private val _sensorLocation: MutableStateFlow<SensorLocationInternal> = - MutableStateFlow(SensorLocationInternal.DEFAULT) - override val sensorLocation = _sensorLocation.asStateFlow() + private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> = + MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT)) + override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> = + _sensorLocations.asStateFlow() private fun setProperties(prop: FingerprintSensorPropertiesInternal) { _sensorId.value = prop.sensorId _strength.value = sensorStrengthIntToObject(prop.sensorStrength) _sensorType.value = sensorTypeIntToObject(prop.sensorType) - _sensorLocation.value = prop.location + _sensorLocations.value = + prop.allLocations.associateBy { sensorLocationInternal -> + sensorLocationInternal.displayId + } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt new file mode 100644 index 000000000000..aa85e5f3b21a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.interactor + +import android.hardware.biometrics.SensorLocationInternal +import android.util.Log +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** Business logic for SideFps overlay offsets. */ +interface SideFpsOverlayInteractor { + + /** Get the corresponding offsets based on different displayId. */ + fun getOverlayOffsets(displayId: String): SensorLocationInternal +} + +@SysUISingleton +class SideFpsOverlayInteractorImpl +@Inject +constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) : + SideFpsOverlayInteractor { + + override fun getOverlayOffsets(displayId: String): SensorLocationInternal { + val offsets = fingerprintPropertyRepository.sensorLocations.value + return if (offsets.containsKey(displayId)) { + offsets[displayId]!! + } else { + Log.w(TAG, "No location specified for display: $displayId") + offsets[""]!! + } + } + + companion object { + private const val TAG = "SideFpsOverlayInteractorImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index d5a41460c89e..f68bd49230d9 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -23,8 +23,6 @@ import android.content.Context; import android.hardware.SensorPrivacyManager; import android.os.Handler; -import androidx.annotation.Nullable; - import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardViewController; import com.android.systemui.battery.BatterySaverModule; @@ -74,12 +72,12 @@ import com.android.systemui.statusbar.policy.SensorPrivacyController; import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl; import com.android.systemui.volume.dagger.VolumeModule; -import javax.inject.Named; - import dagger.Binds; import dagger.Module; import dagger.Provides; +import javax.inject.Named; + /** * A dagger module for injecting default implementations of components of System UI. * @@ -115,9 +113,8 @@ public abstract class ReferenceSystemUIModule { @SysUISingleton @Provides @Named(LEAK_REPORT_EMAIL_NAME) - @Nullable static String provideLeakReportEmail() { - return null; + return ""; } @Binds diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index a4d4a9a9bf56..6967e6c23f35 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -710,6 +710,5 @@ object Flags { // TODO(b/278761837): Tracking Bug @JvmField - val USE_NEW_ACTIVITY_STARTER = unreleasedFlag(2801, name = "use_new_activity_starter", - teamfood = true) + val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter") } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 552e5ea967cf..a0db65ce5fdd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -16,6 +16,7 @@ package com.android.systemui.keyguard; +import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; @@ -101,6 +102,7 @@ import androidx.annotation.VisibleForTesting; import com.android.app.animation.Interpolators; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.Configuration; +import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardStateCallback; @@ -131,6 +133,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.dagger.KeyguardModule; +import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; @@ -1181,12 +1184,16 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private Lazy<ScrimController> mScrimControllerLazy; private FeatureFlags mFeatureFlags; + private final UiEventLogger mUiEventLogger; + private final SessionTracker mSessionTracker; /** * Injected constructor. See {@link KeyguardModule}. */ public KeyguardViewMediator( Context context, + UiEventLogger uiEventLogger, + SessionTracker sessionTracker, UserTracker userTracker, FalsingCollector falsingCollector, LockPatternUtils lockPatternUtils, @@ -1270,6 +1277,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS; mFeatureFlags = featureFlags; + mUiEventLogger = uiEventLogger; + mSessionTracker = sessionTracker; } public void userActivity() { @@ -1660,6 +1669,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence); notifyStartedWakingUp(); } + mUiEventLogger.logWithInstanceIdAndPosition( + BiometricUnlockController.BiometricUiEvent.STARTED_WAKING_UP, + 0, + null, + mSessionTracker.getSessionId(SESSION_KEYGUARD), + pmWakeReason + ); mUpdateMonitor.dispatchStartedWakingUp(pmWakeReason); maybeSendUserPresentBroadcast(); Trace.endSection(); @@ -3082,7 +3098,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking"); mWakeAndUnlocking = true; - keyguardDone(); + mKeyguardViewControllerLazy.get().notifyKeyguardAuthenticated(/* strongAuth */ false); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 5e71458c8bb4..deb8f5d96cbd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -21,6 +21,7 @@ import android.content.Context; import android.os.PowerManager; import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardUpdateMonitor; @@ -50,6 +51,7 @@ import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionMo import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl; +import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; @@ -63,12 +65,12 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.DeviceConfigProxy; -import java.util.concurrent.Executor; - import dagger.Lazy; import dagger.Module; import dagger.Provides; +import java.util.concurrent.Executor; + /** * Dagger Module providing keyguard. */ @@ -93,6 +95,8 @@ public class KeyguardModule { @SysUISingleton public static KeyguardViewMediator newKeyguardViewMediator( Context context, + UiEventLogger uiEventLogger, + SessionTracker sessionTracker, UserTracker userTracker, FalsingCollector falsingCollector, LockPatternUtils lockPatternUtils, @@ -124,6 +128,8 @@ public class KeyguardModule { FeatureFlags featureFlags) { return new KeyguardViewMediator( context, + uiEventLogger, + sessionTracker, userTracker, falsingCollector, lockPatternUtils, diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 385e72017bae..2469a98140e3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -42,7 +42,9 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.concurrency.DelayableExecutor; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; @@ -88,6 +90,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements private final Handler mHandler; private final Intent mIntent; private final UserHandle mUser; + private final DelayableExecutor mExecutor; private final IBinder mToken = new Binder(); private final PackageManagerAdapter mPackageManagerAdapter; private final BroadcastDispatcher mBroadcastDispatcher; @@ -100,25 +103,27 @@ public class TileLifecycleManager extends BroadcastReceiver implements private int mBindTryCount; private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; - private boolean mBound; + private AtomicBoolean mBound = new AtomicBoolean(false); private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false); private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false); - private boolean mUnbindImmediate; + private AtomicBoolean mUnbindImmediate = new AtomicBoolean(false); @Nullable private TileChangeListener mChangeListener; // Return value from bindServiceAsUser, determines whether safe to call unbind. - private boolean mIsBound; + private AtomicBoolean mIsBound = new AtomicBoolean(false); @AssistedInject TileLifecycleManager(@Main Handler handler, Context context, IQSService service, PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, - @Assisted Intent intent, @Assisted UserHandle user) { + @Assisted Intent intent, @Assisted UserHandle user, + @Background DelayableExecutor executor) { mContext = context; mHandler = handler; mIntent = intent; mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder()); mIntent.putExtra(TileService.EXTRA_TOKEN, mToken); mUser = user; + mExecutor = executor; mPackageManagerAdapter = packageManagerAdapter; mBroadcastDispatcher = broadcastDispatcher; if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser); @@ -184,22 +189,21 @@ public class TileLifecycleManager extends BroadcastReceiver implements * Binds just long enough to send any queued messages, then unbinds. */ public void flushMessagesAndUnbind() { - mUnbindImmediate = true; - setBindService(true); + mExecutor.execute(() -> { + mUnbindImmediate.set(true); + setBindService(true); + }); } - /** - * Binds or unbinds to IQSService - */ @WorkerThread - public void setBindService(boolean bind) { - if (mBound && mUnbindImmediate) { + private void setBindService(boolean bind) { + if (mBound.get() && mUnbindImmediate.get()) { // If we are already bound and expecting to unbind, this means we should stay bound // because something else wants to hold the connection open. - mUnbindImmediate = false; + mUnbindImmediate.set(false); return; } - mBound = bind; + mBound.set(bind); if (bind) { if (mBindTryCount == MAX_BIND_RETRIES) { // Too many failures, give up on this tile until an update. @@ -212,31 +216,38 @@ public class TileLifecycleManager extends BroadcastReceiver implements if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser); mBindTryCount++; try { - mIsBound = bindServices(); - if (!mIsBound) { + mIsBound.set(bindServices()); + if (!mIsBound.get()) { mContext.unbindService(this); } } catch (SecurityException e) { Log.e(TAG, "Failed to bind to service", e); - mIsBound = false; + mIsBound.set(false); } } else { if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser); // Give it another chance next time it needs to be bound, out of kindness. mBindTryCount = 0; freeWrapper(); - if (mIsBound) { + if (mIsBound.get()) { try { mContext.unbindService(this); } catch (Exception e) { Log.e(TAG, "Failed to unbind service " + mIntent.getComponent().flattenToShortString(), e); } - mIsBound = false; + mIsBound.set(false); } } } + /** + * Binds or unbinds to IQSService + */ + public void executeSetBindService(boolean bind) { + mExecutor.execute(() -> setBindService(bind)); + } + private boolean bindServices() { String packageName = mIntent.getComponent().getPackageName(); if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName, @@ -317,10 +328,12 @@ public class TileLifecycleManager extends BroadcastReceiver implements } onTileRemoved(); } - if (mUnbindImmediate) { - mUnbindImmediate = false; - setBindService(false); - } + mExecutor.execute(() -> { + if (mUnbindImmediate.get()) { + mUnbindImmediate.set(false); + setBindService(false); + } + }); } public void handleDestroy() { @@ -335,19 +348,11 @@ public class TileLifecycleManager extends BroadcastReceiver implements if (mWrapper == null) return; freeWrapper(); // Clearly not bound anymore - mIsBound = false; - if (!mBound) return; + mIsBound.set(false); + if (!mBound.get()) return; if (DEBUG) Log.d(TAG, "handleDeath"); if (checkComponentState()) { - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (mBound) { - // Retry binding. - setBindService(true); - } - } - }, mBindRetryDelay); + mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay); } } @@ -410,11 +415,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements mChangeListener.onTileChanged(mIntent.getComponent()); } stopPackageListening(); - if (mBound) { - // Trying to bind again will check the state of the package before bothering to bind. - if (DEBUG) Log.d(TAG, "Trying to rebind"); - setBindService(true); - } + mExecutor.execute(() -> { + if (mBound.get()) { + // Trying to bind again will check the state of the package before bothering to + // bind. + if (DEBUG) Log.d(TAG, "Trying to rebind"); + setBindService(true); + } + + }); } private boolean isComponentAvailable() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index 7a10a27f6aca..941a9d6dc952 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -35,6 +35,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener; import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.List; import java.util.Objects; @@ -75,12 +76,12 @@ public class TileServiceManager { TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, - CustomTileAddedRepository customTileAddedRepository) { + CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { this(tileServices, handler, userTracker, customTileAddedRepository, new TileLifecycleManager(handler, tileServices.getContext(), tileServices, - new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher, - new Intent(TileService.ACTION_QS_TILE).setComponent(component), - userTracker.getUserHandle())); + new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher, + new Intent(TileService.ACTION_QS_TILE).setComponent(component), + userTracker.getUserHandle(), executor)); } @VisibleForTesting @@ -203,7 +204,7 @@ public class TileServiceManager { mBound = true; mJustBound = true; mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); } private void unbindService() { @@ -213,7 +214,7 @@ public class TileServiceManager { } mBound = false; mJustBound = false; - mStateManager.setBindService(false); + mStateManager.executeSetBindService(false); } public void calculateBindPriority(long currentTime) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 121955cced1a..a07b955fdb73 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -39,6 +39,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; @@ -47,6 +48,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.ArrayList; import java.util.Collections; @@ -79,6 +81,7 @@ public class TileServices extends IQSService.Stub { private final StatusBarIconController mStatusBarIconController; private final PanelInteractor mPanelInteractor; private final CustomTileAddedRepository mCustomTileAddedRepository; + private final DelayableExecutor mBackgroundExecutor; private int mMaxBound = DEFAULT_MAX_BOUND; @@ -92,7 +95,8 @@ public class TileServices extends IQSService.Stub { CommandQueue commandQueue, StatusBarIconController statusBarIconController, PanelInteractor panelInteractor, - CustomTileAddedRepository customTileAddedRepository) { + CustomTileAddedRepository customTileAddedRepository, + @Background DelayableExecutor backgroundExecutor) { mHost = host; mKeyguardStateController = keyguardStateController; mContext = mHost.getContext(); @@ -105,6 +109,7 @@ public class TileServices extends IQSService.Stub { mCommandQueue.addCallback(mRequestListeningCallback); mPanelInteractor = panelInteractor; mCustomTileAddedRepository = customTileAddedRepository; + mBackgroundExecutor = backgroundExecutor; } public Context getContext() { @@ -132,7 +137,7 @@ public class TileServices extends IQSService.Stub { protected TileServiceManager onCreateTileService(ComponentName component, BroadcastDispatcher broadcastDispatcher) { return new TileServiceManager(this, mHandlerProvider.get(), component, - broadcastDispatcher, mUserTracker, mCustomTileAddedRepository); + broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor); } public void freeService(CustomTile tile, TileServiceManager service) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index e026bdbc2e08..544e6ad295ff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; +import static android.graphics.drawable.Icon.TYPE_URI; import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; @@ -225,7 +226,12 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { return; } mSelectedCard = cards.get(selectedIndex); - mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + android.graphics.drawable.Icon cardImageIcon = mSelectedCard.getCardImage(); + if (cardImageIcon.getType() == TYPE_URI) { + mCardViewDrawable = null; + } else { + mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + } refreshState(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index a43f52019219..07259c2ff283 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -205,8 +205,11 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // TODO move this logic to message queue mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> { if (event.getActionMasked() == ACTION_DOWN) { - centralSurfaces.getShadeViewController() - .startExpandLatencyTracking(); + ShadeViewController shadeViewController = + centralSurfaces.getShadeViewController(); + if (shadeViewController != null) { + shadeViewController.startExpandLatencyTracking(); + } } mHandler.post(() -> { int action = event.getActionMasked(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index 20313c3df465..a048f543d476 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -54,12 +54,20 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { * Listener will also be immediately notified with the current values. */ fun addExpansionListener(listener: ShadeExpansionListener) { - expansionListeners.add(listener) + addShadeExpansionListener(listener) listener.onPanelExpansionChanged( ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount) ) } + /** + * Adds a listener that will be notified when the panel expansion fraction has changed. + * @see #addExpansionListener + */ + fun addShadeExpansionListener(listener: ShadeExpansionListener) { + expansionListeners.add(listener) + } + /** Removes an expansion listener. */ fun removeExpansionListener(listener: ShadeExpansionListener) { expansionListeners.remove(listener) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java index 12f2c22ab86a..f84b96c94a91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java @@ -15,13 +15,14 @@ */ package com.android.systemui.statusbar.connectivity; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT; import android.content.Context; import android.content.Intent; -import android.net.NetworkCapabilities; import android.net.wifi.WifiManager; import android.os.Handler; import android.text.Html; @@ -37,6 +38,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import java.io.PrintWriter; +import java.util.BitSet; /** */ public class WifiSignalController extends SignalController<WifiState, IconGroup> { @@ -56,8 +58,12 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> WifiManager wifiManager, WifiStatusTrackerFactory trackerFactory, @Background Handler bgHandler) { - super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI, - callbackHandler, networkController); + super( + "WifiSignalController", + context, + TRANSPORT_WIFI, + callbackHandler, + networkController); mBgHandler = bgHandler; mWifiManager = wifiManager; mWifiTracker = trackerFactory.createTracker(this::handleStatusUpdated, bgHandler); @@ -160,7 +166,10 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> // The WiFi signal level returned by WifiManager#calculateSignalLevel start from 0, so // WifiManager#getMaxSignalLevel + 1 represents the total level buckets count. int totalLevel = mWifiManager.getMaxSignalLevel() + 1; - boolean noInternet = mCurrentState.inetCondition == 0; + // A carrier merged connection could come from a WIFI *or* CELLULAR transport, so we can't + // use [mCurrentState.inetCondition], which only checks the WIFI status. Instead, check if + // the default connection is validated at all. + boolean noInternet = !mCurrentState.isDefaultConnectionValidated; if (mCurrentState.connected) { return SignalDrawable.getState(level, totalLevel, noInternet); } else if (mCurrentState.enabled) { @@ -236,6 +245,18 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> && mCurrentState.isCarrierMerged && (mCurrentState.subId == subId); } + @Override + void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) { + mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0; + // Because a carrier merged connection can come from either a CELLULAR *or* WIFI transport, + // we need to also store if either transport is validated to correctly display the carrier + // merged case. + mCurrentState.isDefaultConnectionValidated = + validatedTransports.get(TRANSPORT_CELLULAR) + || validatedTransports.get(TRANSPORT_WIFI); + notifyListenersIfNecessary(); + } + @VisibleForTesting void setActivity(int wifiActivity) { mCurrentState.activityIn = wifiActivity == DATA_ACTIVITY_INOUT diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt index d32e34915c61..63a63de83d71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt @@ -24,6 +24,14 @@ internal class WifiState( @JvmField var isDefault: Boolean = false, @JvmField var statusLabel: String? = null, @JvmField var isCarrierMerged: Boolean = false, + /** + * True if the current default connection is validated for *any* transport, not just wifi. + * (Specifically TRANSPORT_CELLULAR *or* TRANSPORT_WIFI.) + * + * This should *only* be used when calculating information for the carrier merged connection and + * *not* for typical wifi connections. See b/225902574. + */ + @JvmField var isDefaultConnectionValidated: Boolean = false, @JvmField var subId: Int = 0 ) : ConnectivityState() { @@ -35,6 +43,7 @@ internal class WifiState( isDefault = state.isDefault statusLabel = state.statusLabel isCarrierMerged = state.isCarrierMerged + isDefaultConnectionValidated = state.isDefaultConnectionValidated subId = state.subId } @@ -45,6 +54,7 @@ internal class WifiState( .append(",isDefault=").append(isDefault) .append(",statusLabel=").append(statusLabel) .append(",isCarrierMerged=").append(isCarrierMerged) + .append(",isDefaultConnectionValidated=").append(isDefaultConnectionValidated) .append(",subId=").append(subId) } @@ -54,6 +64,7 @@ internal class WifiState( "isDefault", "statusLabel", "isCarrierMerged", + "isDefaultConnectionValidated", "subId") return super.tableColumns() + columns @@ -65,6 +76,7 @@ internal class WifiState( isDefault, statusLabel, isCarrierMerged, + isDefaultConnectionValidated, subId).map { it.toString() } @@ -84,6 +96,7 @@ internal class WifiState( if (isDefault != other.isDefault) return false if (statusLabel != other.statusLabel) return false if (isCarrierMerged != other.isCarrierMerged) return false + if (isDefaultConnectionValidated != other.isDefaultConnectionValidated) return false if (subId != other.subId) return false return true @@ -96,6 +109,7 @@ internal class WifiState( result = 31 * result + isDefault.hashCode() result = 31 * result + (statusLabel?.hashCode() ?: 0) result = 31 * result + isCarrierMerged.hashCode() + result = 31 * result + isDefaultConnectionValidated.hashCode() result = 31 * result + subId return result } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index d9dc8878fa61..bbb4f2449330 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -236,7 +236,11 @@ constructor( override fun postStartActivityDismissingKeyguard(intent: Intent, delay: Int) { postOnUiThread(delay) { - activityStarterInternal.startActivityDismissingKeyguard(intent = intent) + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + onlyProvisioned = true, + dismissShade = true, + ) } } @@ -248,6 +252,8 @@ constructor( postOnUiThread(delay) { activityStarterInternal.startActivityDismissingKeyguard( intent = intent, + onlyProvisioned = true, + dismissShade = true, animationController = animationController, ) } @@ -262,6 +268,8 @@ constructor( postOnUiThread(delay) { activityStarterInternal.startActivityDismissingKeyguard( intent = intent, + onlyProvisioned = true, + dismissShade = true, animationController = animationController, customMessage = customMessage, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 6742e4f3041e..bd7840d447cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -190,7 +190,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } } - @VisibleForTesting public enum BiometricUiEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "A biometric event of type fingerprint succeeded.") @@ -221,7 +220,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp BIOMETRIC_IRIS_ERROR(404), @UiEvent(doc = "Bouncer was shown as a result of consecutive failed UDFPS attempts.") - BIOMETRIC_BOUNCER_SHOWN(916); + BIOMETRIC_BOUNCER_SHOWN(916), + + @UiEvent(doc = "Screen started waking up with the given PowerManager wake reason.") + STARTED_WAKING_UP(1378); private final int mId; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 7aa90336e2bf..49de5a232f30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -160,7 +160,7 @@ abstract class StatusBarPipelineModule { @SysUISingleton @SharedConnectivityInputLog fun provideSharedConnectivityTableLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("SharedConnectivityInputLog", 30) + return factory.create("SharedConnectivityInputLog", 60) } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 0e9b6c56437e..81a068d10a14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -38,6 +38,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel @@ -88,6 +89,7 @@ constructor( private val context: Context, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, + airplaneModeRepository: AirplaneModeRepository, // Some "wifi networks" should be rendered as a mobile connection, which is why the wifi // repository is an input to the mobile repository. // See [CarrierMergedConnectionRepository] for details. @@ -106,10 +108,20 @@ constructor( context.getString(R.string.status_bar_network_name_separator) private val carrierMergedSubId: StateFlow<Int?> = - wifiRepository.wifiNetwork - .mapLatest { - if (it is WifiNetworkModel.CarrierMerged) { - it.subscriptionId + combine( + wifiRepository.wifiNetwork, + connectivityRepository.defaultConnections, + airplaneModeRepository.isAirplaneMode, + ) { wifiNetwork, defaultConnections, isAirplaneMode -> + // The carrier merged connection should only be used if it's also the default + // connection or mobile connections aren't available because of airplane mode. + val defaultConnectionIsNonMobile = + defaultConnections.carrierMerged.isDefault || + defaultConnections.wifi.isDefault || + isAirplaneMode + + if (wifiNetwork is WifiNetworkModel.CarrierMerged && defaultConnectionIsNonMobile) { + wifiNetwork.subscriptionId } else { null } @@ -269,12 +281,8 @@ constructor( .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val hasCarrierMergedConnection: StateFlow<Boolean> = - combine( - connectivityRepository.defaultConnections, - carrierMergedSubId, - ) { defaultConnections, carrierMergedSubId -> - defaultConnections.carrierMerged.isDefault || carrierMergedSubId != null - } + carrierMergedSubId + .map { it != null } .distinctUntilChanged() .logDiffsForTable( tableLogger, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index e1ffae01be03..e90f40c74cc5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -155,7 +155,8 @@ constructor( combine( unfilteredSubscriptions, mobileConnectionsRepo.activeMobileDataSubscriptionId, - ) { unfilteredSubs, activeId -> + connectivityRepository.vcnSubId, + ) { unfilteredSubs, activeId, vcnSubId -> // Based on the old logic, if (unfilteredSubs.size != 2) { return@combine unfilteredSubs @@ -182,7 +183,13 @@ constructor( // return the non-opportunistic info return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1) } else { - return@combine if (info1.subscriptionId == activeId) { + // It's possible for the subId of the VCN to disagree with the active subId in + // cases where the system has tried to switch but found no connection. In these + // scenarios, VCN will always have the subId that we want to use, so use that + // value instead of the activeId reported by telephony + val subIdToKeep = vcnSubId ?: activeId + + return@combine if (info1.subscriptionId == subIdToKeep) { listOf(info1) } else { listOf(info2) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt index 051f43f1059c..cac0ae3dbab4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt @@ -61,6 +61,10 @@ constructor( model::messagePrinter, ) } + + fun logVcnSubscriptionId(subId: Int) { + buffer.log(TAG, LogLevel.DEBUG, { int1 = subId }, { "vcnSubId changed: $int1" }) + } } private const val TAG = "ConnectivityInputLogger" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt index 731f1e028470..7076f345df97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt @@ -27,6 +27,7 @@ import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.annotation.ArrayRes import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable @@ -50,10 +51,13 @@ import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn /** @@ -66,6 +70,16 @@ interface ConnectivityRepository { /** Observable for which connection(s) are currently default. */ val defaultConnections: StateFlow<DefaultConnectionModel> + + /** + * Subscription ID of the [VcnTransportInfo] for the default connection. + * + * If the default network has a [VcnTransportInfo], then that transport info contains a subId of + * the VCN. When VCN is connected and default, this subId is what SystemUI will care about. In + * cases where telephony's activeDataSubscriptionId differs from this value, it is expected to + * eventually catch up and reflect what is represented here in the VcnTransportInfo. + */ + val vcnSubId: StateFlow<Int?> } @SuppressLint("MissingPermission") @@ -118,24 +132,13 @@ constructor( initialValue = defaultHiddenIcons ) - @SuppressLint("MissingPermission") - override val defaultConnections: StateFlow<DefaultConnectionModel> = + private val defaultNetworkCapabilities: SharedFlow<NetworkCapabilities?> = conflatedCallbackFlow { val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { override fun onLost(network: Network) { logger.logOnDefaultLost(network) - // The system no longer has a default network, so everything is - // non-default. - trySend( - DefaultConnectionModel( - Wifi(isDefault = false), - Mobile(isDefault = false), - CarrierMerged(isDefault = false), - Ethernet(isDefault = false), - isValidated = false, - ) - ) + trySend(null) } override fun onCapabilitiesChanged( @@ -143,30 +146,7 @@ constructor( networkCapabilities: NetworkCapabilities, ) { logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities) - - val wifiInfo = - networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) - - val isWifiDefault = - networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null - val isMobileDefault = - networkCapabilities.hasTransport(TRANSPORT_CELLULAR) - val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true - val isEthernetDefault = - networkCapabilities.hasTransport(TRANSPORT_ETHERNET) - - val isValidated = - networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) - - trySend( - DefaultConnectionModel( - Wifi(isWifiDefault), - Mobile(isMobileDefault), - CarrierMerged(isCarrierMergedDefault), - Ethernet(isEthernetDefault), - isValidated, - ) - ) + trySend(networkCapabilities) } } @@ -174,6 +154,61 @@ constructor( awaitClose { connectivityManager.unregisterNetworkCallback(callback) } } + .shareIn(scope, SharingStarted.WhileSubscribed()) + + override val vcnSubId: StateFlow<Int?> = + defaultNetworkCapabilities + .map { networkCapabilities -> + networkCapabilities?.run { + val subId = (transportInfo as? VcnTransportInfo)?.subId + // Never return an INVALID_SUBSCRIPTION_ID (-1) + if (subId != INVALID_SUBSCRIPTION_ID) { + subId + } else { + null + } + } + } + .distinctUntilChanged() + /* A note for logging: we use -2 here since -1 == INVALID_SUBSCRIPTION_ID */ + .onEach { logger.logVcnSubscriptionId(it ?: -2) } + .stateIn(scope, SharingStarted.Eagerly, null) + + @SuppressLint("MissingPermission") + override val defaultConnections: StateFlow<DefaultConnectionModel> = + defaultNetworkCapabilities + .map { networkCapabilities -> + if (networkCapabilities == null) { + // The system no longer has a default network, so everything is + // non-default. + DefaultConnectionModel( + Wifi(isDefault = false), + Mobile(isDefault = false), + CarrierMerged(isDefault = false), + Ethernet(isDefault = false), + isValidated = false, + ) + } else { + val wifiInfo = + networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + val isWifiDefault = + networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null + val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR) + val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true + val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET) + + val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) + + DefaultConnectionModel( + Wifi(isWifiDefault), + Mobile(isMobileDefault), + CarrierMerged(isCarrierMergedDefault), + Ethernet(isEthernetDefault), + isValidated, + ) + } + } .distinctUntilChanged() .onEach { logger.logDefaultConnectionsChanged(it) } .stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 5208064d2c0b..5cc3d52f4494 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -38,7 +38,11 @@ import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository; +import com.android.systemui.statusbar.policy.bluetooth.ConnectionStatusModel; import java.io.PrintWriter; import java.util.ArrayList; @@ -50,14 +54,20 @@ import java.util.concurrent.Executor; import javax.inject.Inject; /** + * Controller for information about bluetooth connections. + * + * Note: Right now, this class and {@link BluetoothRepository} co-exist. Any new code should go in + * {@link BluetoothRepository}, but external clients should query this file for now. */ @SysUISingleton public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback, CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener { private static final String TAG = "BluetoothController"; + private final FeatureFlags mFeatureFlags; private final DumpManager mDumpManager; private final BluetoothLogger mLogger; + private final BluetoothRepository mBluetoothRepository; private final LocalBluetoothManager mLocalBluetoothManager; private final UserManager mUserManager; private final int mCurrentUser; @@ -79,14 +89,18 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @Inject public BluetoothControllerImpl( Context context, + FeatureFlags featureFlags, UserTracker userTracker, DumpManager dumpManager, BluetoothLogger logger, + BluetoothRepository bluetoothRepository, @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager, @Nullable BluetoothAdapter bluetoothAdapter) { + mFeatureFlags = featureFlags; mDumpManager = dumpManager; mLogger = logger; + mBluetoothRepository = bluetoothRepository; mLocalBluetoothManager = localBluetoothManager; mHandler = new H(mainLooper); if (mLocalBluetoothManager != null) { @@ -229,6 +243,16 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } private void updateConnected() { + if (mFeatureFlags.isEnabled(Flags.NEW_BLUETOOTH_REPOSITORY)) { + mBluetoothRepository.fetchConnectionStatusInBackground( + getDevices(), this::onConnectionStatusFetched); + } else { + updateConnectedOld(); + } + } + + /** Used only if {@link Flags.NEW_BLUETOOTH_REPOSITORY} is *not* enabled. */ + private void updateConnectedOld() { // Make sure our connection state is up to date. int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState(); List<CachedBluetoothDevice> newList = new ArrayList<>(); @@ -249,6 +273,12 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa // connected. state = BluetoothAdapter.STATE_DISCONNECTED; } + onConnectionStatusFetched(new ConnectionStatusModel(state, newList)); + } + + private void onConnectionStatusFetched(ConnectionStatusModel status) { + List<CachedBluetoothDevice> newList = status.getConnectedDevices(); + int state = status.getMaxConnectionState(); synchronized (mConnectedDevices) { mConnectedDevices.clear(); mConnectedDevices.addAll(newList); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt new file mode 100644 index 000000000000..80f3d76f0897 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.policy.bluetooth + +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothProfile +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LocalBluetoothManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Repository for information about bluetooth connections. + * + * Note: Right now, this class and [BluetoothController] co-exist. Any new code should go in this + * implementation, but external clients should query [BluetoothController] instead of this class for + * now. + */ +interface BluetoothRepository { + /** + * Fetches the connection statuses for the given [currentDevices] and invokes [callback] once + * those statuses have been fetched. The fetching occurs on a background thread because IPCs may + * be required to fetch the statuses (see b/271058380). + */ + fun fetchConnectionStatusInBackground( + currentDevices: Collection<CachedBluetoothDevice>, + callback: ConnectionStatusFetchedCallback, + ) +} + +/** Implementation of [BluetoothRepository]. */ +@SysUISingleton +class BluetoothRepositoryImpl +@Inject +constructor( + @Application private val scope: CoroutineScope, + @Background private val bgDispatcher: CoroutineDispatcher, + private val localBluetoothManager: LocalBluetoothManager?, +) : BluetoothRepository { + override fun fetchConnectionStatusInBackground( + currentDevices: Collection<CachedBluetoothDevice>, + callback: ConnectionStatusFetchedCallback, + ) { + scope.launch { + val result = fetchConnectionStatus(currentDevices) + callback.onConnectionStatusFetched(result) + } + } + + private suspend fun fetchConnectionStatus( + currentDevices: Collection<CachedBluetoothDevice>, + ): ConnectionStatusModel { + return withContext(bgDispatcher) { + val minimumMaxConnectionState = + localBluetoothManager?.bluetoothAdapter?.connectionState + ?: BluetoothProfile.STATE_DISCONNECTED + var maxConnectionState = + if (currentDevices.isEmpty()) { + minimumMaxConnectionState + } else { + currentDevices + .maxOf { it.maxConnectionState } + .coerceAtLeast(minimumMaxConnectionState) + } + + val connectedDevices = currentDevices.filter { it.isConnected } + + if ( + connectedDevices.isEmpty() && maxConnectionState == BluetoothAdapter.STATE_CONNECTED + ) { + // If somehow we think we are connected, but have no connected devices, we aren't + // connected. + maxConnectionState = BluetoothAdapter.STATE_DISCONNECTED + } + + ConnectionStatusModel(maxConnectionState, connectedDevices) + } + } +} + +data class ConnectionStatusModel( + /** The maximum connection state out of all current devices. */ + val maxConnectionState: Int, + /** A list of devices that are currently connected. */ + val connectedDevices: List<CachedBluetoothDevice>, +) + +/** Callback notified when the new status has been fetched. */ +fun interface ConnectionStatusFetchedCallback { + fun onConnectionStatusFetched(status: ConnectionStatusModel) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 1b7353923ada..e1a7b6d59f41 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -62,15 +62,16 @@ import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.statusbar.policy.WalletControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; - -import java.util.concurrent.Executor; - -import javax.inject.Named; +import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository; +import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl; import dagger.Binds; import dagger.Module; import dagger.Provides; +import java.util.concurrent.Executor; + +import javax.inject.Named; /** Dagger Module for code in the statusbar.policy package. */ @Module @@ -84,6 +85,10 @@ public interface StatusBarPolicyModule { /** */ @Binds + BluetoothRepository provideBluetoothRepository(BluetoothRepositoryImpl impl); + + /** */ + @Binds CastController provideCastController(CastControllerImpl controllerImpl); /** */ diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt index 58f22466d44d..57b9f914d4d6 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt +++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt @@ -56,18 +56,6 @@ class DynamicColors { Pair.create("on_error", MDC.onError()), Pair.create("error_container", MDC.errorContainer()), Pair.create("on_error_container", MDC.onErrorContainer()), - Pair.create("primary_fixed", MDC.primaryFixed()), - Pair.create("primary_fixed_dim", MDC.primaryFixedDim()), - Pair.create("on_primary_fixed", MDC.onPrimaryFixed()), - Pair.create("on_primary_fixed_variant", MDC.onPrimaryFixedVariant()), - Pair.create("secondary_fixed", MDC.secondaryFixed()), - Pair.create("secondary_fixed_dim", MDC.secondaryFixedDim()), - Pair.create("on_secondary_fixed", MDC.onSecondaryFixed()), - Pair.create("on_secondary_fixed_variant", MDC.onSecondaryFixedVariant()), - Pair.create("tertiary_fixed", MDC.tertiaryFixed()), - Pair.create("tertiary_fixed_dim", MDC.tertiaryFixedDim()), - Pair.create("on_tertiary_fixed", MDC.onTertiaryFixed()), - Pair.create("on_tertiary_fixed_variant", MDC.onTertiaryFixedVariant()), Pair.create("control_activated", MDC.controlActivated()), Pair.create("control_normal", MDC.controlNormal()), Pair.create("control_highlight", MDC.controlHighlight()), @@ -92,7 +80,24 @@ class DynamicColors { Pair.create( "palette_key_color_neutral_variant", MDC.neutralVariantPaletteKeyColor() - ) + ), + ) + + @JvmField + val FIXED_COLORS_MAPPED: List<Pair<String, DynamicColor>> = + arrayListOf( + Pair.create("primary_fixed", MDC.primaryFixed()), + Pair.create("primary_fixed_dim", MDC.primaryFixedDim()), + Pair.create("on_primary_fixed", MDC.onPrimaryFixed()), + Pair.create("on_primary_fixed_variant", MDC.onPrimaryFixedVariant()), + Pair.create("secondary_fixed", MDC.secondaryFixed()), + Pair.create("secondary_fixed_dim", MDC.secondaryFixedDim()), + Pair.create("on_secondary_fixed", MDC.onSecondaryFixed()), + Pair.create("on_secondary_fixed_variant", MDC.onSecondaryFixedVariant()), + Pair.create("tertiary_fixed", MDC.tertiaryFixed()), + Pair.create("tertiary_fixed_dim", MDC.tertiaryFixedDim()), + Pair.create("on_tertiary_fixed", MDC.onTertiaryFixed()), + Pair.create("on_tertiary_fixed_variant", MDC.onTertiaryFixedVariant()), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 4b73d6190d99..c1999b284553 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -624,6 +624,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { FabricatedOverlay overlay = newFabricatedOverlay("dynamic"); assignDynamicPaletteToOverlay(overlay, true /* isDark */); assignDynamicPaletteToOverlay(overlay, false /* isDark */); + assignFixedColorsToOverlay(overlay); return overlay; } @@ -638,6 +639,15 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { }); } + private void assignFixedColorsToOverlay(FabricatedOverlay overlay) { + DynamicColors.FIXED_COLORS_MAPPED.forEach(p -> { + String resourceName = "android:color/system_" + p.first; + int colorValue = p.second.getArgb(mDynamicSchemeLight); + overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue, + null /* configuration */); + }); + } + /** * Checks if the color scheme in mColorScheme matches the current system palettes. * @param managedProfiles List of managed profiles for this user. @@ -666,7 +676,9 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { && res.getColor(android.R.color.system_primary_container_dark, theme) == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeDark) && res.getColor(android.R.color.system_primary_container_light, theme) - == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeLight))) { + == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeLight) + && res.getColor(android.R.color.system_primary_fixed, theme) + == MaterialDynamicColors.primaryFixed().getArgb(mDynamicSchemeLight))) { return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index 0a78d896e1bd..d9a8e0cfb53a 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -23,8 +23,6 @@ import android.content.Context; import android.hardware.SensorPrivacyManager; import android.os.Handler; -import androidx.annotation.Nullable; - import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardViewController; import com.android.systemui.dagger.ReferenceSystemUIModule; @@ -75,13 +73,13 @@ import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl; import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler; import com.android.systemui.volume.dagger.VolumeModule; -import javax.inject.Named; - import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoSet; +import javax.inject.Named; + /** * A TV specific version of {@link ReferenceSystemUIModule}. * @@ -105,9 +103,8 @@ public abstract class TvSystemUIModule { @SysUISingleton @Provides @Named(LEAK_REPORT_EMAIL_NAME) - @Nullable static String provideLeakReportEmail() { - return null; + return ""; } @Binds diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java index a0d22f388cbc..c1b7d7a874f3 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java @@ -18,7 +18,6 @@ package com.android.systemui.util.leak; import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME; -import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -29,6 +28,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Debug; import android.os.SystemProperties; +import android.text.TextUtils; import android.util.Log; import androidx.core.content.FileProvider; @@ -68,7 +68,7 @@ public class LeakReporter { @Inject public LeakReporter(Context context, UserTracker userTracker, LeakDetector leakDetector, - @Nullable @Named(LEAK_REPORT_EMAIL_NAME) String leakReportEmail) { + @Named(LEAK_REPORT_EMAIL_NAME) String leakReportEmail) { mContext = context; mUserTracker = userTracker; mLeakDetector = leakDetector; @@ -150,9 +150,8 @@ public class LeakReporter { intent.setClipData(clipData); intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments); - String leakReportEmail = mLeakReportEmail; - if (leakReportEmail != null) { - intent.putExtra(Intent.EXTRA_EMAIL, new String[] { leakReportEmail }); + if (!TextUtils.isEmpty(mLeakReportEmail)) { + intent.putExtra(Intent.EXTRA_EMAIL, new String[] { mLeakReportEmail }); } return intent; diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java index fc0033d71844..2d1e622fbdce 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/Events.java +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -98,6 +98,8 @@ public class Events { public static final int DISMISS_REASON_OUTPUT_CHOOSER = 8; public static final int DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED = 9; public static final int DISMISS_REASON_CSD_WARNING_TIMEOUT = 10; + public static final int DISMISS_REASON_POSTURE_CHANGED = 11; + public static final String[] DISMISS_REASONS = { "unknown", "touch_outside", @@ -109,7 +111,8 @@ public class Events { "a11y_stream_changed", "output_chooser", "usb_temperature_below_threshold", - "csd_warning_timeout" + "csd_warning_timeout", + "posture_changed" }; public static final int SHOW_REASON_UNKNOWN = 0; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 91078dc65477..f893cf4694f9 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -34,6 +34,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL; import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder; +import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; import android.animation.Animator; @@ -129,6 +130,7 @@ import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.AlphaTintDrawableWrapper; import com.android.systemui.util.DeviceConfigProxy; @@ -184,7 +186,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final boolean mChangeVolumeRowTintWhenInactive; private final Context mContext; - private final H mHandler = new H(); + private final H mHandler; private final VolumeDialogController mController; private final DeviceProvisionedController mDeviceProvisionedController; private final Region mTouchableRegion = new Region(); @@ -259,16 +261,13 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final AccessibilityManagerWrapper mAccessibilityMgr; private final Object mSafetyWarningLock = new Object(); private final Accessibility mAccessibility = new Accessibility(); - private final ConfigurationController mConfigurationController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final VolumePanelFactory mVolumePanelFactory; private final CsdWarningDialog.Factory mCsdWarningDialogFactory; private final ActivityStarter mActivityStarter; - private boolean mShowing; private boolean mShowA11yStream; - private int mActiveStream; private int mPrevActiveStream; private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE; @@ -300,6 +299,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, @VisibleForTesting int mVolumeRingerMuteIconDrawableId; + private int mOriginalGravity; + private final DevicePostureController.Callback mDevicePostureControllerCallback; + private final DevicePostureController mDevicePostureController; + private @DevicePostureController.DevicePostureInt int mDevicePosture; + private int mOrientation; + public VolumeDialogImpl( Context context, VolumeDialogController volumeDialogController, @@ -313,9 +318,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, DeviceConfigProxy deviceConfigProxy, Executor executor, CsdWarningDialog.Factory csdWarningDialogFactory, + DevicePostureController devicePostureController, + Looper looper, DumpManager dumpManager) { mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); + mHandler = new H(looper); mController = volumeDialogController; mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); @@ -357,6 +365,16 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, initDimens(); + mOrientation = mContext.getResources().getConfiguration().orientation; + mDevicePostureController = devicePostureController; + if (mDevicePostureController != null) { + int initialPosture = mDevicePostureController.getDevicePosture(); + mDevicePosture = initialPosture; + mDevicePostureControllerCallback = this::onPostureChanged; + } else { + mDevicePostureControllerCallback = null; + } + mDeviceConfigProxy = deviceConfigProxy; mExecutor = executor; mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, @@ -365,6 +383,25 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } /** + * Adjust the dialog location on the screen in order to avoid drawing on the hinge. + */ + private void adjustPositionOnScreen() { + final boolean isPortrait = mOrientation == Configuration.ORIENTATION_PORTRAIT; + final boolean isHalfOpen = + mDevicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED; + final boolean isTabletop = isPortrait && isHalfOpen; + WindowManager.LayoutParams lp = mWindow.getAttributes(); + int gravity = isTabletop ? (mOriginalGravity | Gravity.TOP) : mOriginalGravity; + mWindowGravity = Gravity.getAbsoluteGravity(gravity, + mContext.getResources().getConfiguration().getLayoutDirection()); + lp.gravity = mWindowGravity; + } + + @VisibleForTesting int getWindowGravity() { + return mWindowGravity; + } + + /** * If ringer and notification are the same stream (T and earlier), use notification-like bell * icon set. * If ringer and notification are separated, then use generic speaker icons. @@ -419,6 +456,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mExecutor, this::onDeviceConfigChange); + + if (mDevicePostureController != null) { + mDevicePostureController.addCallback(mDevicePostureControllerCallback); + } } @Override @@ -427,6 +468,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mHandler.removeCallbacksAndMessages(null); mConfigurationController.removeCallback(this); mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange); + if (mDevicePostureController != null) { + mDevicePostureController.removeCallback(mDevicePostureControllerCallback); + } } /** @@ -441,7 +485,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mSeparateNotification = newVal; updateRingerModeIconSet(); updateRingRowIcon(); - } } } @@ -500,7 +543,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private void initDialog(int lockTaskModeState) { mDialog = new CustomDialog(mContext); - initDimens(); mConfigurableTexts = new ConfigurableTexts(mContext); @@ -524,14 +566,13 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, lp.setTitle(VolumeDialogImpl.class.getSimpleName()); lp.windowAnimations = -1; - mWindowGravity = Gravity.getAbsoluteGravity( - mContext.getResources().getInteger(R.integer.volume_dialog_gravity), + mOriginalGravity = mContext.getResources().getInteger(R.integer.volume_dialog_gravity); + mWindowGravity = Gravity.getAbsoluteGravity(mOriginalGravity, mContext.getResources().getConfiguration().getLayoutDirection()); lp.gravity = mWindowGravity; mWindow.setAttributes(lp); mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT); - mDialog.setContentView(R.layout.volume_dialog); mDialogView = mDialog.findViewById(R.id.volume_dialog); mDialogView.setAlpha(0); @@ -1539,8 +1580,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, animator.translationX( (isWindowGravityLeft() ? -1 : 1) * mDialogView.getWidth() / 2.0f); } + animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS, mDialogHideAnimationDurationMs)).start(); + checkODICaptionsTooltip(true); synchronized (mSafetyWarningLock) { if (mSafetyWarning != null) { @@ -2237,6 +2280,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mTopContainer.setBackground(background); } + @Override + public void onConfigChanged(Configuration config) { + mOrientation = config.orientation; + } + private final VolumeDialogController.Callbacks mControllerCallbackH = new VolumeDialogController.Callbacks() { @Override @@ -2313,6 +2361,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } }; + @VisibleForTesting void onPostureChanged(int posture) { + dismiss(DISMISS_REASON_POSTURE_CHANGED); + mDevicePosture = posture; + } + private final class H extends Handler { private static final int SHOW = 1; private static final int DISMISS = 2; @@ -2323,8 +2376,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private static final int STATE_CHANGED = 7; private static final int CSD_TIMEOUT = 8; - public H() { - super(Looper.getMainLooper()); + H(Looper looper) { + super(looper); } @Override @@ -2370,6 +2423,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, protected void onStart() { super.setCanceledOnTouchOutside(true); super.onStart(); + adjustPositionOnScreen(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index 14d3ca334073..bb04f82fcffa 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -18,6 +18,7 @@ package com.android.systemui.volume.dagger; import android.content.Context; import android.media.AudioManager; +import android.os.Looper; import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.dagger.qualifiers.Main; @@ -28,6 +29,7 @@ import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.volume.CsdWarningDialog; @@ -42,7 +44,6 @@ import dagger.Provides; import java.util.concurrent.Executor; - /** Dagger Module for code in the volume package. */ @Module public interface VolumeModule { @@ -65,6 +66,7 @@ public interface VolumeModule { DeviceConfigProxy deviceConfigProxy, @Main Executor executor, CsdWarningDialog.Factory csdFactory, + DevicePostureController devicePostureController, DumpManager dumpManager) { VolumeDialogImpl impl = new VolumeDialogImpl( context, @@ -79,6 +81,8 @@ public interface VolumeModule { deviceConfigProxy, executor, csdFactory, + devicePostureController, + Looper.getMainLooper(), dumpManager); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java index 492f2318fec6..81d04d4458c0 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java @@ -338,7 +338,12 @@ public class WalletScreenController implements */ QAWalletCardViewInfo(Context context, WalletCard walletCard) { mWalletCard = walletCard; - mCardDrawable = mWalletCard.getCardImage().loadDrawable(context); + Icon cardImageIcon = mWalletCard.getCardImage(); + if (cardImageIcon.getType() == Icon.TYPE_URI) { + mCardDrawable = null; + } else { + mCardDrawable = mWalletCard.getCardImage().loadDrawable(context); + } Icon icon = mWalletCard.getCardIcon(); mIconDrawable = icon == null ? null : icon.loadDrawable(context); } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java index cd1ad1ba788f..316b54eb0c80 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java @@ -173,7 +173,7 @@ public class ImageWallpaper extends WallpaperService { .isLockscreenLiveWallpaperEnabled(); mSurfaceHolder = surfaceHolder; Rect dimensions = mIsLockscreenLiveWallpaperEnabled - ? mWallpaperManager.peekBitmapDimensions(getSourceFlag()) + ? mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true) : mWallpaperManager.peekBitmapDimensions(); int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width()); int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height()); @@ -325,7 +325,7 @@ public class ImageWallpaper extends WallpaperService { try { bitmap = mIsLockscreenLiveWallpaperEnabled ? mWallpaperManager.getBitmapAsUser( - mUserTracker.getUserId(), false, getSourceFlag()) + mUserTracker.getUserId(), false, getSourceFlag(), true) : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false); if (bitmap != null && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) { @@ -347,7 +347,7 @@ public class ImageWallpaper extends WallpaperService { try { bitmap = mIsLockscreenLiveWallpaperEnabled ? mWallpaperManager.getBitmapAsUser( - mUserTracker.getUserId(), false, getSourceFlag()) + mUserTracker.getUserId(), false, getSourceFlag(), true) : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false); } catch (RuntimeException | OutOfMemoryError e) { Log.w(TAG, "Unable to load default wallpaper!", e); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt index f3a100bd55e5..239e317b92f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt @@ -102,6 +102,12 @@ class FingerprintRepositoryImplTest : SysuiTestCase() { 540 /* sensorLocationX */, 1636 /* sensorLocationY */, 130 /* sensorRadius */ + ), + SensorLocationInternal( + "display_id_1" /* displayId */, + 100 /* sensorLocationX */, + 300 /* sensorLocationY */, + 20 /* sensorRadius */ ) ) ) @@ -112,7 +118,17 @@ class FingerprintRepositoryImplTest : SysuiTestCase() { assertThat(repository.sensorId.value).isEqualTo(1) assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG) assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR) - with(repository.sensorLocation.value) { + + assertThat(repository.sensorLocations.value.size).isEqualTo(2) + assertThat(repository.sensorLocations.value).containsKey("display_id_1") + with(repository.sensorLocations.value["display_id_1"]!!) { + assertThat(displayId).isEqualTo("display_id_1") + assertThat(sensorLocationX).isEqualTo(100) + assertThat(sensorLocationY).isEqualTo(300) + assertThat(sensorRadius).isEqualTo(20) + } + assertThat(repository.sensorLocations.value).containsKey("") + with(repository.sensorLocations.value[""]!!) { assertThat(displayId).isEqualTo("") assertThat(sensorLocationX).isEqualTo(540) assertThat(sensorLocationY).isEqualTo(1636) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt new file mode 100644 index 000000000000..fd96cf45504b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.interactor + +import android.hardware.biometrics.SensorLocationInternal +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(JUnit4::class) +class SideFpsOverlayInteractorTest : SysuiTestCase() { + + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + private lateinit var testScope: TestScope + + private val fingerprintRepository = FakeFingerprintPropertyRepository() + + private lateinit var interactor: SideFpsOverlayInteractor + + @Before + fun setup() { + testScope = TestScope(StandardTestDispatcher()) + interactor = SideFpsOverlayInteractorImpl(fingerprintRepository) + } + + @Test + fun testGetOverlayOffsets() = + testScope.runTest { + fingerprintRepository.setProperties( + sensorId = 1, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.REAR, + sensorLocations = + mapOf( + "" to + SensorLocationInternal( + "" /* displayId */, + 540 /* sensorLocationX */, + 1636 /* sensorLocationY */, + 130 /* sensorRadius */ + ), + "display_id_1" to + SensorLocationInternal( + "display_id_1" /* displayId */, + 100 /* sensorLocationX */, + 300 /* sensorLocationY */, + 20 /* sensorRadius */ + ) + ) + ) + + var offsets = interactor.getOverlayOffsets("display_id_1") + assertThat(offsets.displayId).isEqualTo("display_id_1") + assertThat(offsets.sensorLocationX).isEqualTo(100) + assertThat(offsets.sensorLocationY).isEqualTo(300) + assertThat(offsets.sensorRadius).isEqualTo(20) + + offsets = interactor.getOverlayOffsets("invalid_display_id") + assertThat(offsets.displayId).isEqualTo("") + assertThat(offsets.sensorLocationX).isEqualTo(540) + assertThat(offsets.sensorLocationY).isEqualTo(1636) + assertThat(offsets.sensorRadius).isEqualTo(130) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 8f58140bce43..1d8b5ca1f54a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -52,6 +52,8 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.logging.InstanceId; +import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardSecurityView; @@ -68,6 +70,7 @@ import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -77,6 +80,7 @@ import com.android.systemui.shade.ShadeWindowLogger; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -143,6 +147,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private FalsingCollectorFake mFalsingCollector; private @Mock CentralSurfaces mCentralSurfaces; + private @Mock UiEventLogger mUiEventLogger; + private @Mock SessionTracker mSessionTracker; private FakeFeatureFlags mFeatureFlags; @@ -543,9 +549,32 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { assertTrue(mViewMediator.isShowingAndNotOccluded()); } + @Test + public void testWakeAndUnlocking() { + mViewMediator.onWakeAndUnlocking(); + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); + } + + @Test + public void testOnStartedWakingUp_logsUiEvent() { + final InstanceId instanceId = InstanceId.fakeInstanceId(8); + when(mSessionTracker.getSessionId((anyInt()))).thenReturn(instanceId); + mViewMediator.onStartedWakingUp(PowerManager.WAKE_REASON_LIFT, false); + + verify(mUiEventLogger).logWithInstanceIdAndPosition( + eq(BiometricUnlockController.BiometricUiEvent.STARTED_WAKING_UP), + anyInt(), + any(), + eq(instanceId), + eq(PowerManager.WAKE_REASON_LIFT) + ); + } + private void createAndStartViewMediator() { mViewMediator = new KeyguardViewMediator( mContext, + mUiEventLogger, + mSessionTracker, mUserTracker, mFalsingCollector, mLockPatternUtils, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index 21a7a340aa80..425d0bc47a4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -39,6 +39,7 @@ import android.testing.TestableLooper; import android.util.FeatureFlagUtils; import android.view.View; +import androidx.annotation.NonNull; import androidx.test.filters.MediumTest; import com.android.internal.logging.UiEventLogger; @@ -64,6 +65,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.function.Consumer; @MediumTest @RunWith(AndroidTestingRunner.class) @@ -89,7 +91,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); private final MediaMetadata mMediaMetadata = mock(MediaMetadata.class); - private final MediaDescription mMediaDescription = mock(MediaDescription.class); + private final MediaDescription mMediaDescription = mock(MediaDescription.class); private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( NearbyMediaDevicesManager.class); private final AudioManager mAudioManager = mock(AudioManager.class); @@ -102,6 +104,11 @@ public class MediaOutputDialogTest extends SysuiTestCase { private MediaOutputController mMediaOutputController; private final List<String> mFeatures = new ArrayList<>(); + @Override + protected boolean shouldFailOnLeakedReceiver() { + return true; + } + @Before public void setUp() { when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); @@ -120,8 +127,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, mKeyguardManager, mFlags); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; - mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mMediaOutputController, mUiEventLogger); + mMediaOutputDialog = makeTestDialog(mMediaOutputController); mMediaOutputDialog.show(); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice); @@ -130,7 +136,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { @After public void tearDown() { - mMediaOutputDialog.dismissDialog(); + mMediaOutputDialog.dismiss(); } @Test @@ -311,11 +317,9 @@ public class MediaOutputDialogTest extends SysuiTestCase { MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mockMediaOutputController, mUiEventLogger); - testDialog.show(); - - assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + withTestDialog(mockMediaOutputController, testDialog -> { + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + }); } @Test @@ -328,11 +332,9 @@ public class MediaOutputDialogTest extends SysuiTestCase { when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true); when(mockMediaOutputController.isPlaying()).thenReturn(true); when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false); - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mockMediaOutputController, mUiEventLogger); - testDialog.show(); - - assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + withTestDialog(mockMediaOutputController, testDialog -> { + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + }); } @Test @@ -341,11 +343,9 @@ public class MediaOutputDialogTest extends SysuiTestCase { when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null); when(mockMediaOutputController.isPlaying()).thenReturn(false); - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mockMediaOutputController, mUiEventLogger); - testDialog.show(); - - testDialog.onStopButtonClick(); + withTestDialog(mockMediaOutputController, testDialog -> { + testDialog.onStopButtonClick(); + }); verify(mockMediaOutputController).releaseSession(); } @@ -354,13 +354,22 @@ public class MediaOutputDialogTest extends SysuiTestCase { // Check the visibility metric logging by creating a new MediaOutput dialog, // and verify if the calling times increases. public void onCreate_ShouldLogVisibility() { - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mMediaOutputController, mUiEventLogger); - testDialog.show(); - - testDialog.dismissDialog(); + withTestDialog(mMediaOutputController, testDialog -> {}); verify(mUiEventLogger, times(2)) .log(MediaOutputDialog.MediaOutputEvent.MEDIA_OUTPUT_DIALOG_SHOW); } + + @NonNull + private MediaOutputDialog makeTestDialog(MediaOutputController controller) { + return new MediaOutputDialog(mContext, false, mBroadcastSender, + controller, mUiEventLogger); + } + + private void withTestDialog(MediaOutputController controller, Consumer<MediaOutputDialog> c) { + MediaOutputDialog testDialog = makeTestDialog(controller); + testDialog.show(); + c.accept(testDialog); + testDialog.dismiss(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index e4d8b2598fe3..810ab344e7d8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -145,7 +145,8 @@ public class QSTileHostTest extends SysuiTestCase { mMainExecutor = new FakeExecutor(new FakeSystemClock()); mSharedPreferencesByUser = new SparseArray<>(); - when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class))) + when(mTileLifecycleManagerFactory + .create(any(Intent.class), any(UserHandle.class))) .thenReturn(mTileLifecycleManager); when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())) .thenAnswer((Answer<SharedPreferences>) invocation -> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 2e6b0cf7b0a8..67587e3a8914 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -60,6 +60,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Before; @@ -81,6 +83,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { private ComponentName mTileServiceComponentName; private Intent mTileServiceIntent; private UserHandle mUser; + private FakeExecutor mExecutor; private HandlerThread mThread; private Handler mHandler; private TileLifecycleManager mStateManager; @@ -109,12 +112,14 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mThread = new HandlerThread("TestThread"); mThread.start(); mHandler = Handler.createAsync(mThread.getLooper()); + mExecutor = new FakeExecutor(new FakeSystemClock()); mStateManager = new TileLifecycleManager(mHandler, mWrappedContext, mock(IQSService.class), mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); } @After @@ -152,7 +157,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { @Test public void testBind() { - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); } @@ -160,7 +166,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { public void testPackageReceiverExported() throws Exception { // Make sure that we register a receiver setPackageEnabled(false); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); IntentFilter filter = mWrappedContext.mLastIntentFilter; assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_ADDED)); assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)); @@ -170,14 +177,17 @@ public class TileLifecycleManagerTest extends SysuiTestCase { @Test public void testUnbind() { - mStateManager.setBindService(true); - mStateManager.setBindService(false); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); assertFalse(mContext.isBound(mTileServiceComponentName)); } @Test public void testTileServiceCallbacks() throws Exception { - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); mStateManager.onTileAdded(); verify(mMockTileService).onTileAdded(); mStateManager.onStartListening(); @@ -193,7 +203,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { @Test public void testAddedBeforeBind() throws Exception { mStateManager.onTileAdded(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); verify(mMockTileService).onTileAdded(); @@ -203,7 +214,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { public void testListeningBeforeBind() throws Exception { mStateManager.onTileAdded(); mStateManager.onStartListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); verify(mMockTileService).onTileAdded(); @@ -215,7 +227,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onTileAdded(); mStateManager.onStartListening(); mStateManager.onClick(null); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); verify(mMockTileService).onTileAdded(); @@ -228,10 +241,12 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onTileAdded(); mStateManager.onStartListening(); mStateManager.onStopListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); - mStateManager.setBindService(false); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); assertFalse(mContext.isBound(mTileServiceComponentName)); verify(mMockTileService, never()).onStartListening(); } @@ -242,10 +257,12 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onStartListening(); mStateManager.onClick(null); mStateManager.onStopListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); - mStateManager.setBindService(false); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); assertFalse(mContext.isBound(mTileServiceComponentName)); verify(mMockTileService, never()).onClick(null); } @@ -255,7 +272,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onTileAdded(); mStateManager.onStartListening(); setPackageEnabled(false); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); // Package not available, not yet created. verifyBind(0); @@ -267,18 +285,19 @@ public class TileLifecycleManagerTest extends SysuiTestCase { Intent.ACTION_PACKAGE_CHANGED, Uri.fromParts( "package", mTileServiceComponentName.getPackageName(), null))); + mExecutor.runAllReady(); verifyBind(1); } @Test public void testKillProcess() throws Exception { mStateManager.onStartListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); mStateManager.setBindRetryDelay(0); + mExecutor.runAllReady(); mStateManager.onServiceDisconnected(mTileServiceComponentName); - - // Guarantees mHandler has processed all messages. - assertTrue(mHandler.runWithScissors(()->{}, TEST_FAIL_TIMEOUT)); + mExecutor.runAllReady(); // Two calls: one for the first bind, one for the restart. verifyBind(2); @@ -299,9 +318,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); - manager.setBindService(true); + manager.executeSetBindService(true); + mExecutor.runAllReady(); ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class); verify(falseContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any()); @@ -318,9 +339,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); - manager.setBindService(true); + manager.executeSetBindService(true); + mExecutor.runAllReady(); int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE | Context.BIND_WAIVE_PRIORITY; @@ -337,9 +360,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); - manager.setBindService(true); + manager.executeSetBindService(true); + mExecutor.runAllReady(); int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java index 9ca7a8521e95..28331bbfafb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java @@ -187,7 +187,7 @@ public class TileServiceManagerTest extends SysuiTestCase { mTileServiceManager.setBindAllowed(true); ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class); - verify(mTileLifecycle, times(1)).setBindService(captor.capture()); + verify(mTileLifecycle, times(1)).executeSetBindService(captor.capture()); assertTrue((boolean) captor.getValue()); mTileServiceManager.setBindRequested(false); @@ -198,7 +198,7 @@ public class TileServiceManagerTest extends SysuiTestCase { mTileServiceManager.setBindAllowed(false); captor = ArgumentCaptor.forClass(Boolean.class); - verify(mTileLifecycle, times(2)).setBindService(captor.capture()); + verify(mTileLifecycle, times(2)).executeSetBindService(captor.capture()); assertFalse((boolean) captor.getValue()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 12b5656725eb..4bc16a52f8dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -48,6 +48,9 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Assert; @@ -118,7 +121,8 @@ public class TileServicesTest extends SysuiTestCase { mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher, mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController, - mPanelInteractor, mCustomTileAddedRepository); + mPanelInteractor, mCustomTileAddedRepository, + new FakeExecutor(new FakeSystemClock())); } @After @@ -297,10 +301,10 @@ public class TileServicesTest extends SysuiTestCase { BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, KeyguardStateController keyguardStateController, CommandQueue commandQueue, StatusBarIconController statusBarIconController, PanelInteractor panelInteractor, - CustomTileAddedRepository customTileAddedRepository) { + CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController, commandQueue, statusBarIconController, panelInteractor, - customTileAddedRepository); + customTileAddedRepository, executor); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index b089e380304d..b00ae399af21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -93,6 +93,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private static final String CARD_DESCRIPTION = "•••• 1234"; private static final Icon CARD_IMAGE = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888)); + private static final int PRIMARY_USER_ID = 0; + private static final int SECONDARY_USER_ID = 10; private final Drawable mTileIcon = mContext.getDrawable(R.drawable.ic_qs_wallet); private final Intent mWalletIntent = new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET) @@ -120,6 +122,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private SecureSettings mSecureSettings; @Mock private QuickAccessWalletController mController; + @Mock + private Icon mCardImage; @Captor ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor; @@ -142,6 +146,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true); when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true); when(mController.getWalletClient()).thenReturn(mQuickAccessWalletClient); + when(mCardImage.getType()).thenReturn(Icon.TYPE_URI); + when(mCardImage.loadDrawableAsUser(any(), eq(SECONDARY_USER_ID))).thenReturn(null); mTile = new QuickAccessWalletTile( mHost, @@ -382,6 +388,28 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { } @Test + public void testQueryCards_notCurrentUser_hasCards_noSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + WalletCard walletCard = + new WalletCard.Builder( + CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build(); + GetWalletCardsResponse response = + new GetWalletCardsResponse(Collections.singletonList(walletCard), 0); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + + assertNull(mTile.getState().sideViewCustomDrawable); + } + + @Test public void testQueryCards_noCards_notUpdateSideViewDrawable() { setUpWalletCard(/* hasCard= */ false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index c7ea09cb519d..2e5afa4a8b50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -340,6 +340,11 @@ public class NetworkControllerBaseTest extends SysuiTestCase { } public void setConnectivityViaCallbackInNetworkController( + Network network, NetworkCapabilities networkCapabilities) { + mDefaultCallbackInNetworkController.onCapabilitiesChanged(network, networkCapabilities); + } + + public void setConnectivityViaCallbackInNetworkController( int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(mNetCapabilities); @@ -351,6 +356,13 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mock(Network.class), builder.build()); } + public void setConnectivityViaDefaultAndNormalCallbackInWifiTracker( + Network network, NetworkCapabilities networkCapabilities) { + mNetworkCallback.onAvailable(network); + mNetworkCallback.onCapabilitiesChanged(network, networkCapabilities); + mDefaultCallbackInWifiTracker.onCapabilitiesChanged(network, networkCapabilities); + } + public void setConnectivityViaCallbackInWifiTracker( int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) { final NetworkCapabilities.Builder builder = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java index 68170ea4b518..44a1c50e5a58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.connectivity; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; + import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -25,6 +28,7 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.net.ConnectivityManager; +import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.vcn.VcnTransportInfo; @@ -43,6 +47,8 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import java.util.Collections; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -269,6 +275,83 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } } + /** Test for b/225902574. */ + @Test + public void vcnOnlyOnUnderlyingNetwork() { + setWifiEnabled(true); + + // Set up a carrier merged network... + WifiInfo underlyingCarrierMergedInfo = Mockito.mock(WifiInfo.class); + when(underlyingCarrierMergedInfo.isCarrierMerged()).thenReturn(true); + when(underlyingCarrierMergedInfo.isPrimary()).thenReturn(true); + int zeroLevel = 0; + when(underlyingCarrierMergedInfo.getRssi()).thenReturn(calculateRssiForLevel(zeroLevel)); + + NetworkCapabilities underlyingNetworkCapabilities = Mockito.mock(NetworkCapabilities.class); + when(underlyingNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) + .thenReturn(true); + when(underlyingNetworkCapabilities.getTransportInfo()) + .thenReturn(underlyingCarrierMergedInfo); + + Network underlyingNetwork = Mockito.mock(Network.class); + when(mMockCm.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingNetworkCapabilities); + + NetworkCapabilities.Builder mainCapabilitiesBuilder = new NetworkCapabilities.Builder(); + mainCapabilitiesBuilder.addTransportType(TRANSPORT_CELLULAR); + mainCapabilitiesBuilder.setTransportInfo(null); + // And make the carrier merged network the underlying network, *not* the main network. + mainCapabilitiesBuilder.setUnderlyingNetworks(Collections.singletonList(underlyingNetwork)); + + Network primaryNetwork = Mockito.mock(Network.class); + int primaryNetworkId = 1; + when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId); + + // WHEN this primary network with underlying carrier merged information is sent + setConnectivityViaDefaultAndNormalCallbackInWifiTracker( + primaryNetwork, mainCapabilitiesBuilder.build()); + + // THEN we see the mobile data indicators for carrier merged + verifyLastMobileDataIndicatorsForVcn( + /* visible= */ true, + /* level= */ zeroLevel, + TelephonyIcons.ICON_CWF, + /* inet= */ false); + + // For each level... + for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) { + int rssi = calculateRssiForLevel(testLevel); + when(underlyingCarrierMergedInfo.getRssi()).thenReturn(rssi); + // WHEN the new level is sent to the callbacks + setConnectivityViaDefaultAndNormalCallbackInWifiTracker( + primaryNetwork, mainCapabilitiesBuilder.build()); + + // WHEN the network is validated + mainCapabilitiesBuilder.addCapability(NET_CAPABILITY_VALIDATED); + setConnectivityViaCallbackInNetworkController( + primaryNetwork, mainCapabilitiesBuilder.build()); + + // THEN we see the mobile data indicators with inet=true (no exclamation mark) + verifyLastMobileDataIndicatorsForVcn( + /* visible= */ true, + testLevel, + TelephonyIcons.ICON_CWF, + /* inet= */ true); + + // WHEN the network is not validated + mainCapabilitiesBuilder.removeCapability(NET_CAPABILITY_VALIDATED); + setConnectivityViaCallbackInNetworkController( + primaryNetwork, mainCapabilitiesBuilder.build()); + + // THEN we see the mobile data indicators with inet=false (exclamation mark) + verifyLastMobileDataIndicatorsForVcn( + /* visible= */ true, + testLevel, + TelephonyIcons.ICON_CWF, + /* inet= */ false); + } + } + @Test public void testDisableWiFiWithVcnWithUnderlyingWifi() { String testSsid = "Test VCN SSID"; @@ -290,11 +373,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } protected void setWifiLevel(int level) { - float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1); - int rssi = (int) (MIN_RSSI + level * amountPerLevel); - // Put RSSI in the middle of the range. - rssi += amountPerLevel / 2; - when(mWifiInfo.getRssi()).thenReturn(rssi); + when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level)); setConnectivityViaCallbackInWifiTracker( NetworkCapabilities.TRANSPORT_WIFI, false, true, mWifiInfo); } @@ -312,19 +391,23 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } protected void setWifiLevelForVcn(int level) { - float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1); - int rssi = (int) (MIN_RSSI + level * amountPerLevel); - // Put RSSI in the middle of the range. - rssi += amountPerLevel / 2; when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo); when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo); - when(mWifiInfo.getRssi()).thenReturn(rssi); + when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level)); when(mWifiInfo.isCarrierMerged()).thenReturn(true); when(mWifiInfo.getSubscriptionId()).thenReturn(1); setConnectivityViaCallbackInWifiTrackerForVcn( NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo); } + private int calculateRssiForLevel(int level) { + float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1); + int rssi = (int) (MIN_RSSI + level * amountPerLevel); + // Put RSSI in the middle of the range. + rssi += amountPerLevel / 2; + return rssi; + } + protected void setWifiStateForVcn(boolean connected, String ssid) { when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo); when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt index b6b28c9e4527..4a3080050a37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone import android.app.PendingIntent import android.content.Intent import android.os.RemoteException +import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor @@ -102,6 +103,7 @@ class ActivityStarterImplTest : SysuiTestCase() { activityIntentHelper, mainExecutor, ) + whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER) } @Test @@ -150,11 +152,28 @@ class ActivityStarterImplTest : SysuiTestCase() { @Test fun postStartActivityDismissingKeyguard_intent_postsOnMain() { + whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) val intent = mock(Intent::class.java) underTest.postStartActivityDismissingKeyguard(intent, 0) assertThat(mainExecutor.numPending()).isEqualTo(1) + mainExecutor.runAllReady() + + verify(deviceProvisionedController).isDeviceProvisioned + verify(shadeController).runPostCollapseRunnables() + } + + @Test + fun postStartActivityDismissingKeyguard_intent_notDeviceProvisioned_doesNotProceed() { + whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false) + val intent = mock(Intent::class.java) + + underTest.postStartActivityDismissingKeyguard(intent, 0) + mainExecutor.runAllReady() + + verify(deviceProvisionedController).isDeviceProvisioned + verify(shadeController, never()).runPostCollapseRunnables() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index bde05b9f499e..5a887ebcee80 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository @@ -131,6 +132,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { context, IMMEDIATE, scope, + FakeAirplaneModeRepository(), wifiRepository, mock(), ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 7cc59b67414b..38c7432eb288 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -34,13 +34,16 @@ import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener import android.telephony.TelephonyManager +import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.telephony.PhoneConstants import com.android.settingslib.R import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository @@ -51,25 +54,25 @@ import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManag import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl -import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository -import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.UUID -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield -import org.junit.After +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Before @@ -83,6 +86,9 @@ import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +// This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper +// to run the callback and this makes the looper place nicely with TestScope etc. +@TestableLooper.RunWithLooper class MobileConnectionsRepositoryTest : SysuiTestCase() { private lateinit var underTest: MobileConnectionsRepositoryImpl @@ -90,7 +96,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory private lateinit var connectivityRepository: ConnectivityRepository - private lateinit var wifiRepository: FakeWifiRepository + private lateinit var airplaneModeRepository: FakeAirplaneModeRepository + private lateinit var wifiRepository: WifiRepository private lateinit var carrierConfigRepository: CarrierConfigRepository @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var subscriptionManager: SubscriptionManager @@ -102,7 +109,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private val mobileMappings = FakeMobileMappingsProxy() private val subscriptionManagerProxy = FakeSubscriptionManagerProxy() - private val scope = CoroutineScope(IMMEDIATE) + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) @Before fun setUp() { @@ -138,11 +146,23 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { context, mock(), mock(), - scope, + testScope.backgroundScope, mock(), ) - wifiRepository = FakeWifiRepository() + airplaneModeRepository = FakeAirplaneModeRepository() + + wifiRepository = + WifiRepositoryImpl( + fakeBroadcastDispatcher, + connectivityManager, + connectivityRepository, + mock(), + mock(), + FakeExecutor(FakeSystemClock()), + testScope.backgroundScope, + mock(), + ) carrierConfigRepository = CarrierConfigRepository( @@ -150,28 +170,28 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mock(), mock(), logger, - scope, + testScope.backgroundScope, ) connectionFactory = MobileConnectionRepositoryImpl.Factory( fakeBroadcastDispatcher, telephonyManager = telephonyManager, - bgDispatcher = IMMEDIATE, + bgDispatcher = dispatcher, logger = logger, mobileMappingsProxy = mobileMappings, - scope = scope, + scope = testScope.backgroundScope, carrierConfigRepository = carrierConfigRepository, ) carrierMergedFactory = CarrierMergedConnectionRepository.Factory( telephonyManager, - scope, + testScope.backgroundScope, wifiRepository, ) fullConnectionFactory = FullMobileConnectionRepository.Factory( - scope = scope, + scope = testScope.backgroundScope, logFactory = logBufferFactory, mobileRepoFactory = connectionFactory, carrierMergedRepoFactory = carrierMergedFactory, @@ -188,46 +208,38 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mobileMappings, fakeBroadcastDispatcher, context, - IMMEDIATE, - scope, + dispatcher, + testScope.backgroundScope, + airplaneModeRepository, wifiRepository, fullConnectionFactory, ) - } - @After - fun tearDown() { - scope.cancel() + testScope.runCurrent() } @Test fun testSubscriptions_initiallyEmpty() = - runBlocking(IMMEDIATE) { + testScope.runTest { assertThat(underTest.subscriptions.value).isEqualTo(listOf<SubscriptionModel>()) } @Test fun testSubscriptions_listUpdates() = - runBlocking(IMMEDIATE) { - var latest: List<SubscriptionModel>? = null - - val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) getSubscriptionCallback().onSubscriptionsChanged() assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2)) - - job.cancel() } @Test fun testSubscriptions_removingSub_updatesList() = - runBlocking(IMMEDIATE) { - var latest: List<SubscriptionModel>? = null - - val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) // WHEN 2 networks show up whenever(subscriptionManager.completeActiveSubscriptionInfoList) @@ -241,71 +253,55 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // THEN the subscriptions list represents the newest change assertThat(latest).isEqualTo(listOf(MODEL_2)) - - job.cancel() } @Test fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() = - runBlocking(IMMEDIATE) { - var latest: List<SubscriptionModel>? = null + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) - val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) - - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() assertThat(latest).isEqualTo(listOf(MODEL_CM)) - - job.cancel() } @Test fun testSubscriptions_carrierMergedAndOther_listHasBothWithCarrierMergedLast() = - runBlocking(IMMEDIATE) { - var latest: List<SubscriptionModel>? = null + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) - val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) - - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2, SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2, MODEL_CM)) - - job.cancel() } @Test fun testActiveDataSubscriptionId_initialValueIsNull() = - runBlocking(IMMEDIATE) { + testScope.runTest { assertThat(underTest.activeMobileDataSubscriptionId.value).isEqualTo(null) } @Test fun testActiveDataSubscriptionId_updates() = - runBlocking(IMMEDIATE) { - var active: Int? = null - - val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this) + testScope.runTest { + val active by collectLastValue(underTest.activeMobileDataSubscriptionId) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) assertThat(active).isEqualTo(SUB_2_ID) - - job.cancel() } @Test fun activeSubId_nullIfInvalidSubIdIsReceived() = - runBlocking(IMMEDIATE) { - var latest: Int? = null - - val job = underTest.activeMobileDataSubscriptionId.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.activeMobileDataSubscriptionId) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) @@ -316,8 +312,6 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID) assertThat(latest).isNull() - - job.cancel() } @Test @@ -327,23 +321,19 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun activeRepo_updatesWithActiveDataId() = - runBlocking(IMMEDIATE) { - var latest: MobileConnectionRepository? = null - val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.activeMobileDataRepository) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) assertThat(latest?.subId).isEqualTo(SUB_2_ID) - - job.cancel() } @Test fun activeRepo_nullIfActiveDataSubIdBecomesInvalid() = - runBlocking(IMMEDIATE) { - var latest: MobileConnectionRepository? = null - val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.activeMobileDataRepository) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) @@ -354,64 +344,49 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID) assertThat(latest).isNull() - - job.cancel() } @Test /** Regression test for b/268146648. */ fun activeSubIdIsSetBeforeSubscriptionsAreUpdated_doesNotThrow() = - runBlocking(IMMEDIATE) { - var activeRepo: MobileConnectionRepository? = null - var subscriptions: List<SubscriptionModel>? = null - - val activeRepoJob = - underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this) - val subscriptionsJob = - underTest.subscriptions.onEach { subscriptions = it }.launchIn(this) + testScope.runTest { + val activeRepo by collectLastValue(underTest.activeMobileDataRepository) + val subscriptions by collectLastValue(underTest.subscriptions) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) assertThat(subscriptions).isEmpty() assertThat(activeRepo).isNotNull() - - activeRepoJob.cancel() - subscriptionsJob.cancel() } @Test fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() = - runBlocking(IMMEDIATE) { - var latest: MobileConnectionRepository? = null - var subscriptions: List<SubscriptionModel>? = null - val activeSubIdJob = - underTest.activeMobileDataSubscriptionId - .filterNotNull() - .onEach { latest = underTest.getRepoForSubId(it) } - .launchIn(this) - val subscriptionsJob = - underTest.subscriptions.onEach { subscriptions = it }.launchIn(this) + testScope.runTest { + var latestActiveRepo: MobileConnectionRepository? = null + collectLastValue( + underTest.activeMobileDataSubscriptionId.filterNotNull().onEach { + latestActiveRepo = underTest.getRepoForSubId(it) + } + ) + + val latestSubscriptions by collectLastValue(underTest.subscriptions) // Active data subscription id is sent, but no subscription change has been posted yet getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) // Subscriptions list is empty - assertThat(subscriptions).isEmpty() + assertThat(latestSubscriptions).isEmpty() // getRepoForSubId does not throw - assertThat(latest).isNotNull() - - activeSubIdJob.cancel() - subscriptionsJob.cancel() + assertThat(latestActiveRepo).isNotNull() } @Test fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() = - runBlocking(IMMEDIATE) { - var activeRepo: MobileConnectionRepository? = null - val job = underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this) - val subscriptionsJob = underTest.subscriptions.launchIn(this) + testScope.runTest { + val activeRepo by collectLastValue(underTest.activeMobileDataRepository) + collectLastValue(underTest.subscriptions) // GIVEN active repo is updated before the subscription list updates getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() @@ -429,15 +404,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // THEN the newly request repo has been cached and reused assertThat(activeRepo).isSameInstanceAs(newRepo) - - job.cancel() - subscriptionsJob.cancel() } @Test fun testConnectionRepository_validSubId_isCached() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1)) @@ -447,16 +419,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { val repo2 = underTest.getRepoForSubId(SUB_1_ID) assertThat(repo1).isSameInstanceAs(repo2) - - job.cancel() } @Test fun testConnectionRepository_carrierMergedSubId_isCached() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() @@ -465,16 +436,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { val repo2 = underTest.getRepoForSubId(SUB_CM_ID) assertThat(repo1).isSameInstanceAs(repo2) - - job.cancel() } @Test fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() @@ -483,16 +453,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { val mobileRepo = underTest.getRepoForSubId(SUB_1_ID) assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue() assertThat(mobileRepo.getIsCarrierMerged()).isFalse() - - job.cancel() } @Test fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() @@ -503,26 +472,28 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(mobileRepo.getIsCarrierMerged()).isFalse() // WHEN the wifi network updates to be not carrier merged - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 4, level = 1)) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE) + runCurrent() // THEN the repos update val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID) mobileRepo = underTest.getRepoForSubId(SUB_1_ID) assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse() assertThat(mobileRepo.getIsCarrierMerged()).isFalse() - - job.cancel() } @Test fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) - wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() + runCurrent() val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID) var mobileRepo = underTest.getRepoForSubId(SUB_1_ID) @@ -530,21 +501,21 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(mobileRepo.getIsCarrierMerged()).isFalse() // WHEN the wifi network updates to be carrier merged - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + runCurrent() // THEN the repos update val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID) mobileRepo = underTest.getRepoForSubId(SUB_1_ID) assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue() assertThat(mobileRepo.getIsCarrierMerged()).isFalse() - - job.cancel() } @Test fun testConnectionCache_clearsInvalidSubscriptions() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) @@ -563,16 +534,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { getSubscriptionCallback().onSubscriptionsChanged() assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1) - - job.cancel() } @Test fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2, SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() @@ -591,15 +561,13 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { getSubscriptionCallback().onSubscriptionsChanged() assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1) - - job.cancel() } /** Regression test for b/261706421 */ @Test fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) @@ -617,26 +585,20 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { getSubscriptionCallback().onSubscriptionsChanged() assertThat(underTest.getSubIdRepoCache()).isEmpty() - - job.cancel() } @Test fun testConnectionRepository_invalidSubId_throws() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) - + testScope.runTest { assertThrows(IllegalArgumentException::class.java) { underTest.getRepoForSubId(SUB_1_ID) } - - job.cancel() } @Test fun connectionRepository_logBufferContainsSubIdInItsName() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) @@ -655,15 +617,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { eq(tableBufferLogName(SUB_2_ID)), anyInt(), ) - - job.cancel() } @Test fun testDefaultDataSubId_updatesOnBroadcast() = - runBlocking(IMMEDIATE) { - var latest: Int? = null - val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.defaultDataSubId) assertThat(latest).isEqualTo(INVALID_SUBSCRIPTION_ID) @@ -686,28 +645,24 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } assertThat(latest).isEqualTo(SUB_1_ID) - - job.cancel() } @Test fun defaultDataSubId_fetchesInitialValueOnStart() = - runBlocking(IMMEDIATE) { + testScope.runTest { subscriptionManagerProxy.defaultDataSubId = 2 - var latest: Int? = null - val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.defaultDataSubId) assertThat(latest).isEqualTo(2) - - job.cancel() } @Test fun defaultDataSubId_fetchesCurrentOnRestart() = - runBlocking(IMMEDIATE) { + testScope.runTest { subscriptionManagerProxy.defaultDataSubId = 2 var latest: Int? = null var job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this) + runCurrent() assertThat(latest).isEqualTo(2) @@ -720,6 +675,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { subscriptionManagerProxy.defaultDataSubId = 1 job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this) + runCurrent() assertThat(latest).isEqualTo(1) @@ -733,43 +689,37 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun mobileIsDefault_capsHaveCellular_isDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) } - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun mobileIsDefault_capsDoNotHaveCellular_isNotDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) } - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isFalse() - - job.cancel() } @Test fun mobileIsDefault_carrierMergedViaMobile_isDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } val caps = @@ -778,151 +728,144 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { whenever(it.transportInfo).thenReturn(carrierMergedInfo) } - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun mobileIsDefault_wifiDefault_mobileNotDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) } - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isFalse() - - job.cancel() } @Test fun mobileIsDefault_ethernetDefault_mobileNotDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true) } - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isFalse() - - job.cancel() } /** Regression test for b/272586234. */ @Test fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) whenever(it.transportInfo).thenReturn(carrierMergedInfo) } - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.hasCarrierMergedConnection) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(carrierMergedInfo) } - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.hasCarrierMergedConnection) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } /** Regression test for b/272586234. */ @Test fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) } - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.hasCarrierMergedConnection) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) } - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.hasCarrierMergedConnection) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() = - runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.hasCarrierMergedConnection) val underlyingNetwork = mock<Network>() val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val underlyingWifiCapabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) @@ -941,23 +884,23 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) // THEN there's a carrier merged connection assertThat(latest).isTrue() - - job.cancel() } @Test fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() = - runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.hasCarrierMergedConnection) val underlyingCarrierMergedNetwork = mock<Network>() val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val underlyingCapabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) @@ -977,22 +920,19 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) // THEN there's a carrier merged connection assertThat(latest).isTrue() - - job.cancel() } /** Regression test for b/272586234. */ @Test - fun hasCarrierMergedConnection_defaultNotCarrierMerged_butWifiRepoHasCarrierMerged_isTrue() = - runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + fun hasCarrierMergedConnection_defaultIsWifiNotCarrierMerged_wifiRepoIsCarrierMerged_isTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.hasCarrierMergedConnection) - // WHEN the default callback isn't carrier merged + // WHEN the default callback is TRANSPORT_WIFI but not carrier merged val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) } val caps = @@ -1001,16 +941,57 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { whenever(it.transportInfo).thenReturn(carrierMergedInfo) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - yield() // BUT the wifi repo has gotten updates that it *is* carrier merged - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) // THEN hasCarrierMergedConnection is true assertThat(latest).isTrue() + } - job.cancel() + /** Regression test for b/278618530. */ + @Test + fun hasCarrierMergedConnection_defaultIsCellular_wifiRepoIsCarrierMerged_isFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.hasCarrierMergedConnection) + + // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged + val caps = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + } + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + // BUT the wifi repo has gotten updates that it *is* carrier merged + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + + // THEN hasCarrierMergedConnection is **false** (The default network being CELLULAR + // takes precedence over the wifi network being carrier merged.) + assertThat(latest).isFalse() + } + + /** Regression test for b/278618530. */ + @Test + fun hasCarrierMergedConnection_defaultCellular_wifiIsCarrierMerged_airplaneMode_isTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.hasCarrierMergedConnection) + + // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged + val caps = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + } + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + // BUT the wifi repo has gotten updates that it *is* carrier merged + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + // AND we're in airplane mode + airplaneModeRepository.setIsAirplaneMode(true) + + // THEN hasCarrierMergedConnection is true. + assertThat(latest).isTrue() } @Test @@ -1020,43 +1001,37 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun defaultConnectionIsValidated_capsHaveValidated_isValidated() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true) } - var latest: Boolean? = null - val job = underTest.defaultConnectionIsValidated.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.defaultConnectionIsValidated) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun defaultConnectionIsValidated_capsHaveNotValidated_isNotValidated() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(false) } - var latest: Boolean? = null - val job = underTest.defaultConnectionIsValidated.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.defaultConnectionIsValidated) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isFalse() - - job.cancel() } @Test fun config_initiallyFromContext() = - runBlocking(IMMEDIATE) { + testScope.runTest { overrideResource(R.bool.config_showMin3G, true) val configFromContext = MobileMappings.Config.readConfig(context) assertThat(configFromContext.showAtLeast3G).isTrue() @@ -1074,26 +1049,24 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mobileMappings, fakeBroadcastDispatcher, context, - IMMEDIATE, - scope, + dispatcher, + testScope.backgroundScope, + airplaneModeRepository, wifiRepository, fullConnectionFactory, ) - var latest: MobileMappings.Config? = null - val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.defaultDataSubRatConfig) assertTrue(latest!!.areEqual(configFromContext)) assertTrue(latest!!.showAtLeast3G) - - job.cancel() } @Test fun config_subIdChangeEvent_updated() = - runBlocking(IMMEDIATE) { - var latest: MobileMappings.Config? = null - val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.defaultDataSubRatConfig) + assertThat(latest!!.showAtLeast3G).isFalse() overrideResource(R.bool.config_showMin3G, true) @@ -1112,15 +1085,13 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // THEN the config is updated assertTrue(latest!!.areEqual(configFromContext)) assertTrue(latest!!.showAtLeast3G) - - job.cancel() } @Test fun config_carrierConfigChangeEvent_updated() = - runBlocking(IMMEDIATE) { - var latest: MobileMappings.Config? = null - val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.defaultDataSubRatConfig) + assertThat(latest!!.showAtLeast3G).isFalse() overrideResource(R.bool.config_showMin3G, true) @@ -1138,15 +1109,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // THEN the config is updated assertThat(latest!!.areEqual(configFromContext)).isTrue() assertThat(latest!!.showAtLeast3G).isTrue() - - job.cancel() } @Test fun activeDataChange_inSameGroup_emitsUnit() = - runBlocking(IMMEDIATE) { - var latest: Unit? = null - val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.activeSubChangedInGroupEvent) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED) @@ -1154,15 +1122,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { .onActiveDataSubscriptionIdChanged(SUB_4_ID_GROUPED) assertThat(latest).isEqualTo(Unit) - - job.cancel() } @Test fun activeDataChange_notInSameGroup_doesNotEmit() = - runBlocking(IMMEDIATE) { - var latest: Unit? = null - val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.activeSubChangedInGroupEvent) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED) @@ -1170,38 +1135,46 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { .onActiveDataSubscriptionIdChanged(SUB_1_ID) assertThat(latest).isEqualTo(null) - - job.cancel() } - private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback { + private fun TestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback { + runCurrent() val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture()) return callbackCaptor.value!! } - private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener { + // Note: This is used to update the [WifiRepository]. + private fun TestScope.getNormalNetworkCallback(): ConnectivityManager.NetworkCallback { + runCurrent() + val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() + verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + + private fun TestScope.getSubscriptionCallback(): + SubscriptionManager.OnSubscriptionsChangedListener { + runCurrent() val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>() verify(subscriptionManager) .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture()) return callbackCaptor.value!! } - private fun getTelephonyCallbacks(): List<TelephonyCallback> { + private fun TestScope.getTelephonyCallbacks(): List<TelephonyCallback> { + runCurrent() val callbackCaptor = argumentCaptor<TelephonyCallback>() verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture()) return callbackCaptor.allValues } - private inline fun <reified T> getTelephonyCallbackForType(): T { - val cbs = getTelephonyCallbacks().filterIsInstance<T>() + private inline fun <reified T> TestScope.getTelephonyCallbackForType(): T { + val cbs = this.getTelephonyCallbacks().filterIsInstance<T>() assertThat(cbs.size).isEqualTo(1) return cbs[0] } companion object { - private val IMMEDIATE = Dispatchers.Main.immediate - // Subscription 1 private const val SUB_1_ID = 1 private val GROUP_1 = ParcelUuid(UUID.randomUUID()) @@ -1259,11 +1232,30 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private val SUB_CM = mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) } private val MODEL_CM = SubscriptionModel(subscriptionId = SUB_CM_ID) - private val WIFI_NETWORK_CM = - WifiNetworkModel.CarrierMerged( - networkId = 3, - subscriptionId = SUB_CM_ID, - level = 1, - ) + + private val WIFI_INFO_CM = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.subscriptionId).thenReturn(SUB_CM_ID) + } + private val WIFI_NETWORK_CAPS_CM = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(WIFI_INFO_CM) + whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true) + } + + private val WIFI_INFO_ACTIVE = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(false) + } + private val WIFI_NETWORK_CAPS_ACTIVE = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(WIFI_INFO_ACTIVE) + whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true) + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 1c219da09e27..1fb76b048d47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -272,6 +272,52 @@ class MobileIconsInteractorTest : SysuiTestCase() { } @Test + fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() = + testScope.runTest { + val (sub1, sub3) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + connectionsRepository.setSubscriptions(listOf(sub1, sub3)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + connectivityRepository.vcnSubId.value = SUB_3_ID + whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) + .thenReturn(false) + + var latest: List<SubscriptionModel>? = null + val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(listOf(sub3)) + + job.cancel() + } + + @Test + fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() = + testScope.runTest { + val (sub1, sub3) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + connectionsRepository.setSubscriptions(listOf(sub1, sub3)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + connectivityRepository.vcnSubId.value = SUB_1_ID + whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) + .thenReturn(false) + + var latest: List<SubscriptionModel>? = null + val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(listOf(sub1)) + + job.cancel() + } + + @Test fun activeDataConnection_turnedOn() = testScope.runTest { CONNECTION_1.setDataEnabled(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt index 661002d275b0..fa4e91b68a5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt @@ -24,6 +24,7 @@ import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -37,6 +38,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi import com.android.systemui.tuner.TunerService import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -655,6 +657,139 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { } @Test + fun vcnSubId_initiallyNull() { + assertThat(underTest.vcnSubId.value).isNull() + } + + @Test + fun vcnSubId_tracksVcnTransportInfo() = + testScope.runTest { + val vcnInfo = VcnTransportInfo(SUB_1_ID) + + var latest: Int? = null + val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) + + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(vcnInfo) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isEqualTo(SUB_1_ID) + job.cancel() + } + + @Test + fun vcnSubId_filersOutInvalid() = + testScope.runTest { + val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID) + + var latest: Int? = null + val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) + + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(vcnInfo) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isNull() + job.cancel() + } + + @Test + fun vcnSubId_nullIfNoTransportInfo() = + testScope.runTest { + var latest: Int? = null + val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) + + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isNull() + job.cancel() + } + + @Test + fun vcnSubId_nullIfVcnInfoIsNotCellular() = + testScope.runTest { + // If the underlying network of the VCN is a WiFi network, then there is no subId that + // could disagree with telephony's active data subscription id. + + var latest: Int? = null + val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) + + val wifiInfo = mock<WifiInfo>() + val vcnInfo = VcnTransportInfo(wifiInfo) + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(vcnInfo) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isNull() + job.cancel() + } + + @Test + fun vcnSubId_changingVcnInfoIsTracked() = + testScope.runTest { + var latest: Int? = null + val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) + + val wifiInfo = mock<WifiInfo>() + val wifiVcnInfo = VcnTransportInfo(wifiInfo) + val sub1VcnInfo = VcnTransportInfo(SUB_1_ID) + val sub2VcnInfo = VcnTransportInfo(SUB_2_ID) + + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(wifiVcnInfo) + } + + // WIFI VCN info + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isNull() + + // Cellular VCN info with subId 1 + whenever(capabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(true) + whenever(capabilities.transportInfo).thenReturn(sub1VcnInfo) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isEqualTo(SUB_1_ID) + + // Cellular VCN info with subId 2 + whenever(capabilities.transportInfo).thenReturn(sub2VcnInfo) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isEqualTo(SUB_2_ID) + + // No VCN anymore + whenever(capabilities.transportInfo).thenReturn(null) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isNull() + + job.cancel() + } + + @Test fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() { val wifiInfo = mock<WifiInfo>() val capabilities = @@ -964,6 +1099,9 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { private const val SLOT_WIFI = "wifi" private const val SLOT_MOBILE = "mobile" + private const val SUB_1_ID = 1 + private const val SUB_2_ID = 2 + const val NETWORK_ID = 45 val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt index 9e825b704851..8f28cc003d67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt @@ -30,6 +30,8 @@ class FakeConnectivityRepository : ConnectivityRepository { override val defaultConnections: StateFlow<DefaultConnectionModel> = MutableStateFlow(DefaultConnectionModel()) + override val vcnSubId: MutableStateFlow<Int?> = MutableStateFlow(null) + fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) { _forceHiddenIcons.value = hiddenIcons } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index 2b1370544bfd..7402b4d64b16 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -14,6 +14,8 @@ package com.android.systemui.statusbar.policy; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -44,7 +46,11 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository; +import com.android.systemui.statusbar.policy.bluetooth.FakeBluetoothRepository; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -69,6 +75,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { private DumpManager mMockDumpManager; private BluetoothControllerImpl mBluetoothControllerImpl; private BluetoothAdapter mMockAdapter; + private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags(); private List<CachedBluetoothDevice> mDevices; @@ -89,17 +96,26 @@ public class BluetoothControllerImplTest extends SysuiTestCase { .thenReturn(mock(LocalBluetoothProfileManager.class)); mMockDumpManager = mock(DumpManager.class); - mBluetoothControllerImpl = new BluetoothControllerImpl(mContext, + BluetoothRepository bluetoothRepository = + new FakeBluetoothRepository(mMockBluetoothManager); + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true); + + mBluetoothControllerImpl = new BluetoothControllerImpl( + mContext, + mFakeFeatureFlags, mUserTracker, mMockDumpManager, mock(BluetoothLogger.class), + bluetoothRepository, mTestableLooper.getLooper(), mMockBluetoothManager, mMockAdapter); } @Test - public void testNoConnectionWithDevices() { + public void testNoConnectionWithDevices_repoFlagOff() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false); + CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); when(device.isConnected()).thenReturn(true); when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED); @@ -113,7 +129,39 @@ public class BluetoothControllerImplTest extends SysuiTestCase { } @Test - public void testOnServiceConnected_updatesConnectionState() { + public void testNoConnectionWithDevices_repoFlagOn() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true); + + CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); + when(device.isConnected()).thenReturn(true); + when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED); + + mDevices.add(device); + when(mMockLocalAdapter.getConnectionState()) + .thenReturn(BluetoothAdapter.STATE_DISCONNECTED); + + mBluetoothControllerImpl.onConnectionStateChanged(null, + BluetoothAdapter.STATE_DISCONNECTED); + + assertTrue(mBluetoothControllerImpl.isBluetoothConnected()); + } + + @Test + public void testOnServiceConnected_updatesConnectionState_repoFlagOff() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false); + + when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING); + + mBluetoothControllerImpl.onServiceConnected(); + + assertTrue(mBluetoothControllerImpl.isBluetoothConnecting()); + assertFalse(mBluetoothControllerImpl.isBluetoothConnected()); + } + + @Test + public void testOnServiceConnected_updatesConnectionState_repoFlagOn() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true); + when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING); mBluetoothControllerImpl.onServiceConnected(); @@ -123,6 +171,46 @@ public class BluetoothControllerImplTest extends SysuiTestCase { } @Test + public void getConnectedDevices_onlyReturnsConnected_repoFlagOff() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false); + + CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class); + when(device1Disconnected.isConnected()).thenReturn(false); + mDevices.add(device1Disconnected); + + CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class); + when(device2Connected.isConnected()).thenReturn(true); + mDevices.add(device2Connected); + + mBluetoothControllerImpl.onDeviceAdded(device1Disconnected); + mBluetoothControllerImpl.onDeviceAdded(device2Connected); + + assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1); + assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0)) + .isEqualTo(device2Connected); + } + + @Test + public void getConnectedDevices_onlyReturnsConnected_repoFlagOn() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true); + + CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class); + when(device1Disconnected.isConnected()).thenReturn(false); + mDevices.add(device1Disconnected); + + CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class); + when(device2Connected.isConnected()).thenReturn(true); + mDevices.add(device2Connected); + + mBluetoothControllerImpl.onDeviceAdded(device1Disconnected); + mBluetoothControllerImpl.onDeviceAdded(device2Connected); + + assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1); + assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0)) + .isEqualTo(device2Connected); + } + + @Test public void testOnBluetoothStateChange_updatesBluetoothState() { mBluetoothControllerImpl.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF); @@ -147,8 +235,9 @@ public class BluetoothControllerImplTest extends SysuiTestCase { } @Test - public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection() - throws Exception { + public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOff() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false); + BluetoothController.Callback callback = mock(BluetoothController.Callback.class); mBluetoothControllerImpl.addCallback(callback); @@ -168,6 +257,29 @@ public class BluetoothControllerImplTest extends SysuiTestCase { } @Test + public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOn() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true); + + BluetoothController.Callback callback = mock(BluetoothController.Callback.class); + mBluetoothControllerImpl.addCallback(callback); + + assertFalse(mBluetoothControllerImpl.isBluetoothConnected()); + CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); + mDevices.add(device); + when(device.isConnected()).thenReturn(true); + when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED); + reset(callback); + mBluetoothControllerImpl.onAclConnectionStateChanged(device, + BluetoothProfile.STATE_CONNECTED); + + mTestableLooper.processAllMessages(); + + assertTrue(mBluetoothControllerImpl.isBluetoothConnected()); + verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean()); + } + + + @Test public void testOnActiveDeviceChanged_updatesAudioActive() { assertFalse(mBluetoothControllerImpl.isBluetoothAudioActive()); assertFalse(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt new file mode 100644 index 000000000000..6f40f15ca00a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.policy.bluetooth + +import android.bluetooth.BluetoothProfile +import androidx.test.filters.SmallTest +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LocalBluetoothAdapter +import com.android.settingslib.bluetooth.LocalBluetoothManager +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class BluetoothRepositoryImplTest : SysuiTestCase() { + + private lateinit var underTest: BluetoothRepositoryImpl + + private lateinit var scheduler: TestCoroutineScheduler + private lateinit var dispatcher: TestDispatcher + private lateinit var testScope: TestScope + + @Mock private lateinit var localBluetoothManager: LocalBluetoothManager + @Mock private lateinit var bluetoothAdapter: LocalBluetoothAdapter + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter) + + scheduler = TestCoroutineScheduler() + dispatcher = StandardTestDispatcher(scheduler) + testScope = TestScope(dispatcher) + + underTest = + BluetoothRepositoryImpl(testScope.backgroundScope, dispatcher, localBluetoothManager) + } + + @Test + fun fetchConnectionStatusInBackground_currentDevicesEmpty_maxStateIsManagerState() { + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + + val status = fetchConnectionStatus(currentDevices = emptyList()) + + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING) + } + + @Test + fun fetchConnectionStatusInBackground_currentDevicesEmpty_nullManager_maxStateIsDisconnected() { + // This CONNECTING state should be unused because localBluetoothManager is null + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + underTest = + BluetoothRepositoryImpl( + testScope.backgroundScope, + dispatcher, + localBluetoothManager = null, + ) + + val status = fetchConnectionStatus(currentDevices = emptyList()) + + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED) + } + + @Test + fun fetchConnectionStatusInBackground_managerStateLargerThanDeviceStates_maxStateIsManager() { + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + val device1 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) + } + val device2 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) + } + + val status = fetchConnectionStatus(currentDevices = listOf(device1, device2)) + + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING) + } + + @Test + fun fetchConnectionStatusInBackground_oneCurrentDevice_maxStateIsDeviceState() { + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) + val device = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + } + + val status = fetchConnectionStatus(currentDevices = listOf(device)) + + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING) + } + + @Test + fun fetchConnectionStatusInBackground_multipleDevices_maxStateIsHighestState() { + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) + + val device1 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + whenever(it.isConnected).thenReturn(false) + } + val device2 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED) + whenever(it.isConnected).thenReturn(true) + } + + val status = fetchConnectionStatus(currentDevices = listOf(device1, device2)) + + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTED) + } + + @Test + fun fetchConnectionStatusInBackground_devicesNotConnected_maxStateIsDisconnected() { + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + + // WHEN the devices say their state is CONNECTED but [isConnected] is false + val device1 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED) + whenever(it.isConnected).thenReturn(false) + } + val device2 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED) + whenever(it.isConnected).thenReturn(false) + } + + val status = fetchConnectionStatus(currentDevices = listOf(device1, device2)) + + // THEN the max state is DISCONNECTED + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED) + } + + @Test + fun fetchConnectionStatusInBackground_currentDevicesEmpty_connectedDevicesEmpty() { + val status = fetchConnectionStatus(currentDevices = emptyList()) + + assertThat(status.connectedDevices).isEmpty() + } + + @Test + fun fetchConnectionStatusInBackground_oneCurrentDeviceDisconnected_connectedDevicesEmpty() { + val device = + mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) } + + val status = fetchConnectionStatus(currentDevices = listOf(device)) + + assertThat(status.connectedDevices).isEmpty() + } + + @Test + fun fetchConnectionStatusInBackground_oneCurrentDeviceConnected_connectedDevicesHasDevice() { + val device = + mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) } + + val status = fetchConnectionStatus(currentDevices = listOf(device)) + + assertThat(status.connectedDevices).isEqualTo(listOf(device)) + } + + @Test + fun fetchConnectionStatusInBackground_multipleDevices_connectedDevicesHasOnlyConnected() { + val device1Connected = + mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) } + val device2Disconnected = + mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) } + val device3Connected = + mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) } + + val status = + fetchConnectionStatus( + currentDevices = listOf(device1Connected, device2Disconnected, device3Connected) + ) + + assertThat(status.connectedDevices).isEqualTo(listOf(device1Connected, device3Connected)) + } + + private fun fetchConnectionStatus( + currentDevices: Collection<CachedBluetoothDevice> + ): ConnectionStatusModel { + var receivedStatus: ConnectionStatusModel? = null + underTest.fetchConnectionStatusInBackground(currentDevices) { status -> + receivedStatus = status + } + scheduler.runCurrent() + return receivedStatus!! + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt new file mode 100644 index 000000000000..d8c0f777d4cc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.policy.bluetooth + +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LocalBluetoothManager +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestScope + +/** + * Fake [BluetoothRepository] that delegates to the real [BluetoothRepositoryImpl]. + * + * We only need this because [BluetoothRepository] is called from Java, which can't use [TestScope], + * [StandardTestDispatcher], etc. to create a test version of the repo. This class uses those test + * items under-the-hood so Java classes can indirectly access them. + */ +@OptIn(ExperimentalCoroutinesApi::class) +class FakeBluetoothRepository(localBluetoothManager: LocalBluetoothManager) : BluetoothRepository { + + private val scheduler = TestCoroutineScheduler() + private val dispatcher = StandardTestDispatcher(scheduler) + private val testScope = TestScope(dispatcher) + + private val impl = + BluetoothRepositoryImpl(testScope.backgroundScope, dispatcher, localBluetoothManager) + + override fun fetchConnectionStatusInBackground( + currentDevices: Collection<CachedBluetoothDevice>, + callback: ConnectionStatusFetchedCallback + ) { + impl.fetchConnectionStatusInBackground(currentDevices, callback) + scheduler.runCurrent() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 17b5e0546eca..09ac0e312e21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.theme; +import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; + import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; @@ -29,6 +31,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -926,4 +929,38 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(), any()); } + + @Test + public void createDynamicOverlay_addsAllDynamicColors() { + // Trigger new wallpaper colors to generate an overlay + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, + USER_SYSTEM); + ArgumentCaptor<FabricatedOverlay[]> themeOverlays = + ArgumentCaptor.forClass(FabricatedOverlay[].class); + + verify(mThemeOverlayApplier) + .applyCurrentUserOverlays(any(), themeOverlays.capture(), anyInt(), any()); + + FabricatedOverlay[] overlays = themeOverlays.getValue(); + FabricatedOverlay accents = overlays[0]; + FabricatedOverlay neutrals = overlays[1]; + FabricatedOverlay dynamic = overlays[2]; + + final int colorsPerPalette = 12; + + // Color resources were added for all 3 accent palettes + verify(accents, times(colorsPerPalette * 3)) + .setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null)); + // Color resources were added for all 2 neutral palettes + verify(neutrals, times(colorsPerPalette * 2)) + .setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null)); + // All dynamic colors were added twice: light and dark them + // All fixed colors were added once + verify(dynamic, times( + DynamicColors.ALL_DYNAMIC_COLORS_MAPPED.size() * 2 + + DynamicColors.FIXED_COLORS_MAPPED.size()) + ).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index e06b43ac5aea..45a37cffa588 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -17,22 +17,28 @@ package com.android.systemui.volume; import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; +import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.KeyguardManager; +import android.content.res.Configuration; import android.media.AudioManager; import android.os.SystemClock; import android.provider.DeviceConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.Gravity; import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; @@ -53,7 +59,9 @@ import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.FakeConfigurationController; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -82,6 +90,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { View mDrawerNormal; private DeviceConfigProxyFake mDeviceConfigProxy; private FakeExecutor mExecutor; + private TestableLooper mTestableLooper; + private ConfigurationController mConfigurationController; + private int mOriginalOrientation; @Mock VolumeDialogController mVolumeDialogController; @@ -92,8 +103,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Mock DeviceProvisionedController mDeviceProvisionedController; @Mock - ConfigurationController mConfigurationController; - @Mock MediaOutputDialogFactory mMediaOutputDialogFactory; @Mock VolumePanelFactory mVolumePanelFactory; @@ -104,6 +113,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock CsdWarningDialog mCsdWarningDialog; + @Mock + DevicePostureController mPostureController; private final CsdWarningDialog.Factory mCsdWarningDialogFactory = new CsdWarningDialog.Factory() { @@ -119,9 +130,17 @@ public class VolumeDialogImplTest extends SysuiTestCase { getContext().addMockSystemService(KeyguardManager.class, mKeyguard); + mTestableLooper = TestableLooper.get(this); mDeviceConfigProxy = new DeviceConfigProxyFake(); mExecutor = new FakeExecutor(new FakeSystemClock()); + when(mPostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + mOriginalOrientation = mContext.getResources().getConfiguration().orientation; + + mConfigurationController = new FakeConfigurationController(); + mDialog = new VolumeDialogImpl( getContext(), mVolumeDialogController, @@ -135,8 +154,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDeviceConfigProxy, mExecutor, mCsdWarningDialogFactory, - mDumpManager - ); + mPostureController, + mTestableLooper.getLooper(), + mDumpManager); mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); @@ -227,6 +247,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class); verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any()); VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue(); + callbacks.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI); verify(mAccessibilityMgr).getRecommendedTimeoutMillis( VolumeDialogImpl.DIALOG_SAFETYWARNING_TIMEOUT_MILLIS, @@ -371,11 +392,171 @@ public class VolumeDialogImplTest extends SysuiTestCase { verify(mCsdWarningDialog).show(); } + @Test + public void ifPortraitHalfOpen_drawVerticallyTop() { + DevicePostureController devicePostureController = mock(DevicePostureController.class); + when(devicePostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + devicePostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0 , null); + + verify(devicePostureController).addCallback(any()); + dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED); + mTestableLooper.processAllMessages(); // let dismiss() finish + + setOrientation(Configuration.ORIENTATION_PORTRAIT); + + // Call show() to trigger layout updates before verifying position + dialog.show(SHOW_REASON_UNKNOWN); + mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect + + int gravity = dialog.getWindowGravity(); + assertEquals(Gravity.TOP, gravity & Gravity.VERTICAL_GRAVITY_MASK); + } + + @Test + public void ifPortraitAndOpen_drawCenterVertically() { + DevicePostureController devicePostureController = mock(DevicePostureController.class); + when(devicePostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + devicePostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0, null); + + verify(devicePostureController).addCallback(any()); + dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED); + mTestableLooper.processAllMessages(); // let dismiss() finish + + setOrientation(Configuration.ORIENTATION_PORTRAIT); + + dialog.show(SHOW_REASON_UNKNOWN); + mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect + + int gravity = dialog.getWindowGravity(); + assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK); + } + + @Test + public void ifLandscapeAndHalfOpen_drawCenterVertically() { + DevicePostureController devicePostureController = mock(DevicePostureController.class); + when(devicePostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + devicePostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0, null); + + verify(devicePostureController).addCallback(any()); + dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED); + mTestableLooper.processAllMessages(); // let dismiss() finish + + setOrientation(Configuration.ORIENTATION_LANDSCAPE); + + dialog.show(SHOW_REASON_UNKNOWN); + mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect + + int gravity = dialog.getWindowGravity(); + assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK); + } + + @Test + public void dialogInit_addsPostureControllerCallback() { + // init is already called in setup + verify(mPostureController).addCallback(any()); + } + + @Test + public void dialogDestroy_removesPostureControllerCallback() { + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + mPostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0, null); + + verify(mPostureController, never()).removeCallback(any()); + + dialog.destroy(); + + verify(mPostureController).removeCallback(any()); + } + + private void setOrientation(int orientation) { + Configuration config = new Configuration(); + config.orientation = orientation; + if (mConfigurationController != null) { + mConfigurationController.onConfigurationChanged(config); + } + } + @After public void teardown() { if (mDialog != null) { mDialog.clearInternalHandlerAfterTest(); } + setOrientation(mOriginalOrientation); + mTestableLooper.processAllMessages(); + reset(mPostureController); } /* diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 1ec4e8c48707..8bbd58dc8fe1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -102,7 +102,8 @@ public abstract class SysuiTestCase { mock(Executor.class), mock(DumpManager.class), mock(BroadcastDispatcherLogger.class), - mock(UserTracker.class)); + mock(UserTracker.class), + shouldFailOnLeakedReceiver()); mRealInstrumentation = InstrumentationRegistry.getInstrumentation(); Instrumentation inst = spy(mRealInstrumentation); @@ -141,6 +142,10 @@ public abstract class SysuiTestCase { mDependency.injectTestDependency(DialogLaunchAnimator.class, fakeDialogLaunchAnimator()); } + protected boolean shouldFailOnLeakedReceiver() { + return false; + } + @After public void SysuiTeardown() { if (mRealInstrumentation != null) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt index d9012a527726..2362a5241244 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt @@ -39,20 +39,21 @@ class FakeFingerprintPropertyRepository : FingerprintPropertyRepository { MutableStateFlow(FingerprintSensorType.UNKNOWN) override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow() - private val _sensorLocation: MutableStateFlow<SensorLocationInternal> = - MutableStateFlow(SensorLocationInternal.DEFAULT) - override val sensorLocation = _sensorLocation.asStateFlow() + private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> = + MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT)) + override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> = + _sensorLocations.asStateFlow() fun setProperties( sensorId: Int, strength: SensorStrength, sensorType: FingerprintSensorType, - sensorLocation: SensorLocationInternal + sensorLocations: Map<String, SensorLocationInternal> ) { _sensorId.value = sensorId _strength.value = strength _sensorType.value = sensorType - _sensorLocation.value = sensorLocation + _sensorLocations.value = sensorLocations _isInitialized.value = true } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt index ad086ff9c664..af940e4fa687 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt @@ -27,6 +27,7 @@ import com.android.systemui.SysuiTestableContext import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.dump.DumpManager import com.android.systemui.settings.UserTracker +import java.lang.IllegalStateException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executor @@ -37,7 +38,8 @@ class FakeBroadcastDispatcher( broadcastRunningExecutor: Executor, dumpManager: DumpManager, logger: BroadcastDispatcherLogger, - userTracker: UserTracker + userTracker: UserTracker, + private val shouldFailOnLeakedReceiver: Boolean ) : BroadcastDispatcher( context, @@ -85,6 +87,9 @@ class FakeBroadcastDispatcher( fun cleanUpReceivers(testName: String) { registeredReceivers.forEach { Log.i(testName, "Receiver not unregistered from dispatcher: $it") + if (shouldFailOnLeakedReceiver) { + throw IllegalStateException("Receiver not unregistered from dispatcher: $it") + } } registeredReceivers.clear() } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index fbc7b3cbc63b..874fb0164b01 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -1072,6 +1072,10 @@ public class FullScreenMagnificationController implements } } + protected float getLastActivatedScale(int displayId) { + return getScale(displayId); + } + /** * Returns the X offset of the magnification viewport. If an animation * is in progress, this reflects the end state of the animation. diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 7ee72dfa30fd..fee20c89c106 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -470,12 +470,14 @@ public class MagnificationController implements WindowMagnificationManager.Callb disableFullScreenMagnificationIfNeeded(displayId); } else { long duration; + float scale; synchronized (mLock) { setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_NONE); duration = SystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId); + scale = mWindowMagnificationMgr.getLastActivatedScale(displayId); } - logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration); + logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration, scale); } updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); } @@ -567,13 +569,16 @@ public class MagnificationController implements WindowMagnificationManager.Callb disableWindowMagnificationIfNeeded(displayId); } else { long duration; + float scale; synchronized (mLock) { setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_NONE); duration = SystemClock.uptimeMillis() - mFullScreenModeEnabledTimeArray.get(displayId); + scale = mFullScreenMagnificationController.getLastActivatedScale(displayId); } - logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, duration); + logMagnificationUsageState( + ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, duration, scale); } updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); } @@ -612,10 +617,11 @@ public class MagnificationController implements WindowMagnificationManager.Callb * * @param mode The activated magnification mode. * @param duration The duration in milliseconds during the magnification is activated. + * @param scale The last magnification scale for the activation */ @VisibleForTesting - public void logMagnificationUsageState(int mode, long duration) { - AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration); + public void logMagnificationUsageState(int mode, long duration, float scale) { + AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration, scale); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index ce18b2cf9bad..d07db3f7578d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -142,6 +142,8 @@ public class WindowMagnificationManager implements private boolean mMagnificationFollowTypingEnabled = true; @GuardedBy("mLock") private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray(); + @GuardedBy("mLock") + private final SparseArray<Float> mLastActivatedScale = new SparseArray<>(); private boolean mReceiverRegistered = false; @VisibleForTesting @@ -528,6 +530,7 @@ public class WindowMagnificationManager implements return; } magnifier.setScale(scale); + mLastActivatedScale.put(displayId, scale); } } @@ -615,6 +618,9 @@ public class WindowMagnificationManager implements previousEnabled = magnifier.mEnabled; enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY, animationCallback, windowPosition, id); + if (enabled) { + mLastActivatedScale.put(displayId, getScale(displayId)); + } } if (enabled) { @@ -752,6 +758,15 @@ public class WindowMagnificationManager implements } } + protected float getLastActivatedScale(int displayId) { + synchronized (mLock) { + if (!mLastActivatedScale.contains(displayId)) { + return -1.0f; + } + return mLastActivatedScale.get(displayId); + } + } + /** * Moves window magnification on the specified display with the specified offset. * diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java index 05f2eea621cf..8570515f241d 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java @@ -31,10 +31,10 @@ public class CompanionDeviceConfig { public static final String ENABLE_CONTEXT_SYNC_TELECOM = "enable_context_sync_telecom"; /** - * Returns whether the given flag is currently enabled, with a default value of {@code true}. + * Returns whether the given flag is currently enabled, with a default value of {@code false}. */ public static boolean isEnabled(String flag) { - return DeviceConfig.getBoolean(NAMESPACE_COMPANION, flag, /* defaultValue= */ true); + return DeviceConfig.getBoolean(NAMESPACE_COMPANION, flag, /* defaultValue= */ false); } /** diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java index 17bdb6085c45..0f00f5f1c3a5 100644 --- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -19,10 +19,8 @@ package com.android.server.companion.transport; import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES; import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE; -import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PLATFORM_INFO; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.companion.AssociationInfo; @@ -33,7 +31,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Binder; import android.os.Build; -import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -46,7 +43,6 @@ import com.android.server.companion.AssociationStore; import java.io.FileDescriptor; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -58,19 +54,8 @@ public class CompanionTransportManager { private static final String TAG = "CDM_CompanionTransportManager"; private static final boolean DEBUG = false; - private static final int SECURE_CHANNEL_AVAILABLE_SDK = Build.VERSION_CODES.UPSIDE_DOWN_CAKE; - private static final int NON_ANDROID = -1; - private boolean mSecureTransportEnabled = true; - private static boolean isRequest(int message) { - return (message & 0xFF000000) == 0x63000000; - } - - private static boolean isResponse(int message) { - return (message & 0xFF000000) == 0x33000000; - } - private final Context mContext; private final AssociationStore mAssociationStore; @@ -84,10 +69,6 @@ public class CompanionTransportManager { @NonNull private final SparseArray<IOnMessageReceivedListener> mMessageListeners = new SparseArray<>(); - - @Nullable - private Transport mTempTransport; - public CompanionTransportManager(Context context, AssociationStore associationStore) { mContext = context; mAssociationStore = associationStore; @@ -199,7 +180,8 @@ public class CompanionTransportManager { detachSystemDataTransport(packageName, userId, associationId); } - initializeTransport(associationId, fd); + // TODO: Implement new API to pass a PSK + initializeTransport(associationId, fd, null); notifyOnTransportsChanged(); } @@ -237,107 +219,36 @@ public class CompanionTransportManager { }); } - private void initializeTransport(int associationId, ParcelFileDescriptor fd) { + private void initializeTransport(int associationId, + ParcelFileDescriptor fd, + byte[] preSharedKey) { Slog.i(TAG, "Initializing transport"); + Transport transport; if (!isSecureTransportEnabled()) { - Transport transport = new RawTransport(associationId, fd, mContext); - addMessageListenersToTransport(transport); - transport.start(); - synchronized (mTransports) { - mTransports.put(associationId, transport); - } - Slog.i(TAG, "RawTransport is created"); - return; - } - - // Exchange platform info to decide which transport should be created - mTempTransport = new RawTransport(associationId, fd, mContext); - addMessageListenersToTransport(mTempTransport); - IOnMessageReceivedListener listener = new IOnMessageReceivedListener() { - @Override - public void onMessageReceived(int associationId, byte[] data) throws RemoteException { - synchronized (mTransports) { - onPlatformInfoReceived(associationId, data); - } - } - - @Override - public IBinder asBinder() { - return null; - } - }; - mTempTransport.addListener(MESSAGE_REQUEST_PLATFORM_INFO, listener); - mTempTransport.start(); - - int sdk = Build.VERSION.SDK_INT; - String release = Build.VERSION.RELEASE; - // data format: | SDK_INT (int) | release length (int) | release | - final ByteBuffer data = ByteBuffer.allocate(4 + 4 + release.getBytes().length) - .putInt(sdk) - .putInt(release.getBytes().length) - .put(release.getBytes()); - - // TODO: it should check if preSharedKey is given - try { - mTempTransport.sendMessage(MESSAGE_REQUEST_PLATFORM_INFO, data.array()); - } catch (IOException e) { - Slog.e(TAG, "Failed to exchange platform info"); - } - } - - /** - * Depending on the remote platform info to decide which transport should be created - */ - private void onPlatformInfoReceived(int associationId, byte[] data) { - if (mTempTransport.getAssociationId() != associationId) { - return; - } - // TODO: it should check if preSharedKey is given - - ByteBuffer buffer = ByteBuffer.wrap(data); - int remoteSdk = buffer.getInt(); - byte[] remoteRelease = new byte[buffer.getInt()]; - buffer.get(remoteRelease); - - Slog.i(TAG, "Remote device SDK: " + remoteSdk + ", release:" + new String(remoteRelease)); - - Transport transport = mTempTransport; - mTempTransport.stop(); - - int sdk = Build.VERSION.SDK_INT; - String release = Build.VERSION.RELEASE; - - if (sdk < SECURE_CHANNEL_AVAILABLE_SDK || remoteSdk < SECURE_CHANNEL_AVAILABLE_SDK) { - // If either device is Android T or below, use raw channel - // TODO: depending on the release version, either - // 1) using a RawTransport for old T versions - // 2) or an Ukey2 handshaked transport for UKey2 backported T versions - Slog.d(TAG, "Secure channel is not supported. Using raw transport"); - transport = new RawTransport(transport.getAssociationId(), transport.getFd(), mContext); + // If secure transport is explicitly disabled for testing, use raw transport + Slog.i(TAG, "Secure channel is disabled. Creating raw transport"); + transport = new RawTransport(associationId, fd, mContext); } else if (Build.isDebuggable()) { // If device is debug build, use hardcoded test key for authentication Slog.d(TAG, "Creating an unauthenticated secure channel"); final byte[] testKey = "CDM".getBytes(StandardCharsets.UTF_8); - transport = new SecureTransport(transport.getAssociationId(), transport.getFd(), - mContext, testKey, null); - } else if (sdk == NON_ANDROID || remoteSdk == NON_ANDROID) { + transport = new SecureTransport(associationId, fd, mContext, testKey, null); + } else if (preSharedKey != null) { // If either device is not Android, then use app-specific pre-shared key - // TODO: pass in a real preSharedKey Slog.d(TAG, "Creating a PSK-authenticated secure channel"); - transport = new SecureTransport(transport.getAssociationId(), transport.getFd(), - mContext, new byte[0], null); + transport = new SecureTransport(associationId, fd, mContext, preSharedKey, null); } else { // If none of the above applies, then use secure channel with attestation verification Slog.d(TAG, "Creating a secure channel"); - transport = new SecureTransport(transport.getAssociationId(), transport.getFd(), - mContext); + transport = new SecureTransport(associationId, fd, mContext); } + addMessageListenersToTransport(transport); transport.start(); synchronized (mTransports) { - mTransports.put(transport.getAssociationId(), transport); + mTransports.put(associationId, transport); } - // Doesn't need to notifyTransportsChanged here, it'll be done in attachSystemDataTransport + } public Future<?> requestPermissionRestore(int associationId, byte[] data) { diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java index 41589018b149..e64509facbb4 100644 --- a/services/companion/java/com/android/server/companion/transport/RawTransport.java +++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java @@ -35,7 +35,7 @@ class RawTransport extends Transport { } @Override - public void start() { + void start() { if (DEBUG) { Slog.d(TAG, "Starting raw transport."); } @@ -54,7 +54,7 @@ class RawTransport extends Transport { } @Override - public void stop() { + void stop() { if (DEBUG) { Slog.d(TAG, "Stopping raw transport."); } @@ -62,7 +62,7 @@ class RawTransport extends Transport { } @Override - public void close() { + void close() { stop(); if (DEBUG) { diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java index 4054fc95f04a..949f39ae1609 100644 --- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java +++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java @@ -51,18 +51,18 @@ class SecureTransport extends Transport implements SecureChannel.Callback { } @Override - public void start() { + void start() { mSecureChannel.start(); } @Override - public void stop() { + void stop() { mSecureChannel.stop(); mShouldProcessRequests = false; } @Override - public void close() { + void close() { mSecureChannel.close(); mShouldProcessRequests = false; } diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java index d30104a095cf..6ad6d3a4aa72 100644 --- a/services/companion/java/com/android/server/companion/transport/Transport.java +++ b/services/companion/java/com/android/server/companion/transport/Transport.java @@ -47,7 +47,6 @@ public abstract class Transport { protected static final boolean DEBUG = Build.IS_DEBUGGABLE; static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN - public static final int MESSAGE_REQUEST_PLATFORM_INFO = 0x63807073; // ?PFI public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES @@ -113,17 +112,17 @@ public abstract class Transport { /** * Start listening to messages. */ - public abstract void start(); + abstract void start(); /** * Soft stop listening to the incoming data without closing the streams. */ - public abstract void stop(); + abstract void stop(); /** * Stop listening to the incoming data and close the streams. */ - public abstract void close(); + abstract void close(); protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data) throws IOException; @@ -183,11 +182,6 @@ public abstract class Transport { sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data); break; } - case MESSAGE_REQUEST_PLATFORM_INFO: { - callback(message, data); - // DO NOT SEND A RESPONSE! - break; - } case MESSAGE_REQUEST_CONTEXT_SYNC: { callback(message, data); sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE); diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java index 99178920cc52..2812233815a6 100644 --- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java +++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java @@ -88,7 +88,7 @@ public class WallpaperUpdateReceiver extends BroadcastReceiver { } else { //live wallpaper ComponentName currCN = info.getComponent(); - ComponentName defaultCN = WallpaperManager.getDefaultWallpaperComponent(context); + ComponentName defaultCN = WallpaperManager.getCmfDefaultWallpaperComponent(context); if (!currCN.equals(defaultCN)) { return true; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8ce1829a4b16..9514572e3b01 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -53,6 +53,10 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_NONE; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BACKUP; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_INSTRUMENTATION; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM; import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT; import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES; import static android.content.pm.PackageManager.MATCH_ALL; @@ -158,10 +162,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.MemoryStatUtil.hasMemcg; import static com.android.server.am.ProcessList.ProcStartHandler; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_BACKUP; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_INSTRUMENTATION; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_PERSISTENT; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_SYSTEM; import static com.android.server.net.NetworkPolicyManagerInternal.updateBlockedReasonsWithProcState; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND; @@ -3761,6 +3761,15 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void forceStopPackage(final String packageName, int userId) { + forceStopPackage(packageName, userId, /*flags=*/ 0); + } + + @Override + public void forceStopPackageEvenWhenStopping(final String packageName, int userId) { + forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED); + } + + private void forceStopPackage(final String packageName, int userId, int userRunningFlags) { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) != PackageManager.PERMISSION_GRANTED) { String msg = "Permission Denial: forceStopPackage() from pid=" @@ -3776,7 +3785,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); - synchronized(this) { + synchronized (this) { int[] users = userId == UserHandle.USER_ALL ? mUserController.getUsers() : new int[] { userId }; for (int user : users) { @@ -3804,7 +3813,7 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.w(TAG, "Failed trying to unstop package " + packageName + ": " + e); } - if (mUserController.isUserRunning(user, 0)) { + if (mUserController.isUserRunning(user, userRunningFlags)) { forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid); finishForceStopPackageLocked(packageName, pkgUid); } @@ -7511,7 +7520,31 @@ public class ActivityManagerService extends IActivityManager.Stub "registerUidObserver"); } mUidObserverController.register(observer, which, cutpoint, callingPackage, - Binder.getCallingUid()); + Binder.getCallingUid(), /*uids*/null); + } + + /** + * Registers a UidObserver with a uid filter. + * + * @param observer The UidObserver implementation to register. + * @param which A bitmask of events to observe. See ActivityManager.UID_OBSERVER_*. + * @param cutpoint The cutpoint for onUidStateChanged events. When the state crosses this + * threshold in either direction, onUidStateChanged will be called. + * @param callingPackage The name of the calling package. + * @param uids A list of uids to watch. If all uids are to be watched, use + * registerUidObserver instead. + * @throws RemoteException + * @return Returns A binder token identifying the UidObserver registration. + */ + @Override + public IBinder registerUidObserverForUids(IUidObserver observer, int which, int cutpoint, + String callingPackage, int[] uids) { + if (!hasUsageStatsPermission(callingPackage)) { + enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, + "registerUidObserver"); + } + return mUidObserverController.register(observer, which, cutpoint, callingPackage, + Binder.getCallingUid(), uids); } @Override @@ -7519,6 +7552,40 @@ public class ActivityManagerService extends IActivityManager.Stub mUidObserverController.unregister(observer); } + /** + * Adds a uid to the list of uids that a UidObserver will receive updates about. + * + * @param observerToken The binder token identifying the UidObserver registration. + * @param callingPackage The name of the calling package. + * @param uid The uid to watch. + * @throws RemoteException + */ + @Override + public void addUidToObserver(IBinder observerToken, String callingPackage, int uid) { + if (!hasUsageStatsPermission(callingPackage)) { + enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, + "registerUidObserver"); + } + mUidObserverController.addUidToObserver(observerToken, uid); + } + + /** + * Removes a uid from the list of uids that a UidObserver will receive updates about. + * + * @param observerToken The binder token identifying the UidObserver registration. + * @param callingPackage The name of the calling package. + * @param uid The uid to stop watching. + * @throws RemoteException + */ + @Override + public void removeUidFromObserver(IBinder observerToken, String callingPackage, int uid) { + if (!hasUsageStatsPermission(callingPackage)) { + enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, + "registerUidObserver"); + } + mUidObserverController.removeUidFromObserver(observerToken, uid); + } + @Override public boolean isUidActive(int uid, String callingPackage) { if (!hasUsageStatsPermission(callingPackage)) { @@ -17629,7 +17696,9 @@ public class ActivityManagerService extends IActivityManager.Stub final ProcessRecord r = mPidsSelfLocked.valueAt(i); processMemoryStates.add(new ProcessMemoryState( r.uid, r.getPid(), r.processName, r.mState.getCurAdj(), - r.mServices.hasForegroundServices())); + r.mServices.hasForegroundServices(), + r.mProfile.getCurrentHostingComponentTypes(), + r.mProfile.getHistoricalHostingComponentTypes())); } } return processMemoryStates; @@ -18613,7 +18682,7 @@ public class ActivityManagerService extends IActivityManager.Stub int which, int cutpoint, @NonNull String callingPackage) { mNetworkPolicyUidObserver = observer; mUidObserverController.register(observer, which, cutpoint, callingPackage, - Binder.getCallingUid()); + Binder.getCallingUid(), /*uids*/null); } @Override @@ -18906,6 +18975,13 @@ public class ActivityManagerService extends IActivityManager.Stub pw.flush(); } + void waitForBroadcastDispatch(@NonNull PrintWriter pw, @NonNull Intent intent) { + enforceCallingPermission(permission.DUMP, "waitForBroadcastDispatch"); + for (BroadcastQueue queue : mBroadcastQueues) { + queue.waitForDispatched(intent, pw); + } + } + void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) { Objects.requireNonNull(broadcastAction); enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()"); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 17a0d62c27b3..8759e3f207c4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -368,6 +368,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runWaitForBroadcastBarrier(pw); case "wait-for-application-barrier": return runWaitForApplicationBarrier(pw); + case "wait-for-broadcast-dispatch": + return runWaitForBroadcastDispatch(pw); case "set-ignore-delivery-group-policy": return runSetIgnoreDeliveryGroupPolicy(pw); case "clear-ignore-delivery-group-policy": @@ -3472,6 +3474,18 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runWaitForBroadcastDispatch(PrintWriter pw) throws RemoteException { + pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw)); + final Intent intent; + try { + intent = makeIntent(UserHandle.USER_CURRENT); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + mInternal.waitForBroadcastDispatch(pw, intent); + return 0; + } + int runSetIgnoreDeliveryGroupPolicy(PrintWriter pw) throws RemoteException { final String broadcastAction = getNextArgRequired(); mInternal.setIgnoreDeliveryGroupPolicy(broadcastAction); diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 4d469639be6f..87214decfe2e 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -179,11 +179,39 @@ public class BroadcastConstants { * being "runnable" to give other processes a chance to run. */ public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS; - private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = "bcast_max_running_active_broadcasts"; + private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = + "bcast_max_running_active_broadcasts"; private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = ActivityManager.isLowRamDeviceStatic() ? 8 : 16; /** + * For {@link BroadcastQueueModernImpl}: Maximum number of active "blocking" broadcasts + * to dispatch to a "running" System process queue before we retire them back to + * being "runnable" to give other processes a chance to run. Here "blocking" refers to + * whether or not we are going to block on the finishReceiver() to be called before moving + * to the next broadcast. + */ + public int MAX_CORE_RUNNING_BLOCKING_BROADCASTS = DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS; + private static final String KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS = + "bcast_max_core_running_blocking_broadcasts"; + private static final int DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS = + ActivityManager.isLowRamDeviceStatic() ? 8 : 16; + + /** + * For {@link BroadcastQueueModernImpl}: Maximum number of active non-"blocking" broadcasts + * to dispatch to a "running" System process queue before we retire them back to + * being "runnable" to give other processes a chance to run. Here "blocking" refers to + * whether or not we are going to block on the finishReceiver() to be called before moving + * to the next broadcast. + */ + public int MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS = + DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS; + private static final String KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS = + "bcast_max_core_running_non_blocking_broadcasts"; + private static final int DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS = + ActivityManager.isLowRamDeviceStatic() ? 32 : 64; + + /** * For {@link BroadcastQueueModernImpl}: Maximum number of pending * broadcasts to hold for a process before we ignore any delays that policy * might have applied to that process. @@ -369,6 +397,12 @@ public class BroadcastConstants { DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES); MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS); + MAX_CORE_RUNNING_BLOCKING_BROADCASTS = getDeviceConfigInt( + KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS, + DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS); + MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS = getDeviceConfigInt( + KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS, + DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS); MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS, DEFAULT_MAX_PENDING_BROADCASTS); DELAY_NORMAL_MILLIS = getDeviceConfigLong(KEY_DELAY_NORMAL_MILLIS, @@ -418,6 +452,10 @@ public class BroadcastConstants { pw.print(KEY_MODERN_QUEUE_ENABLED, MODERN_QUEUE_ENABLED).println(); pw.print(KEY_MAX_RUNNING_PROCESS_QUEUES, MAX_RUNNING_PROCESS_QUEUES).println(); pw.print(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, MAX_RUNNING_ACTIVE_BROADCASTS).println(); + pw.print(KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS, + MAX_CORE_RUNNING_BLOCKING_BROADCASTS).println(); + pw.print(KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS, + MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS).println(); pw.print(KEY_MAX_PENDING_BROADCASTS, MAX_PENDING_BROADCASTS).println(); pw.print(KEY_DELAY_NORMAL_MILLIS, TimeUtils.formatDuration(DELAY_NORMAL_MILLIS)).println(); diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java index 2adcf2f48343..8aa3921d3f2f 100644 --- a/services/core/java/com/android/server/am/BroadcastDispatcher.java +++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java @@ -582,6 +582,38 @@ public class BroadcastDispatcher { } } + private static boolean isDispatchedInDeferrals(@NonNull ArrayList<Deferrals> list, + @NonNull Intent intent) { + for (int i = 0; i < list.size(); i++) { + if (!isDispatched(list.get(i).broadcasts, intent)) { + return false; + } + } + return true; + } + + private static boolean isDispatched(@NonNull ArrayList<BroadcastRecord> list, + @NonNull Intent intent) { + for (int i = 0; i < list.size(); i++) { + if (intent.filterEquals(list.get(i).intent)) { + return false; + } + } + return true; + } + + public boolean isDispatched(@NonNull Intent intent) { + synchronized (mLock) { + if ((mCurrentBroadcast != null) && intent.filterEquals(mCurrentBroadcast.intent)) { + return false; + } + return isDispatched(mOrderedBroadcasts, intent) + && isDispatched(mAlarmQueue, intent) + && isDispatchedInDeferrals(mDeferredBroadcasts, intent) + && isDispatchedInDeferrals(mAlarmDeferrals, intent); + } + } + private static int pendingInDeferralsList(ArrayList<Deferrals> list) { int pending = 0; final int numEntries = list.size(); diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 5c68e6759083..59aab4f56404 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -143,6 +143,12 @@ class BroadcastProcessQueue { private int mActiveCountSinceIdle; /** + * Count of {@link #mActive} broadcasts with assumed delivery that have been dispatched + * since this queue was last idle. + */ + private int mActiveAssumedDeliveryCountSinceIdle; + + /** * Flag indicating that the currently active broadcast is being dispatched * was scheduled via a cold start. */ @@ -182,7 +188,7 @@ class BroadcastProcessQueue { private int mCountInstrumented; private int mCountManifest; - private boolean mPrioritizeEarliest; + private int mCountPrioritizeEarliestRequests; private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE; private @Reason int mRunnableAtReason = REASON_EMPTY; @@ -499,6 +505,14 @@ class BroadcastProcessQueue { return mActiveCountSinceIdle; } + /** + * Count of {@link #mActive} broadcasts with assumed delivery that have been dispatched + * since this queue was last idle. + */ + public int getActiveAssumedDeliveryCountSinceIdle() { + return mActiveAssumedDeliveryCountSinceIdle; + } + public void setActiveViaColdStart(boolean activeViaColdStart) { mActiveViaColdStart = activeViaColdStart; } @@ -532,6 +546,8 @@ class BroadcastProcessQueue { mActive = (BroadcastRecord) next.arg1; mActiveIndex = next.argi1; mActiveCountSinceIdle++; + mActiveAssumedDeliveryCountSinceIdle += + (mActive.isAssumedDelivered(mActiveIndex) ? 1 : 0); mActiveViaColdStart = false; mActiveWasStopped = false; next.recycle(); @@ -545,6 +561,7 @@ class BroadcastProcessQueue { mActive = null; mActiveIndex = 0; mActiveCountSinceIdle = 0; + mActiveAssumedDeliveryCountSinceIdle = 0; mActiveViaColdStart = false; invalidateRunnableAt(); } @@ -748,7 +765,7 @@ class BroadcastProcessQueue { final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1; final int nextLPRecordIndex = nextLPArgs.argi1; final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1; - final boolean shouldConsiderLPQueue = (mPrioritizeEarliest + final boolean shouldConsiderLPQueue = (mCountPrioritizeEarliestRequests > 0 || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit); final boolean isLPQueueEligible = shouldConsiderLPQueue && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime @@ -761,10 +778,9 @@ class BroadcastProcessQueue { } /** - * When {@code prioritizeEarliest} is set to {@code true}, then earliest enqueued - * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts - * waiting. This is typically used in case there are callers waiting for "barrier" to be - * reached. + * Add a request to prioritize dispatching of broadcasts that have been enqueued the earliest, + * even if there are urgent broadcasts waiting to be dispatched. This is typically used in + * case there are callers waiting for "barrier" to be reached. * * @return if this operation may have changed internal state, indicating * that the caller is responsible for invoking @@ -772,12 +788,38 @@ class BroadcastProcessQueue { */ @CheckResult @VisibleForTesting - boolean setPrioritizeEarliest(boolean prioritizeEarliest) { - if (mPrioritizeEarliest != prioritizeEarliest) { - mPrioritizeEarliest = prioritizeEarliest; + boolean addPrioritizeEarliestRequest() { + if (mCountPrioritizeEarliestRequests == 0) { + mCountPrioritizeEarliestRequests++; invalidateRunnableAt(); return true; } else { + mCountPrioritizeEarliestRequests++; + return false; + } + } + + /** + * Remove a request to prioritize dispatching of broadcasts that have been enqueued the + * earliest, even if there are urgent broadcasts waiting to be dispatched. This is typically + * used in case there are callers waiting for "barrier" to be reached. + * + * <p> Once there are no more remaining requests, the dispatching order reverts back to normal. + * + * @return if this operation may have changed internal state, indicating + * that the caller is responsible for invoking + * {@link BroadcastQueueModernImpl#updateRunnableList} + */ + @CheckResult + boolean removePrioritizeEarliestRequest() { + mCountPrioritizeEarliestRequests--; + if (mCountPrioritizeEarliestRequests == 0) { + invalidateRunnableAt(); + return true; + } else if (mCountPrioritizeEarliestRequests < 0) { + mCountPrioritizeEarliestRequests = 0; + return false; + } else { return false; } } @@ -837,7 +879,7 @@ class BroadcastProcessQueue { } /** - * Quickly determine if this queue has broadcasts enqueued before the given + * Quickly determine if this queue has non-deferred broadcasts enqueued before the given * barrier timestamp that are still waiting to be delivered. */ public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) { @@ -859,6 +901,41 @@ class BroadcastProcessQueue { || isDeferredUntilActive(); } + /** + * Quickly determine if this queue has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + */ + public boolean isDispatched(@NonNull Intent intent) { + final boolean activeDispatched = (mActive == null) + || (!intent.filterEquals(mActive.intent)); + final boolean dispatched = isDispatchedInQueue(mPending, intent); + final boolean urgentDispatched = isDispatchedInQueue(mPendingUrgent, intent); + final boolean offloadDispatched = isDispatchedInQueue(mPendingOffload, intent); + + return (activeDispatched && dispatched && urgentDispatched && offloadDispatched) + || isDeferredUntilActive(); + } + + /** + * Quickly determine if the {@code queue} has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + */ + private boolean isDispatchedInQueue(@NonNull ArrayDeque<SomeArgs> queue, + @NonNull Intent intent) { + final Iterator<SomeArgs> it = queue.iterator(); + while (it.hasNext()) { + final SomeArgs args = it.next(); + if (args == null) { + return true; + } + final BroadcastRecord record = (BroadcastRecord) args.arg1; + if (intent.filterEquals(record.intent)) { + return false; + } + } + return true; + } + public boolean isRunnable() { if (mRunnableAtInvalidated) updateRunnableAt(); return mRunnableAt != Long.MAX_VALUE; @@ -1309,6 +1386,7 @@ class BroadcastProcessQueue { pw.print(" m:"); pw.print(mCountManifest); pw.print(" csi:"); pw.print(mActiveCountSinceIdle); + pw.print(" adcsi:"); pw.print(mActiveAssumedDeliveryCountSinceIdle); pw.print(" ccu:"); pw.print(mActiveCountConsecutiveUrgent); pw.print(" ccn:"); pw.print(mActiveCountConsecutiveNormal); pw.println(); diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 6d1344d79b6c..8e76e5b5cf48 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -192,7 +192,7 @@ public abstract class BroadcastQueue { public abstract boolean isIdleLocked(); /** - * Quickly determine if this queue has broadcasts enqueued before the given + * Quickly determine if this queue has non-deferred broadcasts enqueued before the given * barrier timestamp that are still waiting to be delivered. * * @see #waitForIdle @@ -202,6 +202,15 @@ public abstract class BroadcastQueue { public abstract boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime); /** + * Quickly determine if this queue has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + * + * @see #waitForDispatched(Intent, PrintWriter) + */ + @GuardedBy("mService") + public abstract boolean isDispatchedLocked(@NonNull Intent intent); + + /** * Wait until this queue becomes completely idle. * <p> * Any broadcasts waiting to be delivered at some point in the future will @@ -214,7 +223,7 @@ public abstract class BroadcastQueue { public abstract void waitForIdle(@NonNull PrintWriter pw); /** - * Wait until any currently waiting broadcasts have been dispatched. + * Wait until any currently waiting non-deferred broadcasts have been dispatched. * <p> * Any broadcasts waiting to be delivered at some point in the future will * be dispatched as quickly as possible. @@ -225,6 +234,15 @@ public abstract class BroadcastQueue { public abstract void waitForBarrier(@NonNull PrintWriter pw); /** + * Wait until all non-deferred broadcasts matching {@code intent}, as defined by + * {@link Intent#filterEquals(Intent)}, have been dispatched. + * <p> + * Any broadcasts waiting to be delivered at some point in the future will + * be dispatched as quickly as possible. + */ + public abstract void waitForDispatched(@NonNull Intent intent, @NonNull PrintWriter pw); + + /** * Delays delivering broadcasts to the specified package. * * <p> Note that this is only valid for modern queue. diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 4a69f90d9fc0..7f3ceb578891 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -1793,6 +1793,23 @@ public class BroadcastQueueImpl extends BroadcastQueue { return mDispatcher.isBeyondBarrier(barrierTime); } + public boolean isDispatchedLocked(Intent intent) { + if (isIdleLocked()) return true; + + for (int i = 0; i < mParallelBroadcasts.size(); i++) { + if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) { + return false; + } + } + + final BroadcastRecord pending = getPendingBroadcastLocked(); + if ((pending != null) && intent.filterEquals(pending.intent)) { + return false; + } + + return mDispatcher.isDispatched(intent); + } + public void waitForIdle(PrintWriter pw) { waitFor(() -> isIdleLocked(), pw, "idle"); } @@ -1802,6 +1819,10 @@ public class BroadcastQueueImpl extends BroadcastQueue { waitFor(() -> isBeyondBarrierLocked(barrierTime), pw, "barrier"); } + public void waitForDispatched(Intent intent, PrintWriter pw) { + waitFor(() -> isDispatchedLocked(intent), pw, "dispatch"); + } + private void waitFor(BooleanSupplier condition, PrintWriter pw, String conditionName) { long lastPrint = 0; while (true) { diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 96e152320282..10a7c12e206b 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -40,6 +40,7 @@ import static com.android.server.am.BroadcastRecord.getReceiverProcessName; import static com.android.server.am.BroadcastRecord.getReceiverUid; import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal; +import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UptimeMillisLong; @@ -446,43 +447,29 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (DEBUG_BROADCAST) logv("Promoting " + queue + " from runnable to running; process is " + queue.app); - - // Allocate this available permit and start running! - final int queueIndex = getRunningIndexOf(null); - mRunning[queueIndex] = queue; - avail--; - - // Remove ourselves from linked list of runnable things - mRunnableHead = removeFromRunnableList(mRunnableHead, queue); - - // Emit all trace events for this process into a consistent track - queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]"; - queue.runningOomAdjusted = queue.isPendingManifest() - || queue.isPendingOrdered() - || queue.isPendingResultTo(); - - // If already warm, we can make OOM adjust request immediately; - // otherwise we need to wait until process becomes warm + promoteToRunningLocked(queue); + final boolean completed; if (processWarm) { - notifyStartedRunning(queue); updateOomAdj |= queue.runningOomAdjusted; - } - - // If we're already warm, schedule next pending broadcast now; - // otherwise we'll wait for the cold start to circle back around - queue.makeActiveNextPending(); - if (processWarm) { - queue.traceProcessRunningBegin(); - scheduleReceiverWarmLocked(queue); + completed = scheduleReceiverWarmLocked(queue); } else { - queue.traceProcessStartingBegin(); - scheduleReceiverColdLocked(queue); + completed = scheduleReceiverColdLocked(queue); + } + // If we are done with delivering the broadcasts to the process, we can demote it + // from the "running" list. + if (completed) { + demoteFromRunningLocked(queue); } + // TODO: If delivering broadcasts to a process is finished, we don't have to hold + // a slot for it. + avail--; // Move to considering next runnable queue queue = nextQueue; } + // TODO: We need to update oomAdj early as this currently doesn't guarantee that the + // procState is updated correctly when the app is handling a broadcast. if (updateOomAdj) { mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER); } @@ -514,7 +501,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { queue.traceProcessEnd(); queue.traceProcessRunningBegin(); - scheduleReceiverWarmLocked(queue); + if (scheduleReceiverWarmLocked(queue)) { + demoteFromRunningLocked(queue); + } // We might be willing to kick off another cold start enqueueUpdateRunningList(); @@ -558,6 +547,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (queue.isActive()) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "onApplicationCleanupLocked"); + demoteFromRunningLocked(queue); } // Skip any pending registered receivers, since the old process @@ -695,8 +685,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * Schedule the currently active broadcast on the given queue when we know * the process is cold. This kicks off a cold start and will eventually call * through to {@link #scheduleReceiverWarmLocked} once it's ready. + * + * @return {@code true} if the broadcast delivery is finished and the process queue can + * be demoted from the running list. Otherwise {@code false}. */ - private void scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) { + @CheckResult + @GuardedBy("mService") + private boolean scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) { checkState(queue.isActive(), "isActive"); // Remember that active broadcast was scheduled via a cold start @@ -711,12 +706,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mRunningColdStart = null; finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, "BroadcastFilter for cold app"); - return; + return true; } - if (maybeSkipReceiver(queue, r, index)) { + final String skipReason = shouldSkipReceiver(queue, r, index); + if (skipReason != null) { mRunningColdStart = null; - return; + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, skipReason); + return true; } final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo; @@ -742,8 +739,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mRunningColdStart = null; finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "startProcessLocked failed"); - return; + return true; } + return false; } /** @@ -754,38 +752,46 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * results by calling through to {@link #finishReceiverLocked}, both in the * case where a broadcast is handled by a remote app, and the case where the * broadcast was finished locally without the remote app being involved. + * + * @return {@code true} if the broadcast delivery is finished and the process queue can + * be demoted from the running list. Otherwise {@code false}. */ + @CheckResult @GuardedBy("mService") - private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) { + private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) { checkState(queue.isActive(), "isActive"); - final BroadcastRecord r = queue.getActive(); - final int index = queue.getActiveIndex(); + final int cookie = traceBegin("scheduleReceiverWarmLocked"); + while (queue.isActive()) { + final BroadcastRecord r = queue.getActive(); + final int index = queue.getActiveIndex(); - if (r.terminalCount == 0) { - r.dispatchTime = SystemClock.uptimeMillis(); - r.dispatchRealTime = SystemClock.elapsedRealtime(); - r.dispatchClockTime = System.currentTimeMillis(); - } + if (r.terminalCount == 0) { + r.dispatchTime = SystemClock.uptimeMillis(); + r.dispatchRealTime = SystemClock.elapsedRealtime(); + r.dispatchClockTime = System.currentTimeMillis(); + } - if (maybeSkipReceiver(queue, r, index)) { - return; - } - dispatchReceivers(queue, r, index); - } + final String skipReason = shouldSkipReceiver(queue, r, index); + if (skipReason == null) { + final boolean isBlockingDispatch = dispatchReceivers(queue, r, index); + if (isBlockingDispatch) { + traceEnd(cookie); + return false; + } + } else { + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, skipReason); + } - /** - * Examine a receiver and possibly skip it. The method returns true if the receiver is - * skipped (and therefore no more work is required). - */ - private boolean maybeSkipReceiver(@NonNull BroadcastProcessQueue queue, - @NonNull BroadcastRecord r, int index) { - final String reason = shouldSkipReceiver(queue, r, index); - if (reason != null) { - finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, reason); - return true; + if (shouldRetire(queue)) { + break; + } + + // We're on a roll; move onto the next broadcast for this process + queue.makeActiveNextPending(); } - return false; + traceEnd(cookie); + return true; } /** @@ -826,24 +832,21 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } /** - * Return true if this receiver should be assumed to have been delivered. - */ - private boolean isAssumedDelivered(BroadcastRecord r, int index) { - return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered - && (r.resultTo == null); - } - - /** * A receiver is about to be dispatched. Start ANR timers, if necessary. + * + * @return {@code true} if this a blocking delivery. That is, we are going to block on the + * finishReceiver() to be called before moving to the next broadcast. Otherwise, + * {@code false}. */ - private void dispatchReceivers(@NonNull BroadcastProcessQueue queue, + @CheckResult + private boolean dispatchReceivers(@NonNull BroadcastProcessQueue queue, @NonNull BroadcastRecord r, int index) { final ProcessRecord app = queue.app; final Object receiver = r.receivers.get(index); // Skip ANR tracking early during boot, when requested, or when we // immediately assume delivery success - final boolean assumeDelivered = isAssumedDelivered(r, index); + final boolean assumeDelivered = r.isAssumedDelivered(index); if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) { queue.lastCpuDelayTime = queue.app.getCpuDelayTime(); @@ -898,6 +901,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (assumeDelivered) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered"); + return false; } } else { notifyScheduleReceiver(app, r, (ResolveInfo) receiver); @@ -908,17 +912,21 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.shareIdentity ? r.callingUid : Process.INVALID_UID, r.shareIdentity ? r.callerPackage : null); } + return true; } catch (RemoteException e) { final String msg = "Failed to schedule " + r + " to " + receiver + " via " + app + ": " + e; logw(msg); app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true); - finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app"); + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, + "remote app"); + return false; } } else { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "missing IApplicationThread"); + return false; } } @@ -989,6 +997,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT, "deliveryTimeoutHardLocked"); + demoteFromRunningLocked(queue); } @Override @@ -1015,7 +1024,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // To ensure that "beyond" high-water marks are updated in a monotonic // way, we finish this receiver before possibly skipping any remaining // aborted receivers - final boolean res = finishReceiverActiveLocked(queue, + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app"); // When the caller aborted an ordered broadcast, we mark all @@ -1027,30 +1036,52 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - return res; + if (shouldRetire(queue)) { + demoteFromRunningLocked(queue); + return true; + } + + // We're on a roll; move onto the next broadcast for this process + queue.makeActiveNextPending(); + if (scheduleReceiverWarmLocked(queue)) { + demoteFromRunningLocked(queue); + return true; + } + + return false; } /** - * Return true if there are more broadcasts in the queue and the queue is runnable. + * Return true if there are no more broadcasts in the queue or if the queue is not runnable. */ - private boolean shouldContinueScheduling(@NonNull BroadcastProcessQueue queue) { + private boolean shouldRetire(@NonNull BroadcastProcessQueue queue) { // If we've made reasonable progress, periodically retire ourselves to // avoid starvation of other processes and stack overflow when a // broadcast is immediately finished without waiting - final boolean shouldRetire = - (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS); + final boolean shouldRetire; + if (UserHandle.isCore(queue.uid)) { + final int nonBlockingDeliveryCount = queue.getActiveAssumedDeliveryCountSinceIdle(); + final int blockingDeliveryCount = (queue.getActiveCountSinceIdle() + - queue.getActiveAssumedDeliveryCountSinceIdle()); + shouldRetire = (blockingDeliveryCount + >= mConstants.MAX_CORE_RUNNING_BLOCKING_BROADCASTS) || (nonBlockingDeliveryCount + >= mConstants.MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS); + } else { + shouldRetire = + (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS); + } - return queue.isRunnable() && queue.isProcessWarm() && !shouldRetire; + return !queue.isRunnable() || !queue.isProcessWarm() || shouldRetire; } /** * Terminate all active broadcasts on the queue. */ - private boolean finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue, + private void finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue, @DeliveryState int deliveryState, @NonNull String reason) { if (!queue.isActive()) { logw("Ignoring finish; no active broadcast for " + queue); - return false; + return; } final int cookie = traceBegin("finishReceiver"); @@ -1077,27 +1108,63 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // Given that a receiver just finished, check if the "waitingFor" conditions are met. checkAndRemoveWaitingFor(); - final boolean res = shouldContinueScheduling(queue); - if (res) { - // We're on a roll; move onto the next broadcast for this process - queue.makeActiveNextPending(); - scheduleReceiverWarmLocked(queue); - } else { - // We've drained running broadcasts; maybe move back to runnable - queue.makeActiveIdle(); - queue.traceProcessEnd(); + traceEnd(cookie); + } - final int queueIndex = getRunningIndexOf(queue); - mRunning[queueIndex] = null; - updateRunnableList(queue); - enqueueUpdateRunningList(); + /** + * Promote a process to the "running" list. + */ + @GuardedBy("mService") + private void promoteToRunningLocked(@NonNull BroadcastProcessQueue queue) { + // Allocate this available permit and start running! + final int queueIndex = getRunningIndexOf(null); + mRunning[queueIndex] = queue; + + // Remove ourselves from linked list of runnable things + mRunnableHead = removeFromRunnableList(mRunnableHead, queue); + + // Emit all trace events for this process into a consistent track + queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]"; + queue.runningOomAdjusted = queue.isPendingManifest() + || queue.isPendingOrdered() + || queue.isPendingResultTo(); + + // If already warm, we can make OOM adjust request immediately; + // otherwise we need to wait until process becomes warm + final boolean processWarm = queue.isProcessWarm(); + if (processWarm) { + notifyStartedRunning(queue); + } - // Tell other OS components that app is not actively running, giving - // a chance to update OOM adjustment - notifyStoppedRunning(queue); + // If we're already warm, schedule next pending broadcast now; + // otherwise we'll wait for the cold start to circle back around + queue.makeActiveNextPending(); + if (processWarm) { + queue.traceProcessRunningBegin(); + } else { + queue.traceProcessStartingBegin(); } + } + + /** + * Demote a process from the "running" list. + */ + @GuardedBy("mService") + private void demoteFromRunningLocked(@NonNull BroadcastProcessQueue queue) { + final int cookie = traceBegin("demoteFromRunning"); + // We've drained running broadcasts; maybe move back to runnable + queue.makeActiveIdle(); + queue.traceProcessEnd(); + + final int queueIndex = getRunningIndexOf(queue); + mRunning[queueIndex] = null; + updateRunnableList(queue); + enqueueUpdateRunningList(); + + // Tell other OS components that app is not actively running, giving + // a chance to update OOM adjustment + notifyStoppedRunning(queue); traceEnd(cookie); - return res; } /** @@ -1376,6 +1443,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } @Override + public boolean isDispatchedLocked(@NonNull Intent intent) { + return isDispatchedLocked(intent, LOG_WRITER_INFO); + } + + public boolean isDispatchedLocked(@NonNull Intent intent, @NonNull PrintWriter pw) { + return testAllProcessQueues(q -> q.isDispatched(intent), + "dispatch of " + intent, pw); + } + + @Override public void waitForIdle(@NonNull PrintWriter pw) { waitFor(() -> isIdleLocked(pw)); } @@ -1383,28 +1460,35 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @Override public void waitForBarrier(@NonNull PrintWriter pw) { final long now = SystemClock.uptimeMillis(); - waitFor(() -> isBeyondBarrierLocked(now, pw)); + synchronized (mService) { + forEachMatchingQueue(QUEUE_PREDICATE_ANY, + q -> q.addPrioritizeEarliestRequest()); + } + try { + waitFor(() -> isBeyondBarrierLocked(now, pw)); + } finally { + synchronized (mService) { + forEachMatchingQueue(QUEUE_PREDICATE_ANY, + q -> q.removePrioritizeEarliestRequest()); + } + } + } + + @Override + public void waitForDispatched(@NonNull Intent intent, @NonNull PrintWriter pw) { + waitFor(() -> isDispatchedLocked(intent, pw)); } private void waitFor(@NonNull BooleanSupplier condition) { final CountDownLatch latch = new CountDownLatch(1); synchronized (mService) { mWaitingFor.add(Pair.create(condition, latch)); - forEachMatchingQueue(QUEUE_PREDICATE_ANY, - (q) -> q.setPrioritizeEarliest(true)); } enqueueUpdateRunningList(); try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); - } finally { - synchronized (mService) { - if (mWaitingFor.isEmpty()) { - forEachMatchingQueue(QUEUE_PREDICATE_ANY, - (q) -> q.setPrioritizeEarliest(false)); - } - } } } diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 64fe39314f0e..a92744086f70 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -237,6 +237,14 @@ final class BroadcastRecord extends Binder { } } + /** + * Return true if this receiver should be assumed to have been delivered. + */ + boolean isAssumedDelivered(int index) { + return (receivers.get(index) instanceof BroadcastFilter) && !ordered + && (resultTo == null); + } + ProcessRecord curApp; // hosting application of current receiver. ComponentName curComponent; // the receiver class that is currently running. ActivityInfo curReceiver; // the manifest receiver that is currently running. diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 6015e5f02221..e744eee8b485 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PROVIDER; import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile; import static android.os.Process.PROC_CHAR; import static android.os.Process.PROC_OUT_LONG; @@ -34,7 +35,6 @@ import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_E import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; import static com.android.server.am.ActivityManagerService.TAG_MU; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_PROVIDER; import android.annotation.Nullable; import android.annotation.UserIdInt; diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java index 4c15308a574e..5ad49a47a012 100644 --- a/services/core/java/com/android/server/am/ProcessProfileRecord.java +++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java @@ -17,9 +17,10 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_EMPTY; -import android.annotation.IntDef; import android.app.IApplicationThread; +import android.app.ProcessMemoryState.HostingComponentType; import android.content.pm.ApplicationInfo; import android.os.Debug; import android.os.SystemClock; @@ -42,88 +43,6 @@ import java.util.concurrent.atomic.AtomicLong; * Profiling info of the process, such as PSS, cpu, etc. */ final class ProcessProfileRecord { - // Keep below types in sync with the HostingComponentType in the atoms.proto. - /** - * The type of the component this process is hosting; - * this means not hosting any components (cached). - */ - static final int HOSTING_COMPONENT_TYPE_EMPTY = 0x0; - - /** - * The type of the component this process is hosting; - * this means it's a system process. - */ - static final int HOSTING_COMPONENT_TYPE_SYSTEM = 0x00000001; - - /** - * The type of the component this process is hosting; - * this means it's a persistent process. - */ - static final int HOSTING_COMPONENT_TYPE_PERSISTENT = 0x00000002; - - /** - * The type of the component this process is hosting; - * this means it's hosting a backup/restore agent. - */ - static final int HOSTING_COMPONENT_TYPE_BACKUP = 0x00000004; - - /** - * The type of the component this process is hosting; - * this means it's hosting an instrumentation. - */ - static final int HOSTING_COMPONENT_TYPE_INSTRUMENTATION = 0x00000008; - - /** - * The type of the component this process is hosting; - * this means it's hosting an activity. - */ - static final int HOSTING_COMPONENT_TYPE_ACTIVITY = 0x00000010; - - /** - * The type of the component this process is hosting; - * this means it's hosting a broadcast receiver. - */ - static final int HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER = 0x00000020; - - /** - * The type of the component this process is hosting; - * this means it's hosting a content provider. - */ - static final int HOSTING_COMPONENT_TYPE_PROVIDER = 0x00000040; - - /** - * The type of the component this process is hosting; - * this means it's hosting a started service. - */ - static final int HOSTING_COMPONENT_TYPE_STARTED_SERVICE = 0x00000080; - - /** - * The type of the component this process is hosting; - * this means it's hosting a foreground service. - */ - static final int HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE = 0x00000100; - - /** - * The type of the component this process is hosting; - * this means it's being bound via a service binding. - */ - static final int HOSTING_COMPONENT_TYPE_BOUND_SERVICE = 0x00000200; - - @IntDef(flag = true, prefix = { "HOSTING_COMPONENT_TYPE_" }, value = { - HOSTING_COMPONENT_TYPE_EMPTY, - HOSTING_COMPONENT_TYPE_SYSTEM, - HOSTING_COMPONENT_TYPE_PERSISTENT, - HOSTING_COMPONENT_TYPE_BACKUP, - HOSTING_COMPONENT_TYPE_INSTRUMENTATION, - HOSTING_COMPONENT_TYPE_ACTIVITY, - HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER, - HOSTING_COMPONENT_TYPE_PROVIDER, - HOSTING_COMPONENT_TYPE_STARTED_SERVICE, - HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE, - HOSTING_COMPONENT_TYPE_BOUND_SERVICE, - }) - @interface HostingComponentType {} - final ProcessRecord mApp; private final ActivityManagerService mService; diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 53fa4f1b2ac2..81d0b6ac700b 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -16,8 +16,8 @@ package com.android.server.am; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE; import android.app.ActivityManager; import android.content.Context; diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index ab71acd5f21d..db341d253818 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -19,11 +19,11 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_ACTIVITY; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_STARTED_SERVICE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_ACTIVITY; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_STARTED_SERVICE; import static com.android.server.am.ProcessRecord.TAG; import android.annotation.ElapsedRealtimeLong; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 6551db9ad783..b22ece30c386 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; import static android.os.PowerExemptionManager.REASON_DENIED; import static android.os.Process.INVALID_UID; @@ -25,7 +26,6 @@ import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java index 790cc7b87f80..5e41dcd0009e 100644 --- a/services/core/java/com/android/server/am/UidObserverController.java +++ b/services/core/java/com/android/server/am/UidObserverController.java @@ -27,7 +27,9 @@ import android.app.ActivityManager; import android.app.ActivityManagerProto; import android.app.IUidObserver; import android.content.pm.PackageManager; +import android.os.Binder; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; @@ -43,6 +45,8 @@ import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserve import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; +import java.util.UUID; public class UidObserverController { /** If a UID observer takes more than this long, send a WTF. */ @@ -79,14 +83,19 @@ public class UidObserverController { mValidateUids = new ActiveUids(null /* service */, false /* postChangesToAtm */); } - void register(@NonNull IUidObserver observer, int which, int cutpoint, - @NonNull String callingPackage, int callingUid) { + IBinder register(@NonNull IUidObserver observer, int which, int cutpoint, + @NonNull String callingPackage, int callingUid, @Nullable int[] uids) { + IBinder token = new Binder("UidObserver-" + callingPackage + "-" + + UUID.randomUUID().toString()); + synchronized (mLock) { mUidObservers.register(observer, new UidObserverRegistration(callingUid, callingPackage, which, cutpoint, ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, callingUid) - == PackageManager.PERMISSION_GRANTED)); + == PackageManager.PERMISSION_GRANTED, uids, token)); } + + return token; } void unregister(@NonNull IUidObserver observer) { @@ -95,6 +104,42 @@ public class UidObserverController { } } + void addUidToObserver(@NonNull IBinder observerToken, int uid) { + synchronized (mLock) { + int i = mUidObservers.beginBroadcast(); + while (i-- > 0) { + var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i); + if (reg.getToken().equals(observerToken)) { + reg.addUid(uid); + break; + } + + if (i == 0) { + Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token"); + } + } + mUidObservers.finishBroadcast(); + } + } + + void removeUidFromObserver(@NonNull IBinder observerToken, int uid) { + synchronized (mLock) { + int i = mUidObservers.beginBroadcast(); + while (i-- > 0) { + var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i); + if (reg.getToken().equals(observerToken)) { + reg.removeUid(uid); + break; + } + + if (i == 0) { + Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token"); + } + } + mUidObservers.finishBroadcast(); + } + } + int enqueueUidChange(@Nullable ChangeRecord currentRecord, int uid, int change, int procState, int procAdj, long procStateSeq, int capability, boolean ephemeral) { synchronized (mLock) { @@ -257,6 +302,10 @@ public class UidObserverController { final ChangeRecord item = mActiveUidChanges[j]; final long start = SystemClock.uptimeMillis(); final int change = item.change; + // Is the observer watching this uid? + if (!reg.isWatchingUid(item.uid)) { + continue; + } // Does the user have permission? Don't send a non user UID change otherwise if (UserHandle.getUserId(item.uid) != UserHandle.getUserId(reg.mUid) && !reg.mCanInteractAcrossUsers) { @@ -450,6 +499,8 @@ public class UidObserverController { private final int mWhich; private final int mCutpoint; private final boolean mCanInteractAcrossUsers; + private final IBinder mToken; + private int[] mUids; /** * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}. @@ -481,16 +532,94 @@ public class UidObserverController { }; UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint, - boolean canInteractAcrossUsers) { + boolean canInteractAcrossUsers, @Nullable int[] uids, @NonNull IBinder token) { this.mUid = uid; this.mPkg = pkg; this.mWhich = which; this.mCutpoint = cutpoint; this.mCanInteractAcrossUsers = canInteractAcrossUsers; + + if (uids != null) { + this.mUids = uids.clone(); + Arrays.sort(this.mUids); + } else { + this.mUids = null; + } + + this.mToken = token; + mLastProcStates = cutpoint >= ActivityManager.MIN_PROCESS_STATE ? new SparseIntArray() : null; } + boolean isWatchingUid(int uid) { + if (mUids == null) { + return true; + } + + return Arrays.binarySearch(mUids, uid) != -1; + } + + void addUid(int uid) { + if (mUids == null) { + return; + } + + int[] temp = mUids; + mUids = new int[temp.length + 1]; + boolean inserted = false; + for (int i = 0; i < temp.length; i++) { + if (!inserted) { + if (temp[i] < uid) { + mUids[i] = temp[i]; + } else if (temp[i] == uid) { + // Duplicate uid, no-op and fallback to the previous array + mUids = temp; + return; + } else { + mUids[i] = uid; + mUids[i + 1] = temp[i]; + inserted = true; + } + } else { + mUids[i + 1] = temp[i]; + } + } + + if (!inserted) { + mUids[temp.length] = uid; + } + } + + void removeUid(int uid) { + if (mUids == null || mUids.length == 0) { + return; + } + + int[] temp = mUids; + mUids = new int[temp.length - 1]; + boolean removed = false; + for (int i = 0; i < temp.length; i++) { + if (!removed) { + if (temp[i] == uid) { + removed = true; + } else if (i == temp.length - 1) { + // Uid not found, no-op and fallback to the previous array + mUids = temp; + return; + } else { + mUids[i] = temp[i]; + } + } else { + mUids[i - 1] = temp[i]; + } + } + } + + IBinder getToken() { + return mToken; + } + void dump(@NonNull PrintWriter pw, @NonNull IUidObserver observer) { pw.print(" "); UserHandle.formatUid(pw, mUid); diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index ac8ff21b7fa6..ba5907c4b4d0 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -256,6 +256,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, private final PackageManagerInternal mPmInternal; private volatile boolean mFeatureEnabled = PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT; + @GuardedBy("mDisabledPackages") private final ArraySet<String> mDisabledPackages = new ArraySet<>(); @Nullable @@ -272,7 +273,9 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, mInjector = null; mPmInternal = null; mFeatureEnabled = orig.mFeatureEnabled; - mDisabledPackages.addAll(orig.mDisabledPackages); + synchronized (orig.mDisabledPackages) { + mDisabledPackages.addAll(orig.mDisabledPackages); + } mLoggingEnabled = orig.mLoggingEnabled; } @@ -319,7 +322,9 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "packageIsEnabled"); } try { - return !mDisabledPackages.contains(pkg.getPackageName()); + synchronized (mDisabledPackages) { + return !mDisabledPackages.contains(pkg.getPackageName()); + } } finally { if (DEBUG_TRACING) { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -376,10 +381,12 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, final boolean enabled = mInjector.getCompatibility().isChangeEnabledInternalNoLogging( PackageManager.FILTER_APPLICATION_QUERY, AndroidPackageUtils.generateAppInfoWithoutState(pkg)); - if (enabled) { - mDisabledPackages.remove(pkg.getPackageName()); - } else { - mDisabledPackages.add(pkg.getPackageName()); + synchronized (mDisabledPackages) { + if (enabled) { + mDisabledPackages.remove(pkg.getPackageName()); + } else { + mDisabledPackages.add(pkg.getPackageName()); + } } if (mAppsFilter != null) { mAppsFilter.onChanged(); @@ -393,7 +400,9 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, || setting.getPkg().isDebuggable()); enableLogging(setting.getAppId(), enableLogging); if (removed) { - mDisabledPackages.remove(setting.getPackageName()); + synchronized (mDisabledPackages) { + mDisabledPackages.remove(setting.getPackageName()); + } if (mAppsFilter != null) { mAppsFilter.onChanged(); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 1721f83538ff..a3651946da12 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1316,6 +1316,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements final var snapshot = mPm.snapshotComputer(); final int callingUid = Binder.getCallingUid(); + final var callingPackageName = snapshot.getNameForUid(callingUid); + if (!TextUtils.equals(callingPackageName, installerPackageName)) { + throw new SecurityException("The installerPackageName set by the caller doesn't match " + + "the caller's own package name."); + } if (!PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)) { for (var packageName : packageNames) { var ps = snapshot.getPackageStateInternal(packageName); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 3492b2660c4a..f41d964bf906 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -23,7 +23,6 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT; -import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED; import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; @@ -3657,25 +3656,6 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt for (String permission : pkg.getRequestedPermissions()) { Integer permissionState = permissionStates.get(permission); - if (Objects.equals(permission, Manifest.permission.USE_FULL_SCREEN_INTENT) - && permissionState == null) { - final PackageStateInternal ps; - final long token = Binder.clearCallingIdentity(); - try { - ps = mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); - } finally { - Binder.restoreCallingIdentity(token); - } - final String[] useFullScreenIntentPackageNames = - mContext.getResources().getStringArray( - com.android.internal.R.array.config_useFullScreenIntentPackages); - final boolean canUseFullScreenIntent = (ps != null && ps.isSystem()) - || ArrayUtils.contains(useFullScreenIntentPackageNames, - pkg.getPackageName()); - permissionState = canUseFullScreenIntent ? PERMISSION_STATE_GRANTED - : PERMISSION_STATE_DENIED; - } - if (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT) { continue; } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 6eded1a14dbf..e82521584731 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -18,6 +18,7 @@ package com.android.server.stats.pull; import static android.app.AppOpsManager.OP_FLAG_SELF; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; +import static android.app.AppProtoEnums.HOSTING_COMPONENT_TYPE_EMPTY; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH; @@ -2350,7 +2351,8 @@ public class StatsPullAtomService extends SystemService { snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes, snapshot.anonRssInKilobytes + snapshot.swapInKilobytes, gpuMemPerPid.get(managedProcess.pid), managedProcess.hasForegroundServices, - snapshot.rssShmemKilobytes)); + snapshot.rssShmemKilobytes, managedProcess.mHostingComponentTypes, + managedProcess.mHistoricalHostingComponentTypes)); } // Complement the data with native system processes. Given these measurements can be taken // in response to LMKs happening, we want to first collect the managed app stats (to @@ -2370,7 +2372,10 @@ public class StatsPullAtomService extends SystemService { snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes, snapshot.anonRssInKilobytes + snapshot.swapInKilobytes, gpuMemPerPid.get(pid), false /* has_foreground_services */, - snapshot.rssShmemKilobytes)); + snapshot.rssShmemKilobytes, + // Native processes don't really have a hosting component type. + HOSTING_COMPONENT_TYPE_EMPTY, + HOSTING_COMPONENT_TYPE_EMPTY)); } return StatsManager.PULL_SUCCESS; } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 6f640700949e..2f24b65fd663 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1583,7 +1583,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mShuttingDown = false; mImageWallpaper = ComponentName.unflattenFromString( context.getResources().getString(R.string.image_wallpaper_component)); - mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context); + mDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context); mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mIPackageManager = AppGlobals.getPackageManager(); @@ -2789,6 +2789,20 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } /** + * Returns true if there is a static wallpaper on the specified screen. With which=FLAG_LOCK, + * always return false if the lockscreen doesn't run its own wallpaper engine. + */ + @Override + public boolean isStaticWallpaper(int which) { + synchronized (mLock) { + WallpaperData wallpaperData = (which == FLAG_LOCK ? mLockWallpaperMap : mWallpaperMap) + .get(mCurrentUserId); + if (wallpaperData == null) return false; + return mImageWallpaper.equals(wallpaperData.wallpaperComponent); + } + } + + /** * Sets wallpaper dim amount for the calling UID. This applies to all destinations (home, lock) * with an active wallpaper engine. * diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 3f4a775bc37a..7926216fe15d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -178,6 +178,9 @@ public abstract class ActivityTaskManagerInternal { /** * Returns the top activity from each of the currently visible root tasks, and the related task * id. The first entry will be the focused activity. + * + * <p>NOTE: If the top activity is in the split screen, the other activities in the same split + * screen will also be returned. */ public abstract List<ActivityAssistInfo> getTopVisibleActivities(); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 5149985f8ff9..0074ebd579cf 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1746,9 +1746,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> /** * @return a list of pairs, containing activities and their task id which are the top ones in * each visible root task. The first entry will be the focused activity. + * + * <p>NOTE: If the top activity is in the split screen, the other activities in the same split + * screen will also be returned. */ List<ActivityAssistInfo> getTopVisibleActivities() { final ArrayList<ActivityAssistInfo> topVisibleActivities = new ArrayList<>(); + final ArrayList<ActivityAssistInfo> activityAssistInfos = new ArrayList<>(); final Task topFocusedRootTask = getTopDisplayFocusedRootTask(); // Traverse all displays. forAllRootTasks(rootTask -> { @@ -1756,11 +1760,21 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (rootTask.shouldBeVisible(null /* starting */)) { final ActivityRecord top = rootTask.getTopNonFinishingActivity(); if (top != null) { - ActivityAssistInfo visibleActivity = new ActivityAssistInfo(top); + activityAssistInfos.clear(); + activityAssistInfos.add(new ActivityAssistInfo(top)); + // Check if the activity on the split screen. + final Task adjacentTask = top.getTask().getAdjacentTask(); + if (adjacentTask != null) { + final ActivityRecord adjacentActivityRecord = + adjacentTask.getTopNonFinishingActivity(); + if (adjacentActivityRecord != null) { + activityAssistInfos.add(new ActivityAssistInfo(adjacentActivityRecord)); + } + } if (rootTask == topFocusedRootTask) { - topVisibleActivities.add(0, visibleActivity); + topVisibleActivities.addAll(0, activityAssistInfos); } else { - topVisibleActivities.add(visibleActivity); + topVisibleActivities.addAll(activityAssistInfos); } } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index e94748f111f4..be5f141b3762 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1416,6 +1416,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } void onAppTransitionDone() { + if (mSurfaceFreezer.hasLeash()) { + mSurfaceFreezer.unfreeze(getSyncTransaction()); + } for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.onAppTransitionDone(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8baf048980ed..f253fb0c7271 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3922,15 +3922,17 @@ public class WindowManagerService extends IWindowManager.Stub /** * Returns the touch mode state for the display id passed as argument. + * + * This method will return the default touch mode state (represented by + * {@code com.android.internal.R.bool.config_defaultInTouchMode}) if the display passed as + * argument is no longer registered in {@RootWindowContainer}). */ @Override // Binder call public boolean isInTouchMode(int displayId) { synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent == null) { - throw new IllegalStateException("Failed to retrieve the touch mode state for" - + "display {" + displayId + "}: display is not registered in " - + "WindowRootContainer"); + return mContext.getResources().getBoolean(R.bool.config_defaultInTouchMode); } return displayContent.isInTouchMode(); } diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java index 04ecd6ebd2d1..e3d4c224f1fd 100644 --- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java @@ -19,6 +19,7 @@ package com.android.server.credentials; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.credentials.ClearCredentialStateException; import android.credentials.ClearCredentialStateRequest; import android.credentials.CredentialProviderInfo; import android.credentials.IClearCredentialStateCallback; @@ -29,6 +30,8 @@ import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.util.Slog; +import com.android.server.credentials.metrics.ProviderSessionMetric; + import java.util.ArrayList; import java.util.Set; @@ -92,9 +95,12 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta public void onFinalResponseReceived( ComponentName componentName, Void response) { - mRequestSessionMetric.collectChosenMetricViaCandidateTransfer( - mProviders.get(componentName.flattenToString()).mProviderSessionMetric - .getCandidatePhasePerProviderMetric()); + if (mProviders.get(componentName.flattenToString()) != null) { + ProviderSessionMetric providerSessionMetric = + mProviders.get(componentName.flattenToString()).mProviderSessionMetric; + mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric + .getCandidatePhasePerProviderMetric()); + } respondToClientWithResponseAndFinish(null); } @@ -141,8 +147,9 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta return; } } - // TODO: Replace with properly defined error type - respondToClientWithErrorAndFinish("UNKNOWN", "All providers failed"); + String exception = ClearCredentialStateException.TYPE_UNKNOWN; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "All providers failed"); } @Override diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index 0af56470965c..2d6e74f0ccf1 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -35,6 +35,7 @@ import android.service.credentials.CallingAppInfo; import android.service.credentials.PermissionUtils; import android.util.Slog; +import com.android.server.credentials.metrics.ProviderSessionMetric; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -131,9 +132,12 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Nullable CreateCredentialResponse response) { Slog.i(TAG, "Final credential received from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); - mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get( - componentName.flattenToString()).mProviderSessionMetric - .getCandidatePhasePerProviderMetric()); + if (mProviders.get(componentName.flattenToString()) != null) { + ProviderSessionMetric providerSessionMetric = + mProviders.get(componentName.flattenToString()).mProviderSessionMetric; + mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric + .getCandidatePhasePerProviderMetric()); + } if (response != null) { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); @@ -141,7 +145,9 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR } else { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, + String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "Invalid response"); } } @@ -154,18 +160,21 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Override public void onUiCancellation(boolean isUserCancellation) { - if (isUserCancellation) { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_USER_CANCELED, - "User cancelled the selector"); - } else { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_INTERRUPTED, - "The UI was interrupted - please try again."); + String exception = CreateCredentialException.TYPE_USER_CANCELED; + String message = "User cancelled the selector"; + if (!isUserCancellation) { + exception = CreateCredentialException.TYPE_INTERRUPTED; + message = "The UI was interrupted - please try again."; } + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, message); } @Override public void onUiSelectorInvocationFailure() { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, + String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No create options available."); } @@ -181,7 +190,9 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR Slog.i(TAG, "Provider status changed - ui invocation is needed"); getProviderDataAndInitiateUi(); } else { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, + String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No create options available."); } } diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index e9fa88328691..9eb3b3b5b7cd 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -32,6 +32,7 @@ import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.util.Slog; +import com.android.server.credentials.metrics.ProviderSessionMetric; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -106,8 +107,10 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, } catch (RemoteException e) { mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false); mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED); + String exception = GetCredentialException.TYPE_UNKNOWN; + mRequestSessionMetric.collectFrameworkException(exception); respondToClientWithErrorAndFinish( - GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector"); + exception, "Unable to instantiate selector"); } } @@ -128,9 +131,12 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Nullable GetCredentialResponse response) { Slog.i(TAG, "onFinalResponseReceived from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); - mRequestSessionMetric.collectChosenMetricViaCandidateTransfer( - mProviders.get(componentName.flattenToString()) - .mProviderSessionMetric.getCandidatePhasePerProviderMetric()); + if (mProviders.get(componentName.flattenToString()) != null) { + ProviderSessionMetric providerSessionMetric = + mProviders.get(componentName.flattenToString()).mProviderSessionMetric; + mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric + .getCandidatePhasePerProviderMetric()); + } if (response != null) { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); @@ -138,7 +144,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, } else { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "Invalid response from provider"); } } @@ -152,18 +160,21 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override public void onUiCancellation(boolean isUserCancellation) { - if (isUserCancellation) { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_USER_CANCELED, - "User cancelled the selector"); - } else { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_INTERRUPTED, - "The UI was interrupted - please try again."); + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + String message = "User cancelled the selector"; + if (!isUserCancellation) { + exception = GetCredentialException.TYPE_INTERRUPTED; + message = "The UI was interrupted - please try again."; } + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, message); } @Override public void onUiSelectorInvocationFailure() { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No credentials available."); } @@ -187,7 +198,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, Slog.i(TAG, "Provider status changed - ui invocation is needed"); getProviderDataAndInitiateUi(); } else { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No credentials available"); } } @@ -208,7 +221,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, // Respond to client if all auth entries are empty and nothing else to show on the UI if (providerDataContainsEmptyAuthEntriesOnly()) { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No credentials available"); } } diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index e4c6b3a10dd8..f9c44a94f89b 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -24,12 +24,14 @@ import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.CandidateAggregateMetric; import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric; import com.android.server.credentials.metrics.CandidatePhaseMetric; import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric; import com.android.server.credentials.metrics.EntryEnum; import com.android.server.credentials.metrics.InitialPhaseMetric; +import java.security.SecureRandom; import java.util.List; import java.util.Map; @@ -48,12 +50,15 @@ public class MetricUtilities { public static final String DEFAULT_STRING = ""; public static final int[] DEFAULT_REPEATED_INT_32 = new int[0]; public static final String[] DEFAULT_REPEATED_STR = new String[0]; + public static final boolean[] DEFAULT_REPEATED_BOOL = new boolean[0]; // Used for single count metric emits, such as singular amounts of various types public static final int UNIT = 1; // Used for zero count metric emits, such as zero amounts of various types public static final int ZERO = 0; // The number of characters at the end of the string to use as a key - public static final int DELTA_CUT = 20; + public static final int DELTA_RESPONSES_CUT = 20; + // The cut for exception strings from the end - used to keep metrics small + public static final int DELTA_EXCEPTION_CUT = 30; /** * This retrieves the uid of any package name, given a context and a component name for the @@ -76,6 +81,15 @@ public class MetricUtilities { } /** + * Used to help generate random sequences for local sessions, in the time-scale of credential + * manager flows. + * @return a high entropy int useful to use in reasonable time-frame sessions. + */ + public static int getHighlyUniqueInteger() { + return new SecureRandom().nextInt(); + } + + /** * Given any two timestamps in nanoseconds, this gets the difference and converts to * milliseconds. Assumes the difference is not larger than the maximum int size. * @@ -127,7 +141,7 @@ public class MetricUtilities { index++; } FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED, - /* session_id */ finalPhaseMetric.getSessionId(), + /* session_id */ finalPhaseMetric.getSessionIdProvider(), /* sequence_num */ emitSequenceId, /* ui_returned_final_start */ finalPhaseMetric.isUiReturned(), /* chosen_provider_uid */ finalPhaseMetric.getChosenUid(), @@ -165,8 +179,9 @@ public class MetricUtilities { finalPhaseMetric.getResponseCollective().getUniqueResponseStrings(), /* per_classtype_counts */ finalPhaseMetric.getResponseCollective().getUniqueResponseCounts(), - /* framework_exception_unique_classtypes */ - DEFAULT_STRING + /* framework_exception_unique_classtype */ + finalPhaseMetric.getFrameworkException(), + /* primary_indicated */ false ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during final provider uid emit: " + e); @@ -210,7 +225,7 @@ public class MetricUtilities { CandidatePhaseMetric metric = session.mProviderSessionMetric .getCandidatePhasePerProviderMetric(); if (sessionId == -1) { - sessionId = metric.getSessionId(); + sessionId = metric.getSessionIdProvider(); } if (!queryReturned) { queryReturned = metric.isQueryReturned(); @@ -268,7 +283,9 @@ public class MetricUtilities { /* per_classtype_counts */ initialPhaseMetric.getUniqueRequestCounts(), /* api_name */ - initialPhaseMetric.getApiName() + initialPhaseMetric.getApiName(), + /* primary_candidates_indicated */ + DEFAULT_REPEATED_BOOL ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during candidate provider uid metric emit: " + e); @@ -312,7 +329,7 @@ public class MetricUtilities { FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_INIT_PHASE_REPORTED, /* api_name */ initialPhaseMetric.getApiName(), /* caller_uid */ initialPhaseMetric.getCallerUid(), - /* session_id */ initialPhaseMetric.getSessionId(), + /* session_id */ initialPhaseMetric.getSessionIdCaller(), /* sequence_num */ sequenceNum, /* initial_timestamp_reference_nanoseconds */ initialPhaseMetric.getCredentialServiceStartedTimeNanoseconds(), @@ -329,4 +346,129 @@ public class MetricUtilities { Slog.w(TAG, "Unexpected error during initial metric emit: " + e); } } + + /** + * A logging utility focused on track 1, where the calling app is known. This captures all + * aggregate information for the candidate phase. + * + * @param candidateAggregateMetric the aggregate candidate metric information collected + * @param sequenceNum the sequence number for this api call session emit + */ + public static void logApiCalledAggregateCandidate( + CandidateAggregateMetric candidateAggregateMetric, + int sequenceNum) { + try { + if (!LOG_FLAG) { + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_TOTAL_REPORTED, + /*session_id*/ candidateAggregateMetric.getSessionIdProvider(), + /*sequence_num*/ sequenceNum, + /*query_returned*/ candidateAggregateMetric.isQueryReturned(), + /*num_providers*/ candidateAggregateMetric.getNumProviders(), + /*min_query_start_timestamp_microseconds*/ + DEFAULT_INT_32, + /*max_query_end_timestamp_microseconds*/ + DEFAULT_INT_32, + /*query_response_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*query_per_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*query_unique_entries*/ + DEFAULT_REPEATED_INT_32, + /*query_per_entry_counts*/ + DEFAULT_REPEATED_INT_32, + /*query_total_candidate_failure*/ + DEFAULT_INT_32, + /*query_framework_exception_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*query_per_exception_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*auth_response_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*auth_per_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*auth_unique_entries*/ + DEFAULT_REPEATED_INT_32, + /*auth_per_entry_counts*/ + DEFAULT_REPEATED_INT_32, + /*auth_total_candidate_failure*/ + DEFAULT_INT_32, + /*auth_framework_exception_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*auth_per_exception_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*num_auth_clicks*/ + DEFAULT_INT_32, + /*auth_returned*/ false + ); + } + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during metric logging: " + e); + } + } + + /** + * A logging utility used primarily for the final phase of the current metric setup for track 1. + * + * @param finalPhaseMetric the coalesced data of the chosen provider + * @param browsingPhaseMetrics the coalesced data of the browsing phase + * @param apiStatus the final status of this particular api call + * @param emitSequenceId an emitted sequence id for the current session + */ + public static void logApiCalledNoUidFinal(ChosenProviderFinalPhaseMetric finalPhaseMetric, + List<CandidateBrowsingPhaseMetric> browsingPhaseMetrics, int apiStatus, + int emitSequenceId) { + try { + if (!LOG_FLAG) { + return; + } + int browsedSize = browsingPhaseMetrics.size(); + int[] browsedClickedEntries = new int[browsedSize]; + int[] browsedProviderUid = new int[browsedSize]; + int index = 0; + for (CandidateBrowsingPhaseMetric metric : browsingPhaseMetrics) { + browsedClickedEntries[index] = metric.getEntryEnum(); + browsedProviderUid[index] = metric.getProviderUid(); + index++; + } + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINALNOUID_REPORTED, + /* session_id */ finalPhaseMetric.getSessionIdCaller(), + /* sequence_num */ emitSequenceId, + /* ui_returned_final_start */ finalPhaseMetric.isUiReturned(), + /* chosen_provider_query_start_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getQueryStartTimeNanoseconds()), + /* chosen_provider_query_end_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getQueryEndTimeNanoseconds()), + /* chosen_provider_ui_invoked_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getUiCallStartTimeNanoseconds()), + /* chosen_provider_ui_finished_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getUiCallEndTimeNanoseconds()), + /* chosen_provider_finished_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getFinalFinishTimeNanoseconds()), + /* chosen_provider_status */ finalPhaseMetric.getChosenProviderStatus(), + /* chosen_provider_has_exception */ finalPhaseMetric.isHasException(), + /* unique_entries */ + finalPhaseMetric.getResponseCollective().getUniqueEntries(), + /* per_entry_counts */ + finalPhaseMetric.getResponseCollective().getUniqueEntryCounts(), + /* unique_response_classtypes */ + finalPhaseMetric.getResponseCollective().getUniqueResponseStrings(), + /* per_classtype_counts */ + finalPhaseMetric.getResponseCollective().getUniqueResponseCounts(), + /* framework_exception_unique_classtype */ + finalPhaseMetric.getFrameworkException(), + /* clicked_entries */ browsedClickedEntries, + /* provider_of_clicked_entry */ browsedProviderUid, + /* api_status */ apiStatus, + /* primary_indicated */ false + ); + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during metric logging: " + e); + } + } + } diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java index 1f0346a2b9d6..d4b88001111e 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java @@ -132,7 +132,8 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS protected void invokeSession() { if (mRemoteCredentialService != null) { startCandidateMetrics(); - mRemoteCredentialService.onClearCredentialState(mProviderRequest, this); + mRemoteCredentialService.setCallback(this); + mRemoteCredentialService.onClearCredentialState(mProviderRequest); } } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java index 16beaa4eb13e..25f20caee16d 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -193,11 +193,11 @@ public final class ProviderCreateSession extends ProviderSession< mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(), response.getRemoteCreateEntry()); if (mProviderResponseDataHandler.isEmptyResponse(response)) { - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } else { - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } @@ -248,7 +248,8 @@ public final class ProviderCreateSession extends ProviderSession< protected void invokeSession() { if (mRemoteCredentialService != null) { startCandidateMetrics(); - mRemoteCredentialService.onBeginCreateCredential(mProviderRequest, this); + mRemoteCredentialService.setCallback(this); + mRemoteCredentialService.onBeginCreateCredential(mProviderRequest); } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 1e80703378e0..51af25b58992 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -309,7 +309,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential protected void invokeSession() { if (mRemoteCredentialService != null) { startCandidateMetrics(); - mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this); + mRemoteCredentialService.setCallback(this); + mRemoteCredentialService.onBeginGetCredential(mProviderRequest); } } @@ -432,6 +433,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential BeginGetCredentialResponse response = PendingIntentResultHandler .extractResponseContent(providerPendingIntentResponse .getResultData()); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/true); if (response != null && !mProviderResponseDataHandler.isEmptyResponse(response)) { addToInitialRemoteResponse(response, /*isInitialResponse=*/ false); // Additional content received is in the form of new response content. @@ -469,12 +471,12 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential addToInitialRemoteResponse(response, /*isInitialResponse=*/true); // Log the data. if (mProviderResponseDataHandler.isEmptyResponse(response)) { - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE, /*source=*/ CredentialsSource.REMOTE_PROVIDER); return; } - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index 27b78a4b7b15..068ca7928117 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -71,7 +71,7 @@ public abstract class ProviderSession<T, R> @NonNull protected Boolean mProviderResponseSet = false; @NonNull - protected final ProviderSessionMetric mProviderSessionMetric = new ProviderSessionMetric(); + protected final ProviderSessionMetric mProviderSessionMetric; @NonNull private int mProviderSessionUid; @@ -114,6 +114,13 @@ public abstract class ProviderSession<T, R> } /** + * Gives access to the objects metric collectors. + */ + public ProviderSessionMetric getProviderSessionMetric() { + return this.mProviderSessionMetric; + } + + /** * Interface to be implemented by any class that wishes to get a callback when a particular * provider session's status changes. Typically, implemented by the {@link RequestSession} * class. @@ -147,6 +154,8 @@ public abstract class ProviderSession<T, R> mComponentName = componentName; mRemoteCredentialService = remoteCredentialService; mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName); + mProviderSessionMetric = new ProviderSessionMetric( + ((RequestSession) mCallbacks).mRequestSessionMetric.getSessionIdTrackTwo()); } /** Provider status at various states of the provider session. */ @@ -206,10 +215,10 @@ public abstract class ProviderSession<T, R> CredentialsSource source) { setStatus(status); mProviderSessionMetric.collectCandidateMetricUpdate(isTerminatingStatus(status), - isCompletionStatus(status), mProviderSessionUid); + isCompletionStatus(status), mProviderSessionUid, + source == CredentialsSource.AUTH_ENTRY); mCallbacks.onProviderStatusChanged(status, mComponentName, source); } - /** Common method that transfers metrics from the init phase to candidates */ protected void startCandidateMetrics() { mProviderSessionMetric.collectCandidateMetricSetupViaInitialMetric( diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java index 83e7e939e064..4bcf8be0d21e 100644 --- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java +++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java @@ -48,6 +48,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; /** @@ -66,6 +67,10 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr private final ComponentName mComponentName; + private AtomicBoolean mOngoingRequest = new AtomicBoolean(false); + + @Nullable private ProviderCallbacks mCallback; + /** * Callbacks to be invoked when the provider remote service responds with a * success or failure. @@ -94,12 +99,35 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr mComponentName = componentName; } + public void setCallback(ProviderCallbacks callback) { + mCallback = callback; + } + /** Unbinds automatically after this amount of time. */ @Override protected long getAutoDisconnectTimeoutMs() { return TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS; } + @Override + public void onBindingDied(ComponentName name) { + super.onBindingDied(name); + + Slog.w(TAG, "binding died for: " + name); + } + + @Override + public void binderDied() { + super.binderDied(); + Slog.w(TAG, "binderDied"); + + if (mCallback != null) { + mOngoingRequest.set(false); + mCallback.onProviderServiceDied(this); + } + + } + /** Return the componentName of the service to be connected. */ @NonNull public ComponentName getComponentName() { @@ -116,11 +144,14 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * provider service. * * @param request the request to be sent to the provider - * @param callback the callback to be used to send back the provider response to the - * {@link ProviderGetSession} class that maintains provider state */ - public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request, - ProviderCallbacks<BeginGetCredentialResponse> callback) { + public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request) { + if (mCallback == null) { + Slog.w(TAG, "Callback is not set"); + return; + } + mOngoingRequest.set(true); + AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef = new AtomicReference<>(); @@ -154,7 +185,9 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr dispatchCancellationSignal(cancellation); } else { cancellationSink.set(cancellation); - callback.onProviderCancellable(cancellation); + if (mCallback != null) { + mCallback.onProviderCancellable(cancellation); + } } } }); @@ -166,7 +199,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> - handleExecutionResponse(result, error, cancellationSink, callback))); + handleExecutionResponse(result, error, cancellationSink))); } /** @@ -174,11 +207,14 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * provider service. * * @param request the request to be sent to the provider - * @param callback the callback to be used to send back the provider response to the - * {@link ProviderCreateSession} class that maintains provider state */ - public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request, - ProviderCallbacks<BeginCreateCredentialResponse> callback) { + public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request) { + if (mCallback == null) { + Slog.w(TAG, "Callback is not set"); + return; + } + mOngoingRequest.set(true); + AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef = new AtomicReference<>(); @@ -212,7 +248,9 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr dispatchCancellationSignal(cancellation); } else { cancellationSink.set(cancellation); - callback.onProviderCancellable(cancellation); + if (mCallback != null) { + mCallback.onProviderCancellable(cancellation); + } } } }); @@ -224,7 +262,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> - handleExecutionResponse(result, error, cancellationSink, callback))); + handleExecutionResponse(result, error, cancellationSink))); } /** @@ -232,11 +270,14 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * provider service. * * @param request the request to be sent to the provider - * @param callback the callback to be used to send back the provider response to the - * {@link ProviderClearSession} class that maintains provider state */ - public void onClearCredentialState(@NonNull ClearCredentialStateRequest request, - ProviderCallbacks<Void> callback) { + public void onClearCredentialState(@NonNull ClearCredentialStateRequest request) { + if (mCallback == null) { + Slog.w(TAG, "Callback is not set"); + return; + } + mOngoingRequest.set(true); + AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>(); @@ -269,7 +310,9 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr dispatchCancellationSignal(cancellation); } else { cancellationSink.set(cancellation); - callback.onProviderCancellable(cancellation); + if (mCallback != null) { + mCallback.onProviderCancellable(cancellation); + } } } }); @@ -281,40 +324,58 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> - handleExecutionResponse(result, error, cancellationSink, callback))); + handleExecutionResponse(result, error, cancellationSink))); } private <T> void handleExecutionResponse(T result, Throwable error, - AtomicReference<ICancellationSignal> cancellationSink, - ProviderCallbacks<T> callback) { + AtomicReference<ICancellationSignal> cancellationSink) { if (error == null) { - callback.onProviderResponseSuccess(result); + if (mCallback != null) { + mCallback.onProviderResponseSuccess(result); + } } else { if (error instanceof TimeoutException) { Slog.i(TAG, "Remote provider response timed tuo for: " + mComponentName); + if (!mOngoingRequest.get()) { + return; + } dispatchCancellationSignal(cancellationSink.get()); - callback.onProviderResponseFailure( - CredentialProviderErrors.ERROR_TIMEOUT, - null); + if (mCallback != null) { + mOngoingRequest.set(false); + mCallback.onProviderResponseFailure( + CredentialProviderErrors.ERROR_TIMEOUT, null); + } } else if (error instanceof CancellationException) { Slog.i(TAG, "Cancellation exception for remote provider: " + mComponentName); + if (!mOngoingRequest.get()) { + return; + } dispatchCancellationSignal(cancellationSink.get()); - callback.onProviderResponseFailure( - CredentialProviderErrors.ERROR_TASK_CANCELED, - null); + if (mCallback != null) { + mOngoingRequest.set(false); + mCallback.onProviderResponseFailure( + CredentialProviderErrors.ERROR_TASK_CANCELED, + null); + } } else if (error instanceof GetCredentialException) { - callback.onProviderResponseFailure( - CredentialProviderErrors.ERROR_PROVIDER_FAILURE, - (GetCredentialException) error); + if (mCallback != null) { + mCallback.onProviderResponseFailure( + CredentialProviderErrors.ERROR_PROVIDER_FAILURE, + (GetCredentialException) error); + } } else if (error instanceof CreateCredentialException) { - callback.onProviderResponseFailure( - CredentialProviderErrors.ERROR_PROVIDER_FAILURE, - (CreateCredentialException) error); + if (mCallback != null) { + mCallback.onProviderResponseFailure( + CredentialProviderErrors.ERROR_PROVIDER_FAILURE, + (CreateCredentialException) error); + } } else { - callback.onProviderResponseFailure( - CredentialProviderErrors.ERROR_UNKNOWN, - (Exception) error); + if (mCallback != null) { + mCallback.onProviderResponseFailure( + CredentialProviderErrors.ERROR_UNKNOWN, + (Exception) error); + } } } } diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index 7caa921eacda..a41b5713ee14 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -75,6 +75,8 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential protected final Handler mHandler; @UserIdInt protected final int mUserId; + + protected final int mUniqueSessionInteger; private final int mCallingUid; @NonNull protected final CallingAppInfo mClientAppInfo; @@ -82,7 +84,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential protected final CancellationSignal mCancellationSignal; protected final Map<String, ProviderSession> mProviders = new ConcurrentHashMap<>(); - protected final RequestSessionMetric mRequestSessionMetric = new RequestSessionMetric(); + protected final RequestSessionMetric mRequestSessionMetric; protected final String mHybridService; protected final Object mLock; @@ -132,7 +134,10 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential mUserId, this, mEnabledProviders); mHybridService = context.getResources().getString( R.string.config_defaultCredentialManagerHybridService); - mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mRequestId, + mUniqueSessionInteger = MetricUtilities.getHighlyUniqueInteger(); + mRequestSessionMetric = new RequestSessionMetric(mUniqueSessionInteger, + MetricUtilities.getHighlyUniqueInteger()); + mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType)); setCancellationListener(); } diff --git a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java new file mode 100644 index 000000000000..51e86d51acdf --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java @@ -0,0 +1,36 @@ +/* + * 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.credentials.metrics; + +/** + * Encapsulates an authentication entry click atom, as a part of track 2. + * Contains information about what was collected from the authentication entry output. + */ +public class BrowsedAuthenticationMetric { + // The session id of this provider known flow related metric + private final int mSessionIdProvider; + // TODO(b/271135048) - Match the atom and provide a clean per provider session metric + // encapsulation. + + public BrowsedAuthenticationMetric(int sessionIdProvider) { + mSessionIdProvider = sessionIdProvider; + } + + public int getSessionIdProvider() { + return mSessionIdProvider; + } +} diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java new file mode 100644 index 000000000000..08e75837a274 --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java @@ -0,0 +1,75 @@ +/* + * 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.credentials.metrics; + +import com.android.server.credentials.ProviderSession; + +import java.util.Map; + +/** + * This will generate most of its data via using the information of {@link CandidatePhaseMetric} + * across all the providers. This belongs to the metric flow where the calling app is known. + */ +public class CandidateAggregateMetric { + + private static final String TAG = "CandidateProviderMetric"; + // The session id of this provider metric + private final int mSessionIdProvider; + // Indicates if this provider returned from the query phase, default false + private boolean mQueryReturned = false; + // Indicates the total number of providers this aggregate captures information for, default 0 + private int mNumProviders = 0; + // Indicates the total number of authentication entries that were tapped in aggregate, default 0 + private int mNumAuthEntriesTapped = 0; + + public CandidateAggregateMetric(int sessionIdTrackOne) { + mSessionIdProvider = sessionIdTrackOne; + } + + public int getSessionIdProvider() { + return mSessionIdProvider; + } + + /** + * This will take all the candidate data captured and aggregate that information. + * TODO(b/271135048) : Add on authentication entry outputs from track 2 here as well once + * generated + * @param providers the providers associated with the candidate flow + */ + public void collectAverages(Map<String, ProviderSession> providers) { + // TODO(b/271135048) : Complete this method + mNumProviders = providers.size(); + var providerSessions = providers.values(); + for (var session : providerSessions) { + var metric = session.getProviderSessionMetric(); + mQueryReturned = mQueryReturned || metric + .mCandidatePhasePerProviderMetric.isQueryReturned(); + } + } + + public int getNumProviders() { + return mNumProviders; + } + + public boolean isQueryReturned() { + return mQueryReturned; + } + + public int getNumAuthEntriesTapped() { + return mNumAuthEntriesTapped; + } +} diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java index 07af6549411e..6b74252dec19 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java @@ -27,23 +27,11 @@ package com.android.server.credentials.metrics; * though collection will begin in the candidate phase when the user begins browsing options. */ public class CandidateBrowsingPhaseMetric { - // The session id associated with the API Call this candidate provider is a part of, default -1 - private int mSessionId = -1; // The EntryEnum that was pressed, defaults to -1 private int mEntryEnum = EntryEnum.UNKNOWN.getMetricCode(); // The provider associated with the press, defaults to -1 private int mProviderUid = -1; - /* -- The session ID -- */ - - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; - } - /* -- The Entry of this tap -- */ public void setEntryEnum(int entryEnum) { diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java index 3ea9b1ce86f8..d9bf4a134adb 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java @@ -32,8 +32,8 @@ import java.util.Map; public class CandidatePhaseMetric { private static final String TAG = "CandidateProviderMetric"; - // The session id of this provider, default set to -1 - private int mSessionId = -1; + // The session id of this provider metric + private final int mSessionIdProvider; // Indicates if this provider returned from the query phase, default false private boolean mQueryReturned = false; @@ -53,13 +53,15 @@ public class CandidatePhaseMetric { private int mProviderQueryStatus = -1; // Indicates if an exception was thrown by this provider, false by default private boolean mHasException = false; + // Indicates the framework only exception belonging to this provider private String mFrameworkException = ""; // Stores the response credential information, as well as the response entry information which // by default, contains empty info private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of()); - public CandidatePhaseMetric() { + public CandidatePhaseMetric(int sessionIdTrackTwo) { + mSessionIdProvider = sessionIdTrackTwo; } /* ---------- Latencies ---------- */ @@ -141,12 +143,8 @@ public class CandidatePhaseMetric { /* -------------- Session Id ---------------- */ - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; + public int getSessionIdProvider() { + return mSessionIdProvider; } /* -------------- Query Returned Status ---------------- */ diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java index 93a82906aa50..e8af86012aaf 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java @@ -32,8 +32,12 @@ import java.util.Map; */ public class ChosenProviderFinalPhaseMetric { private static final String TAG = "ChosenFinalPhaseMetric"; - // The session id associated with this API call, used to unite split emits - private int mSessionId = -1; + // The session id associated with this API call, used to unite split emits, for the flow + // where we know the calling app + private final int mSessionIdCaller; + // The session id associated with this API call, used to unite split emits, for the flow + // where we know the provider apps + private final int mSessionIdProvider; // Reveals if the UI was returned, false by default private boolean mUiReturned = false; private int mChosenUid = -1; @@ -66,13 +70,17 @@ public class ChosenProviderFinalPhaseMetric { private int mChosenProviderStatus = -1; // Indicates if an exception was thrown by this provider, false by default private boolean mHasException = false; + // Indicates a framework only exception that occurs in the final phase of the flow + private String mFrameworkException = ""; // Stores the response credential information, as well as the response entry information which // by default, contains empty info private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of()); - public ChosenProviderFinalPhaseMetric() { + public ChosenProviderFinalPhaseMetric(int sessionIdCaller, int sessionIdProvider) { + mSessionIdCaller = sessionIdCaller; + mSessionIdProvider = sessionIdProvider; } /* ------------------- UID ------------------- */ @@ -235,12 +243,8 @@ public class ChosenProviderFinalPhaseMetric { /* ----------- Session ID -------------- */ - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; + public int getSessionIdProvider() { + return mSessionIdProvider; } /* ----------- UI Returned Successfully -------------- */ @@ -272,4 +276,20 @@ public class ChosenProviderFinalPhaseMetric { public ResponseCollective getResponseCollective() { return mResponseCollective; } + + /* -------------- Framework Exception ---------------- */ + + public void setFrameworkException(String frameworkException) { + mFrameworkException = frameworkException; + } + + public String getFrameworkException() { + return mFrameworkException; + } + + /* -------------- Session ID for Track One (Known Calling App) ---------------- */ + + public int getSessionIdCaller() { + return mSessionIdCaller; + } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java index 060e56ce965b..8e965e3e5ba5 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java @@ -32,8 +32,8 @@ public class InitialPhaseMetric { private int mApiName = ApiName.UNKNOWN.getMetricCode(); // The caller uid of the calling application, default to -1 private int mCallerUid = -1; - // The session id to unite multiple atom emits, default to -1 - private int mSessionId = -1; + // The session id to unite multiple atom emits + private final int mSessionIdCaller; // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a // reference point. @@ -50,7 +50,8 @@ public class InitialPhaseMetric { private Map<String, Integer> mRequestCounts = new LinkedHashMap<>(); - public InitialPhaseMetric() { + public InitialPhaseMetric(int sessionIdTrackOne) { + mSessionIdCaller = sessionIdTrackOne; } /* ---------- Latencies ---------- */ @@ -105,12 +106,8 @@ public class InitialPhaseMetric { /* ------ SessionId ------ */ - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; + public int getSessionIdCaller() { + return mSessionIdCaller; } /* ------ Count Request Class Types ------ */ diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java index f011b554fe53..47db8f59ff35 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java @@ -16,7 +16,7 @@ package com.android.server.credentials.metrics; -import static com.android.server.credentials.MetricUtilities.DELTA_CUT; +import static com.android.server.credentials.MetricUtilities.DELTA_RESPONSES_CUT; import static com.android.server.credentials.MetricUtilities.generateMetricKey; import android.annotation.NonNull; @@ -44,10 +44,17 @@ public class ProviderSessionMetric { // Specific candidate provider metric for the provider this session handles @NonNull - protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric = - new CandidatePhaseMetric(); + protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric; - public ProviderSessionMetric() {} + // IFF there was an authentication entry clicked, this stores all required information for + // that event. This is for the 'get' flow. + @NonNull + protected final BrowsedAuthenticationMetric mBrowsedAuthenticationMetric; + + public ProviderSessionMetric(int sessionIdTrackTwo) { + mCandidatePhasePerProviderMetric = new CandidatePhaseMetric(sessionIdTrackTwo); + mBrowsedAuthenticationMetric = new BrowsedAuthenticationMetric(sessionIdTrackTwo); + } /** * Retrieve the candidate provider phase metric and the data it contains. @@ -56,6 +63,7 @@ public class ProviderSessionMetric { return mCandidatePhasePerProviderMetric; } + /** * This collects for ProviderSessions, with respect to the candidate providers, whether * an exception occurred in the candidate call. @@ -78,6 +86,13 @@ public class ProviderSessionMetric { } } + private void collectAuthEntryUpdate(boolean isFailureStatus, + boolean isCompletionStatus, int providerSessionUid) { + // TODO(b/271135048) - Mimic typical candidate update, but with authentication metric + // Collect the final timestamps (and start timestamp), status, exceptions and the provider + // uid. This occurs typically *after* the collection is complete. + } + /** * Used to collect metrics at the update stage when a candidate provider gives back an update. * @@ -86,8 +101,12 @@ public class ProviderSessionMetric { * @param providerSessionUid the uid of the provider */ public void collectCandidateMetricUpdate(boolean isFailureStatus, - boolean isCompletionStatus, int providerSessionUid) { + boolean isCompletionStatus, int providerSessionUid, boolean isAuthEntry) { try { + if (isAuthEntry) { + collectAuthEntryUpdate(isFailureStatus, isCompletionStatus, providerSessionUid); + return; + } mCandidatePhasePerProviderMetric.setCandidateUid(providerSessionUid); mCandidatePhasePerProviderMetric .setQueryFinishTimeNanoseconds(System.nanoTime()); @@ -119,7 +138,6 @@ public class ProviderSessionMetric { */ public void collectCandidateMetricSetupViaInitialMetric(InitialPhaseMetric initMetric) { try { - mCandidatePhasePerProviderMetric.setSessionId(initMetric.getSessionId()); mCandidatePhasePerProviderMetric.setServiceBeganTimeNanoseconds( initMetric.getCredentialServiceStartedTimeNanoseconds()); mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime()); @@ -133,13 +151,14 @@ public class ProviderSessionMetric { * purposes. * * @param response contains entries and data from the candidate provider responses + * @param isAuthEntry indicates if this is an auth entry collection or not * @param <R> the response type associated with the API flow in progress */ - public <R> void collectCandidateEntryMetrics(R response) { + public <R> void collectCandidateEntryMetrics(R response, boolean isAuthEntry) { try { if (response instanceof BeginGetCredentialResponse) { beginGetCredentialResponseCollectionCandidateEntryMetrics( - (BeginGetCredentialResponse) response); + (BeginGetCredentialResponse) response, isAuthEntry); } else if (response instanceof BeginCreateCredentialResponse) { beginCreateCredentialResponseCollectionCandidateEntryMetrics( (BeginCreateCredentialResponse) response); @@ -170,7 +189,7 @@ public class ProviderSessionMetric { entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries); entries.forEach(entry -> { - String entryKey = generateMetricKey(entry.getType(), DELTA_CUT); + String entryKey = generateMetricKey(entry.getType(), DELTA_RESPONSES_CUT); responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1); }); @@ -198,7 +217,7 @@ public class ProviderSessionMetric { } private void beginGetCredentialResponseCollectionCandidateEntryMetrics( - BeginGetCredentialResponse response) { + BeginGetCredentialResponse response, boolean isAuthEntry) { Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>(); Map<String, Integer> responseCounts = new LinkedHashMap<>(); int numCredEntries = response.getCredentialEntries().size(); @@ -212,11 +231,16 @@ public class ProviderSessionMetric { entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries); response.getCredentialEntries().forEach(entry -> { - String entryKey = generateMetricKey(entry.getType(), DELTA_CUT); + String entryKey = generateMetricKey(entry.getType(), DELTA_RESPONSES_CUT); responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1); }); ResponseCollective responseCollective = new ResponseCollective(responseCounts, entryCounts); - mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective); + + if (!isAuthEntry) { + mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective); + } else { + // TODO(b/immediately) - Add the auth entry get logic + } } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java index 4624e0b3701a..03ffe23f9886 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -16,14 +16,16 @@ package com.android.server.credentials.metrics; -import static com.android.server.credentials.MetricUtilities.DELTA_CUT; +import static com.android.server.credentials.MetricUtilities.DELTA_EXCEPTION_CUT; +import static com.android.server.credentials.MetricUtilities.DELTA_RESPONSES_CUT; import static com.android.server.credentials.MetricUtilities.generateMetricKey; import static com.android.server.credentials.MetricUtilities.logApiCalledCandidatePhase; import static com.android.server.credentials.MetricUtilities.logApiCalledFinalPhase; +import static com.android.server.credentials.MetricUtilities.logApiCalledNoUidFinal; +import android.annotation.NonNull; import android.credentials.GetCredentialRequest; import android.credentials.ui.UserSelectionDialogResult; -import android.os.IBinder; import android.util.Slog; import com.android.server.credentials.ProviderSession; @@ -47,13 +49,24 @@ public class RequestSessionMetric { // As emits occur in sequential order, increment this counter and utilize protected int mSequenceCounter = 0; - protected final InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric(); + protected final InitialPhaseMetric mInitialPhaseMetric; protected final ChosenProviderFinalPhaseMetric - mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric(); + mChosenProviderFinalPhaseMetric; // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4) protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>(); + // Specific aggregate candidate provider metric for the provider this session handles + @NonNull + protected final CandidateAggregateMetric mCandidateAggregateMetric; + // Since track two is shared, this allows provider sessions to capture a metric-specific + // session token for the flow where the provider is known + private final int mSessionIdTrackTwo; - public RequestSessionMetric() { + public RequestSessionMetric(int sessionIdTrackOne, int sessionIdTrackTwo) { + mSessionIdTrackTwo = sessionIdTrackTwo; + mInitialPhaseMetric = new InitialPhaseMetric(sessionIdTrackOne); + mCandidateAggregateMetric = new CandidateAggregateMetric(sessionIdTrackOne); + mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric( + sessionIdTrackOne, sessionIdTrackTwo); } /** @@ -75,18 +88,25 @@ public class RequestSessionMetric { } /** + * @return the aggregate candidate phase metrics associated with the request session + */ + public CandidateAggregateMetric getCandidateAggregateMetric() { + return mCandidateAggregateMetric; + } + + /** * Upon starting the service, this fills the initial phase metric properly. * * @param timestampStarted the timestamp the service begins at - * @param mRequestId the IBinder used to retrieve a unique id * @param mCallingUid the calling process's uid * @param metricCode typically pulled from {@link ApiName} + * @param callingAppFlowUniqueInt the unique integer used as the session id for the calling app + * known flow */ - public void collectInitialPhaseMetricInfo(long timestampStarted, IBinder mRequestId, + public void collectInitialPhaseMetricInfo(long timestampStarted, int mCallingUid, int metricCode) { try { mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted); - mInitialPhaseMetric.setSessionId(mRequestId.hashCode()); mInitialPhaseMetric.setCallerUid(mCallingUid); mInitialPhaseMetric.setApiName(metricCode); } catch (Exception e) { @@ -168,11 +188,9 @@ public class RequestSessionMetric { Map<String, Integer> uniqueRequestCounts = new LinkedHashMap<>(); try { request.getCredentialOptions().forEach(option -> { - String optionKey = generateMetricKey(option.getType(), DELTA_CUT); - if (!uniqueRequestCounts.containsKey(optionKey)) { - uniqueRequestCounts.put(optionKey, 0); - } - uniqueRequestCounts.put(optionKey, uniqueRequestCounts.get(optionKey) + 1); + String optionKey = generateMetricKey(option.getType(), DELTA_RESPONSES_CUT); + uniqueRequestCounts.put(optionKey, uniqueRequestCounts.getOrDefault(optionKey, + 0) + 1); }); } catch (Exception e) { Slog.i(TAG, "Unexpected error during get request metric logging: " + e); @@ -207,7 +225,6 @@ public class RequestSessionMetric { CandidatePhaseMetric selectedProviderPhaseMetric) { try { CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric(); - browsingPhaseMetric.setSessionId(mInitialPhaseMetric.getSessionId()); browsingPhaseMetric.setEntryEnum( EntryEnum.getMetricCodeFromString(selection.getEntryKey())); browsingPhaseMetric.setProviderUid(selectedProviderPhaseMetric.getCandidateUid()); @@ -218,7 +235,7 @@ public class RequestSessionMetric { } /** - * Updates the final phase metric with the designated bit + * Updates the final phase metric with the designated bit. * * @param exceptionBitFinalPhase represents if the final phase provider had an exception */ @@ -231,6 +248,21 @@ public class RequestSessionMetric { } /** + * This allows collecting the framework exception string for the final phase metric. + * NOTE that this exception will be cut for space optimizations. + * + * @param exception the framework exception that is being recorded + */ + public void collectFrameworkException(String exception) { + try { + mChosenProviderFinalPhaseMetric.setFrameworkException( + generateMetricKey(exception, DELTA_EXCEPTION_CUT)); + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during metric logging: " + e); + } + } + + /** * Allows encapsulating the overall final phase metric status from the chosen and final * provider. * @@ -260,7 +292,6 @@ public class RequestSessionMetric { */ public void collectChosenMetricViaCandidateTransfer(CandidatePhaseMetric candidatePhaseMetric) { try { - mChosenProviderFinalPhaseMetric.setSessionId(candidatePhaseMetric.getSessionId()); mChosenProviderFinalPhaseMetric.setChosenUid(candidatePhaseMetric.getCandidateUid()); mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds( @@ -284,7 +315,7 @@ public class RequestSessionMetric { * In the final phase, this helps log use cases that were either pure failures or user * canceled. It's expected that {@link #collectFinalPhaseProviderMetricStatus(boolean, * ProviderStatusForMetrics) collectFinalPhaseProviderMetricStatus} is called prior to this. - * Otherwise, the logging will miss required bits + * Otherwise, the logging will miss required bits. * * @param isUserCanceledError a boolean indicating if the error was due to user cancelling */ @@ -318,6 +349,20 @@ public class RequestSessionMetric { } /** + * Handles aggregate candidate phase metric emits in the RequestSession context, after the + * candidate phase completes. + * + * @param providers a map with known providers and their held metric objects + */ + public void logCandidateAggregateMetrics(Map<String, ProviderSession> providers) { + try { + mCandidateAggregateMetric.collectAverages(providers); + } catch (Exception e) { + Slog.i(TAG, "Unexpected error during aggregate candidate logging " + e); + } + } + + /** * Handles the final logging for RequestSession context for the final phase. * * @param apiStatus the final status of the api being called @@ -327,9 +372,15 @@ public class RequestSessionMetric { logApiCalledFinalPhase(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric, apiStatus, ++mSequenceCounter); + logApiCalledNoUidFinal(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric, + apiStatus, + ++mSequenceCounter); } catch (Exception e) { Slog.i(TAG, "Unexpected error during final metric emit: " + e); } } + public int getSessionIdTrackTwo() { + return mSessionIdTrackTwo; + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index acfea85d60a2..5d3b91368dcb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -769,7 +769,7 @@ public final class BroadcastQueueModernImplTest { BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); - queue.setPrioritizeEarliest(true); + queue.addPrioritizeEarliestRequest(); long timeCounter = 100; enqueueOrReplaceBroadcast(queue, @@ -814,6 +814,28 @@ public final class BroadcastQueueModernImplTest { queue.makeActiveNextPending(); assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE, queue.getActive().intent.getAction()); + + + queue.removePrioritizeEarliestRequest(); + + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED) + .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++); + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), + 0, timeCounter++); + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++); + + // Once the request to prioritize earliest is removed, we should expect broadcasts + // to be dispatched in the order of foreground, normal and then offload. + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction()); } /** diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 7be1d7cde27f..3a8d2c92eaff 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -1890,6 +1890,36 @@ public class BroadcastQueueTest { assertTrue(mQueue.isBeyondBarrierLocked(afterSecond)); } + @Test + public void testWaitForBroadcastDispatch() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); + + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); + assertTrue(mQueue.isDispatchedLocked(timeTick)); + + final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(timezone, callerApp, + List.of(makeRegisteredReceiver(receiverApp)))); + + assertTrue(mQueue.isDispatchedLocked(timeTick)); + assertFalse(mQueue.isDispatchedLocked(timezone)); + + enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, + List.of(makeRegisteredReceiver(receiverApp)))); + + assertFalse(mQueue.isDispatchedLocked(timeTick)); + assertFalse(mQueue.isDispatchedLocked(timezone)); + + mLooper.release(); + + mQueue.waitForDispatched(timeTick, LOG_WRITER_INFO); + assertTrue(mQueue.isDispatchedLocked(timeTick)); + + mQueue.waitForDispatched(timezone, LOG_WRITER_INFO); + assertTrue(mQueue.isDispatchedLocked(timezone)); + } + /** * Verify that we OOM adjust for manifest receivers. */ diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index f8955edab1d7..3d0163d84929 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -172,12 +172,12 @@ public class WallpaperManagerServiceTests { sImageWallpaperComponentName = ComponentName.unflattenFromString( sContext.getResources().getString(R.string.image_wallpaper_component)); // Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper. - sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext); + sDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(sContext); if (sDefaultWallpaperComponent == null) { sDefaultWallpaperComponent = sImageWallpaperComponentName; doReturn(sImageWallpaperComponentName).when(() -> - WallpaperManager.getDefaultWallpaperComponent(any())); + WallpaperManager.getCmfDefaultWallpaperComponent(any())); } else { sContext.addMockService(sDefaultWallpaperComponent, sWallpaperService); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index 11e4120e77e6..5f2db795f8bc 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -208,8 +208,8 @@ public class MagnificationControllerTest { mMockConnection = new MockWindowMagnificationConnection(true); mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mMagnificationController = new MagnificationController(mService, globalLock, mContext, - mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider); + mMagnificationController = spy(new MagnificationController(mService, globalLock, mContext, + mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider)); mMagnificationController.setMagnificationCapabilities( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); @@ -774,15 +774,21 @@ public class MagnificationControllerTest { verify(mWindowMagnificationManager, times(2)).removeMagnificationButton(eq(TEST_DISPLAY)); } + @Test public void activateWindowMagnification_triggerCallback() throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + verify(mMagnificationController).onWindowMagnificationActivationState( + eq(TEST_DISPLAY), eq(true)); + } @Test - public void onWindowMagnificationActivationState_windowActivated_logWindowDuration() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true); - - spyController.onWindowMagnificationActivationState(TEST_DISPLAY, false); + public void deactivateWindowMagnification_windowActivated_triggerCallbackAndLogUsage() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, /* clear= */ true); - verify(spyController).logMagnificationUsageState( - eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), anyLong()); + verify(mMagnificationController).onWindowMagnificationActivationState( + eq(TEST_DISPLAY), eq(false)); + verify(mMagnificationController).logMagnificationUsageState( + eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), anyLong(), eq(DEFAULT_SCALE)); } @Test @@ -906,15 +912,22 @@ public class MagnificationControllerTest { assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, lastActivatedMode); } - @Test - public void onFullScreenMagnificationActivationState_fullScreenEnabled_logFullScreenDuration() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true); + @Test public void activateFullScreenMagnification_triggerCallback() throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + verify(mMagnificationController).onFullScreenMagnificationActivationState( + eq(TEST_DISPLAY), eq(true)); + } - spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, false); + @Test + public void deactivateFullScreenMagnification_fullScreenEnabled_triggerCallbackAndLogUsage() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + mScreenMagnificationController.reset(TEST_DISPLAY, /* animate= */ false); - verify(spyController).logMagnificationUsageState( - eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN), anyLong()); + verify(mMagnificationController).onFullScreenMagnificationActivationState( + eq(TEST_DISPLAY), eq(false)); + verify(mMagnificationController).logMagnificationUsageState( + eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN), anyLong(), eq(DEFAULT_SCALE)); } @Test @@ -1106,48 +1119,44 @@ public class MagnificationControllerTest { @Test public void imeWindowStateShown_windowMagnifying_logWindowMode() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true); + mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true); - spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); - verify(spyController).logMagnificationModeWithIme( + verify(mMagnificationController).logMagnificationModeWithIme( eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)); } @Test public void imeWindowStateShown_fullScreenMagnifying_logFullScreenMode() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true); + mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true); - spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); - verify(spyController).logMagnificationModeWithIme( + verify(mMagnificationController).logMagnificationModeWithIme( eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)); } @Test public void imeWindowStateShown_noMagnifying_noLogAnyMode() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); - verify(spyController, never()).logMagnificationModeWithIme(anyInt()); + verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt()); } @Test public void imeWindowStateHidden_windowMagnifying_noLogAnyMode() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true); + mMagnificationController.onFullScreenMagnificationActivationState( + TEST_DISPLAY, true); - verify(spyController, never()).logMagnificationModeWithIme(anyInt()); + verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt()); } @Test public void imeWindowStateHidden_fullScreenMagnifying_noLogAnyMode() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true); + mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true); - verify(spyController, never()).logMagnificationModeWithIme(anyInt()); + verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java index 46974cf72381..37afc7f52f7e 100644 --- a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java @@ -217,7 +217,8 @@ public class UidObserverControllerTest { private void registerObserver(IUidObserver observer, int which, int cutpoint, String callingPackage, int callingUid) { when(observer.asBinder()).thenReturn((IBinder) observer); - mUidObserverController.register(observer, which, cutpoint, callingPackage, callingUid); + mUidObserverController.register(observer, which, cutpoint, callingPackage, callingUid, + /*uids*/null); Mockito.reset(observer); } diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java index 385c28ae7152..6a1674b7df8e 100644 --- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java +++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java @@ -38,7 +38,6 @@ import android.os.BatteryStatsInternal; import android.os.Process; import android.os.RemoteException; -import androidx.test.filters.FlakyTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.FakeLatencyTracker; @@ -92,12 +91,10 @@ public class SoundTriggerMiddlewareLoggingLatencyTest { } @Test - @FlakyTest(bugId = 275113847) public void testSetUpAndTearDown() { } @Test - @FlakyTest(bugId = 275113847) public void testOnPhraseRecognitionStartsLatencyTrackerWithSuccessfulPhraseIdTrigger() throws RemoteException { ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( @@ -114,7 +111,6 @@ public class SoundTriggerMiddlewareLoggingLatencyTest { } @Test - @FlakyTest(bugId = 275113847) public void testOnPhraseRecognitionRestartsActiveSession() throws RemoteException { ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( ISoundTriggerCallback.class); @@ -135,7 +131,6 @@ public class SoundTriggerMiddlewareLoggingLatencyTest { } @Test - @FlakyTest(bugId = 275113847) public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNonSuccessEvent() throws RemoteException { ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( @@ -153,7 +148,6 @@ public class SoundTriggerMiddlewareLoggingLatencyTest { } @Test - @FlakyTest(bugId = 275113847) public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNoKeyphraseId() throws RemoteException { ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 58bf184994e4..d3f68185a269 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -67,7 +67,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.pm.ActivityInfo; -import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.VirtualDisplay; @@ -515,12 +514,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_instrumentedProcessGetPermissionToSwitchTouchMode() { - // Disable global touch mode (config_perDisplayFocusEnabled set to true) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(true).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); + // Enable global touch mode + mWm.mPerDisplayFocusEnabled = true; // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); @@ -539,12 +534,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_nonInstrumentedProcessDontGetPermissionToSwitchTouchMode() { - // Disable global touch mode (config_perDisplayFocusEnabled set to true) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(true).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); + // Enable global touch mode + mWm.mPerDisplayFocusEnabled = true; // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); @@ -563,6 +554,9 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() { + // Disable global touch mode + mWm.mPerDisplayFocusEnabled = false; + // Create one extra display final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false); final VirtualDisplay virtualDisplayOwnTouchMode = @@ -570,17 +564,10 @@ public class WindowManagerServiceTests extends WindowTestsBase { final int numberOfDisplays = mWm.mRoot.mChildren.size(); assertThat(numberOfDisplays).isAtLeast(3); final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream() - .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0) - .count(); + .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0) + .count(); assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2); - // Enable global touch mode (config_perDisplayFocusEnabled set to false) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(false).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); int callingPid = Binder.getCallingPid(); @@ -598,18 +585,14 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() { + // Enable global touch mode + mWm.mPerDisplayFocusEnabled = true; + // Create one extra display final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false); final int numberOfDisplays = mWm.mRoot.mChildren.size(); assertThat(numberOfDisplays).isAtLeast(2); - // Disable global touch mode (config_perDisplayFocusEnabled set to true) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(true).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); int callingPid = Binder.getCallingPid(); @@ -628,18 +611,14 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() { + // Disable global touch mode + mWm.mPerDisplayFocusEnabled = false; + // Create one extra display final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true); final int numberOfDisplays = mWm.mRoot.mChildren.size(); assertThat(numberOfDisplays).isAtLeast(2); - // Enable global touch mode (config_perDisplayFocusEnabled set to false) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(false).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); int callingPid = Binder.getCallingPid(); @@ -667,19 +646,14 @@ public class WindowManagerServiceTests extends WindowTestsBase { } private void testSetInTouchModeOnAllDisplays(boolean perDisplayFocusEnabled) { + // Set global touch mode with the value passed as argument. + mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled; + // Create a couple of extra displays. // setInTouchModeOnAllDisplays should ignore the ownFocus setting. final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false); final VirtualDisplay virtualDisplayOwnFocus = createVirtualDisplay(/* ownFocus= */ true); - // Enable or disable global touch mode (config_perDisplayFocusEnabled setting). - // setInTouchModeOnAllDisplays should ignore this value. - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(perDisplayFocusEnabled).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index a4cad5e24dc1..863523f826b2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -197,6 +197,8 @@ class WindowTestsBase extends SystemServiceTestsBase { */ private static boolean sOverridesCheckedTestDisplay; + private boolean mOriginalPerDisplayFocusEnabled; + @BeforeClass public static void setUpOnceBase() { AttributeCache.init(getInstrumentation().getTargetContext()); @@ -208,6 +210,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mSupervisor = mAtm.mTaskSupervisor; mRootWindowContainer = mAtm.mRootWindowContainer; mWm = mSystemServicesTestRule.getWindowManagerService(); + mOriginalPerDisplayFocusEnabled = mWm.mPerDisplayFocusEnabled; SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock); mDefaultDisplay = mWm.mRoot.getDefaultDisplay(); @@ -279,6 +282,7 @@ class WindowTestsBase extends SystemServiceTestsBase { if (mUseFakeSettingsProvider) { FakeSettingsProvider.clearSettingsProvider(); } + mWm.mPerDisplayFocusEnabled = mOriginalPerDisplayFocusEnabled; } /** diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index fb46ff991d23..2021ac7c4cd5 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -278,6 +278,11 @@ public class SatelliteManager { */ public static final int SATELLITE_REQUEST_IN_PROGRESS = 21; + /** + * Satellite modem is currently busy due to which current request cannot be processed. + */ + public static final int SATELLITE_MODEM_BUSY = 22; + /** @hide */ @IntDef(prefix = {"SATELLITE_"}, value = { SATELLITE_ERROR_NONE, @@ -301,7 +306,8 @@ public class SatelliteManager { SATELLITE_NOT_REACHABLE, SATELLITE_NOT_AUTHORIZED, SATELLITE_NOT_SUPPORTED, - SATELLITE_REQUEST_IN_PROGRESS + SATELLITE_REQUEST_IN_PROGRESS, + SATELLITE_MODEM_BUSY }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteError {} |