diff options
44 files changed, 1114 insertions, 758 deletions
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 99ef315676c4..e9fbf6b178fb 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)") 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/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 70c42d8f3b8a..c0106ab0bc11 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 A Drawable object for the requested wallpaper. * - * @return Returns a Drawable object that will draw the 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 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. + * + * @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()); } 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/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/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/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/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/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/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/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index b81339e85bcc..ba20a5e779fd 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -661,7 +661,7 @@ class CreateFlowUtils { passwordCount = createEntry.getPasswordCredentialCount(), passkeyCount = createEntry.getPublicKeyCredentialCount(), totalCredentialCount = createEntry.getTotalCredentialCount(), - lastUsedTime = createEntry.lastUsedTime, + lastUsedTime = createEntry.lastUsedTime ?: Instant.MIN, footerDescription = createEntry.description?.toString() )) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index fe1ce1b60413..8f3f3c87d93c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -72,7 +72,7 @@ class CreateOptionInfo( val passwordCount: Int?, val passkeyCount: Int?, val totalCredentialCount: Int?, - val lastUsedTime: Instant?, + val lastUsedTime: Instant, val footerDescription: String?, ) : BaseEntry( providerId, 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/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml index 0cd062383570..8d35b237f31b 100644 --- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml +++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml @@ -25,4 +25,5 @@ android:scaleType="fitCenter" android:tint="?android:attr/textColorPrimary" android:src="@drawable/controls_icon" + android:elevation="@dimen/dream_overlay_bottom_affordance_elevation" android:contentDescription="@string/quick_controls_title" /> 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/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index bf0b8a660432..7488e9fbbe3e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1642,6 +1642,7 @@ <dimen name="dream_overlay_bottom_affordance_width">64dp</dimen> <dimen name="dream_overlay_bottom_affordance_radius">32dp</dimen> <dimen name="dream_overlay_bottom_affordance_padding">14dp</dimen> + <dimen name="dream_overlay_bottom_affordance_elevation">4dp</dimen> <dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen> <dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen> <dimen name="dream_overlay_complication_preview_text_size">36sp</dimen> 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/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/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/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/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/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/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/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/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/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8ce1829a4b16..5d3bb31bc31f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7511,7 +7511,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 +7543,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)) { @@ -18613,7 +18671,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 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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 6f640700949e..84d3a216ac91 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -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/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/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..409806a21679 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -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..64438e338e5b 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); } } 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/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; } /** |