diff options
313 files changed, 6228 insertions, 2380 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java index ce381b6699ea..e08200b055d8 100644 --- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java +++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java @@ -22,7 +22,6 @@ import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener; import android.app.AppOpsManager; import android.app.AppOpsManager.PackageOps; import android.app.IActivityManager; -import android.app.UidObserver; import android.app.usage.UsageStatsManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -54,6 +53,7 @@ import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.util.ArrayUtils; import com.android.internal.util.StatLogger; +import com.android.modules.expresslog.Counter; import com.android.server.AppStateTrackerProto.ExemptedPackage; import com.android.server.AppStateTrackerProto.RunAnyInBackgroundRestrictedPackages; import com.android.server.usage.AppStandbyInternal; @@ -79,6 +79,9 @@ import java.util.Set; public class AppStateTrackerImpl implements AppStateTracker { private static final boolean DEBUG = false; + private static final String APP_RESTRICTION_COUNTER_METRIC_ID = + "battery.value_app_background_restricted"; + private final Object mLock = new Object(); private final Context mContext; @@ -748,6 +751,9 @@ public class AppStateTrackerImpl implements AppStateTracker { } catch (RemoteException e) { // Shouldn't happen } + if (restricted) { + Counter.logIncrementWithUid(APP_RESTRICTION_COUNTER_METRIC_ID, uid); + } synchronized (mLock) { if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) { mHandler.notifyRunAnyAppOpsChanged(uid, packageName); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index c5405171edd0..0a7bffc786cc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -49,8 +49,10 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.BitUtils; +import com.android.modules.expresslog.Histogram; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.AppSchedulingModuleThread; import com.android.server.IoThread; import com.android.server.job.JobSchedulerInternal.JobStorePersistStats; import com.android.server.job.controllers.JobStatus; @@ -94,6 +96,7 @@ public final class JobStore { /** Threshold to adjust how often we want to write to the db. */ private static final long JOB_PERSIST_DELAY = 2000L; + private static final long SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS = 30 * 60_000L; @VisibleForTesting static final String JOB_FILE_SPLIT_PREFIX = "jobs_"; private static final int ALL_UIDS = -1; @@ -131,6 +134,30 @@ public final class JobStore { private JobStorePersistStats mPersistInfo = new JobStorePersistStats(); + /** + * Separately updated value of the JobSet size to avoid recalculating it frequently for logging + * purposes. Continue to use {@link JobSet#size()} for the up-to-date and accurate value. + */ + private int mCurrentJobSetSize = 0; + private int mScheduledJob30MinHighWaterMark = 0; + private static final Histogram sScheduledJob30MinHighWaterMarkLogger = new Histogram( + "job_scheduler.value_hist_scheduled_job_30_min_high_water_mark", + new Histogram.ScaledRangeOptions(15, 1, 99, 1.5f)); + private final Runnable mScheduledJobHighWaterMarkLoggingRunnable = new Runnable() { + @Override + public void run() { + AppSchedulingModuleThread.getHandler().removeCallbacks(this); + synchronized (mLock) { + sScheduledJob30MinHighWaterMarkLogger.logSample(mScheduledJob30MinHighWaterMark); + mScheduledJob30MinHighWaterMark = mJobSet.size(); + } + // The count doesn't need to be logged at exact times. Logging based on system uptime + // should be fine. + AppSchedulingModuleThread.getHandler() + .postDelayed(this, SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS); + } + }; + /** Used by the {@link JobSchedulerService} to instantiate the JobStore. */ static JobStore get(JobSchedulerService jobManagerService) { synchronized (sSingletonLock) { @@ -183,6 +210,9 @@ public final class JobStore { mXmlTimestamp = mJobsFile.exists() ? mJobsFile.getLastModifiedTime() : mJobFileDirectory.lastModified(); mRtcGood = (sSystemClock.millis() > mXmlTimestamp); + + AppSchedulingModuleThread.getHandler().postDelayed( + mScheduledJobHighWaterMarkLoggingRunnable, SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS); } private void init() { @@ -252,7 +282,10 @@ public final class JobStore { * @param jobStatus Job to add. */ public void add(JobStatus jobStatus) { - mJobSet.add(jobStatus); + if (mJobSet.add(jobStatus)) { + mCurrentJobSetSize++; + maybeUpdateHighWaterMark(); + } if (jobStatus.isPersisted()) { mPendingJobWriteUids.put(jobStatus.getUid(), true); maybeWriteStatusToDiskAsync(); @@ -267,7 +300,10 @@ public final class JobStore { */ @VisibleForTesting public void addForTesting(JobStatus jobStatus) { - mJobSet.add(jobStatus); + if (mJobSet.add(jobStatus)) { + mCurrentJobSetSize++; + maybeUpdateHighWaterMark(); + } if (jobStatus.isPersisted()) { mPendingJobWriteUids.put(jobStatus.getUid(), true); } @@ -303,6 +339,7 @@ public final class JobStore { } return false; } + mCurrentJobSetSize--; if (removeFromPersisted && jobStatus.isPersisted()) { mPendingJobWriteUids.put(jobStatus.getUid(), true); maybeWriteStatusToDiskAsync(); @@ -315,7 +352,9 @@ public final class JobStore { */ @VisibleForTesting public void removeForTesting(JobStatus jobStatus) { - mJobSet.remove(jobStatus); + if (mJobSet.remove(jobStatus)) { + mCurrentJobSetSize--; + } if (jobStatus.isPersisted()) { mPendingJobWriteUids.put(jobStatus.getUid(), true); } @@ -327,6 +366,7 @@ public final class JobStore { */ public void removeJobsOfUnlistedUsers(int[] keepUserIds) { mJobSet.removeJobsOfUnlistedUsers(keepUserIds); + mCurrentJobSetSize = mJobSet.size(); } /** Note a change in the specified JobStatus that necessitates writing job state to disk. */ @@ -342,6 +382,7 @@ public final class JobStore { public void clear() { mJobSet.clear(); mPendingJobWriteUids.put(ALL_UIDS, true); + mCurrentJobSetSize = 0; maybeWriteStatusToDiskAsync(); } @@ -352,6 +393,7 @@ public final class JobStore { public void clearForTesting() { mJobSet.clear(); mPendingJobWriteUids.put(ALL_UIDS, true); + mCurrentJobSetSize = 0; } void setUseSplitFiles(boolean useSplitFiles) { @@ -442,6 +484,12 @@ public final class JobStore { mJobSet.forEachJobForSourceUid(sourceUid, functor); } + private void maybeUpdateHighWaterMark() { + if (mScheduledJob30MinHighWaterMark < mCurrentJobSetSize) { + mScheduledJob30MinHighWaterMark = mCurrentJobSetSize; + } + } + /** Version of the db schema. */ private static final int JOBS_FILE_VERSION = 1; /** @@ -1125,6 +1173,12 @@ public final class JobStore { if (needFileMigration) { migrateJobFilesAsync(); } + + // Log the count immediately after loading from boot. + mCurrentJobSetSize = numJobs; + mScheduledJob30MinHighWaterMark = mCurrentJobSetSize; + mScheduledJobHighWaterMarkLoggingRunnable.run(); + if (mCompletionLatch != null) { mCompletionLatch.countDown(); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 934622305a05..e42e52619320 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2367,6 +2367,7 @@ package android.os { method public static boolean isApp(int); field public static final int MIN_SECONDARY_USER_ID = 10; // 0xa field public static final int USER_ALL = -1; // 0xffffffff + field public static final int USER_CURRENT = -2; // 0xfffffffe field public static final int USER_NULL = -10000; // 0xffffd8f0 field public static final int USER_SYSTEM = 0; // 0x0 } @@ -2375,10 +2376,12 @@ package android.os { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createProfileForUser(@Nullable String, @NonNull String, int, int, @Nullable String[]); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getAliveUsers(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getBootUser(); method public int getMainDisplayIdAssignedToUser(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType(); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(); method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index e2ef00525902..b5ee895a5a01 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4433,6 +4433,21 @@ public class ActivityManager { } /** + * Similar to {@link #forceStopPackageAsUser(String, int)} but will also stop the package even + * when the user is in the stopping state. + * + * @hide + */ + @RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES) + public void forceStopPackageAsUserEvenWhenStopping(String packageName, @UserIdInt int userId) { + try { + getService().forceStopPackageEvenWhenStopping(packageName, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets the current locales of the device. Calling app must have the permission * {@code android.permission.CHANGE_CONFIGURATION} and * {@code android.permission.WRITE_SETTINGS}. diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 9e59ee496de1..0e5cbe228c1b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -60,13 +60,16 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import android.util.LongSparseArray; import android.util.LongSparseLongArray; import android.util.Pools; +import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -179,6 +182,8 @@ import java.util.function.Supplier; */ @SystemService(Context.APP_OPS_SERVICE) public class AppOpsManager { + private static final String LOG_TAG = "AppOpsManager"; + /** * This is a subtle behavior change to {@link #startWatchingMode}. * @@ -7517,6 +7522,7 @@ public class AppOpsManager { */ @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setUidMode(int code, int uid, @Mode int mode) { + logAnySeriousModeChanges(code, uid, null, mode); try { mService.setUidMode(code, uid, mode); } catch (RemoteException e) { @@ -7537,6 +7543,7 @@ public class AppOpsManager { @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setUidMode(@NonNull String appOp, int uid, @Mode int mode) { + logAnySeriousModeChanges(strOpToOp(appOp), uid, null, mode); try { mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode); } catch (RemoteException e) { @@ -7572,11 +7579,32 @@ public class AppOpsManager { } } + private void logAnySeriousModeChanges(int code, int uid, String packageName, @Mode int mode) { + // TODO (b/280869337): Remove this once we have the required data. + if (code != OP_RUN_ANY_IN_BACKGROUND || mode == MODE_ALLOWED) { + return; + } + final StringBuilder log = new StringBuilder("Attempt to change RUN_ANY_IN_BACKGROUND to ") + .append(modeToName(mode)) + .append(" for uid: ") + .append(UserHandle.formatUid(uid)) + .append(" package: ") + .append(packageName) + .append(" by: ") + .append(mContext.getOpPackageName()); + if (Process.myUid() == Process.SYSTEM_UID) { + Slog.wtfStack(LOG_TAG, log.toString()); + } else { + Log.w(LOG_TAG, log.toString()); + } + } + /** @hide */ @UnsupportedAppUsage @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setMode(int code, int uid, String packageName, @Mode int mode) { + logAnySeriousModeChanges(code, uid, packageName, mode); try { mService.setMode(code, uid, packageName, mode); } catch (RemoteException e) { @@ -7599,6 +7627,7 @@ public class AppOpsManager { @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setMode(@NonNull String op, int uid, @Nullable String packageName, @Mode int mode) { + logAnySeriousModeChanges(strOpToOp(op), uid, packageName, mode); try { mService.setMode(strOpToOp(op), uid, packageName, mode); } catch (RemoteException e) { diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 99ef315676c4..e15e08fc0ef0 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -102,6 +102,43 @@ interface IActivityManager { void registerUidObserver(in IUidObserver observer, int which, int cutpoint, String callingPackage); void unregisterUidObserver(in IUidObserver observer); + + /** + * Registers a UidObserver with a uid filter. + * + * @param observer The UidObserver implementation to register. + * @param which A bitmask of events to observe. See ActivityManager.UID_OBSERVER_*. + * @param cutpoint The cutpoint for onUidStateChanged events. When the state crosses this + * threshold in either direction, onUidStateChanged will be called. + * @param callingPackage The name of the calling package. + * @param uids A list of uids to watch. If all uids are to be watched, use + * registerUidObserver instead. + * @throws RemoteException + * @return Returns A binder token identifying the UidObserver registration. + */ + IBinder registerUidObserverForUids(in IUidObserver observer, int which, int cutpoint, + String callingPackage, in int[] uids); + + /** + * Adds a uid to the list of uids that a UidObserver will receive updates about. + * + * @param observerToken The binder token identifying the UidObserver registration. + * @param callingPackage The name of the calling package. + * @param uid The uid to watch. + * @throws RemoteException + */ + void addUidToObserver(in IBinder observerToken, String callingPackage, int uid); + + /** + * Removes a uid from the list of uids that a UidObserver will receive updates about. + * + * @param observerToken The binder token identifying the UidObserver registration. + * @param callingPackage The name of the calling package. + * @param uid The uid to stop watching. + * @throws RemoteException + */ + void removeUidFromObserver(in IBinder observerToken, String callingPackage, int uid); + boolean isUidActive(int uid, String callingPackage); @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)") @@ -299,6 +336,7 @@ interface IActivityManager { boolean registerForegroundServiceObserver(in IForegroundServiceObserver callback); @UnsupportedAppUsage void forceStopPackage(in String packageName, int userId); + void forceStopPackageEvenWhenStopping(in String packageName, int userId); boolean killPids(in int[] pids, in String reason, boolean secure); @UnsupportedAppUsage List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags); diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index 2b1558937d21..4d308d90ce2d 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -259,6 +259,14 @@ interface IWallpaperManager { boolean lockScreenWallpaperExists(); /** + * Return true if there is a static wallpaper on the specified screen. With which=FLAG_LOCK, + * always return false if the lock screen doesn't run its own wallpaper engine. + * + * @hide + */ + boolean isStaticWallpaper(int which); + + /** * Temporary method for project b/197814683. * Return true if the lockscreen wallpaper always uses a WallpaperService, not a static image. * @hide diff --git a/core/java/android/app/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/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 27270d9f378f..d6592d572726 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -765,10 +765,6 @@ public class LauncherApps { @Nullable public PendingIntent getMainActivityLaunchIntent(@NonNull ComponentName component, @Nullable Bundle startActivityOptions, @NonNull UserHandle user) { - if (mContext.checkSelfPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) - != PackageManager.PERMISSION_GRANTED) { - Log.w(TAG, "Only allowed for recents."); - } logErrorForInvalidProfileAccess(user); if (DEBUG) { Log.i(TAG, "GetMainActivityLaunchIntent " + component + " " + user); diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index a89d17bb2343..50be5c466223 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -91,7 +91,7 @@ public class PackageInfo implements Parcelable { /** * The version name of this package, as specified by the <manifest> * tag's {@link android.R.styleable#AndroidManifest_versionName versionName} - * attribute. + * attribute, or null if there was none. */ public String versionName; 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/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java index 9ebb0585afd0..3fc3be58f022 100644 --- a/core/java/android/credentials/ui/RequestInfo.java +++ b/core/java/android/credentials/ui/RequestInfo.java @@ -52,6 +52,12 @@ public final class RequestInfo implements Parcelable { @NonNull public static final String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED"; /** Type value for a getCredential request. */ @NonNull public static final String TYPE_GET = "android.credentials.ui.TYPE_GET"; + /** Type value for a getCredential request that utilizes the credential registry. + * + * @hide + **/ + @NonNull public static final String TYPE_GET_VIA_REGISTRY = + "android.credentials.ui.TYPE_GET_VIA_REGISTRY"; /** Type value for a createCredential request. */ @NonNull public static final String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE"; diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java index 069c264313e7..dcf1a47c3f70 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -1136,7 +1136,10 @@ public final class SQLiteConnectionPool implements Closeable { Printer indentedPrinter = PrefixPrinter.create(printer, " "); synchronized (mLock) { if (directories != null) { - directories.add(new File(mConfiguration.path).getParent()); + String parent = new File(mConfiguration.path).getParent(); + if (parent != null) { + directories.add(parent); + } } boolean isCompatibilityWalEnabled = mConfiguration.isLegacyCompatibilityWalEnabled(); printer.println("Connection pool for " + mConfiguration.path + ":"); diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index 6cd32ff1e8e5..17bbe1459d03 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -233,7 +233,7 @@ public class InputSettings { */ public static boolean useTouchpadNaturalScrolling(@NonNull Context context) { return Settings.System.getIntForUser(context.getContentResolver(), - Settings.System.TOUCHPAD_NATURAL_SCROLLING, 0, UserHandle.USER_CURRENT) == 1; + Settings.System.TOUCHPAD_NATURAL_SCROLLING, 1, UserHandle.USER_CURRENT) == 1; } /** diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java index 22f59021bca1..f9a2dbb1bdb4 100644 --- a/core/java/android/hardware/radio/TunerCallbackAdapter.java +++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java @@ -144,6 +144,9 @@ final class TunerCallbackAdapter extends ITunerCallback.Stub { int errorCode; switch (status) { + case RadioTuner.TUNER_RESULT_CANCELED: + errorCode = RadioTuner.ERROR_CANCELLED; + break; case RadioManager.STATUS_PERMISSION_DENIED: case RadioManager.STATUS_DEAD_OBJECT: errorCode = RadioTuner.ERROR_SERVER_DIED; @@ -152,10 +155,16 @@ final class TunerCallbackAdapter extends ITunerCallback.Stub { case RadioManager.STATUS_NO_INIT: case RadioManager.STATUS_BAD_VALUE: case RadioManager.STATUS_INVALID_OPERATION: + case RadioTuner.TUNER_RESULT_INTERNAL_ERROR: + case RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS: + case RadioTuner.TUNER_RESULT_INVALID_STATE: + case RadioTuner.TUNER_RESULT_NOT_SUPPORTED: + case RadioTuner.TUNER_RESULT_UNKNOWN_ERROR: Log.i(TAG, "Got an error with no mapping to the legacy API (" + status + "), doing a best-effort conversion to ERROR_SCAN_TIMEOUT"); // fall through case RadioManager.STATUS_TIMED_OUT: + case RadioTuner.TUNER_RESULT_TIMEOUT: default: errorCode = RadioTuner.ERROR_SCAN_TIMEOUT; } diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 301b412e6ce2..bfff4dbdd627 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -2306,7 +2306,7 @@ public class SoundTrigger { Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper(); try { return new SoundTriggerModule(getService(), moduleId, listener, looper, - middlemanIdentity, originatorIdentity); + middlemanIdentity, originatorIdentity, false); } catch (Exception e) { Log.e(TAG, "", e); return null; diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java index 48d4ea40fecd..8813a17c504b 100644 --- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java +++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java @@ -83,7 +83,8 @@ public class SoundTriggerModule { */ public SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service, int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper, - @NonNull Identity middlemanIdentity, @NonNull Identity originatorIdentity) { + @NonNull Identity middlemanIdentity, @NonNull Identity originatorIdentity, + boolean isTrusted) { mId = moduleId; mEventHandlerDelegate = new EventHandlerDelegate(listener, looper); @@ -91,7 +92,8 @@ public class SoundTriggerModule { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { mService = service.attachAsMiddleman(moduleId, middlemanIdentity, originatorIdentity, - mEventHandlerDelegate); + mEventHandlerDelegate, + isTrusted); } mService.asBinder().linkToDeath(mEventHandlerDelegate, 0); } catch (RemoteException e) { diff --git a/core/java/android/nfc/NfcAntennaInfo.java b/core/java/android/nfc/NfcAntennaInfo.java index d54fcd2ed5b3..b002ca21e8e3 100644 --- a/core/java/android/nfc/NfcAntennaInfo.java +++ b/core/java/android/nfc/NfcAntennaInfo.java @@ -85,8 +85,8 @@ public final class NfcAntennaInfo implements Parcelable { this.mDeviceHeight = in.readInt(); this.mDeviceFoldable = in.readByte() != 0; this.mAvailableNfcAntennas = new ArrayList<>(); - in.readParcelableList(this.mAvailableNfcAntennas, - AvailableNfcAntenna.class.getClassLoader()); + in.readTypedList(this.mAvailableNfcAntennas, + AvailableNfcAntenna.CREATOR); } public static final @NonNull Parcelable.Creator<NfcAntennaInfo> CREATOR = diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 2c31e32f2ef8..94971b8654ee 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -107,42 +107,22 @@ public class GraphicsEnvironment { private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2; private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF = 3; - // System properties related to ANGLE and legacy GLES graphics drivers. - private static final String PROPERTY_EGL_SYSTEM_DRIVER = "ro.hardware.egl"; - // TODO (b/224558229): Properly add this to the list of system properties for a device: - private static final String PROPERTY_EGL_LEGACY_DRIVER = "ro.hardware.egl_legacy"; - // Values for ANGLE_GL_DRIVER_ALL_ANGLE private static final int ANGLE_GL_DRIVER_ALL_ANGLE_ON = 1; private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0; - private static final int ANGLE_GL_DRIVER_ALL_LEGACY = -1; // Values for ANGLE_GL_DRIVER_SELECTION_VALUES private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default"; private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle"; - private static final String ANGLE_GL_DRIVER_CHOICE_LEGACY = "legacy"; - // The following value is a deprecated choice for "legacy" private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native"; - // Values returned by getDriverForPackage() and getDefaultDriverToUse() (avoid returning - // strings for performance reasons) - private static final int ANGLE_GL_DRIVER_TO_USE_LEGACY = 0; - private static final int ANGLE_GL_DRIVER_TO_USE_ANGLE = 1; - private ClassLoader mClassLoader; private String mLibrarySearchPaths; private String mLibraryPermittedPaths; private GameManager mGameManager; - private boolean mAngleIsSystemDriver = false; - private boolean mNoLegacyDriver = false; - // When ANGLE is the system driver, this is the name of the legacy driver. - // - // TODO (b/224558229): This is temporarily set to a value that works for testing, until - // PROPERTY_EGL_LEGACY_DRIVER has been properly plumbed and this becomes broadly available. - private String mEglLegacyDriver = "mali"; - private int mAngleOptInIndex = -1; + private boolean mEnabledByGameMode = false; /** * Set up GraphicsEnvironment @@ -159,24 +139,6 @@ public class GraphicsEnvironment { setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData); Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); - // Determine if ANGLE is the system driver, as this will determine other logic - final String eglSystemDriver = SystemProperties.get(PROPERTY_EGL_SYSTEM_DRIVER); - Log.v(TAG, "GLES system driver is '" + eglSystemDriver + "'"); - mAngleIsSystemDriver = eglSystemDriver.equals(ANGLE_DRIVER_NAME); - if (mAngleIsSystemDriver) { - // Lookup the legacy driver, to send down to the EGL loader - final String eglLegacyDriver = SystemProperties.get(PROPERTY_EGL_LEGACY_DRIVER); - if (eglLegacyDriver.isEmpty()) { - mNoLegacyDriver = true; - // TBD/TODO: Do we need this?: - mEglLegacyDriver = eglSystemDriver; - } - } else { - // TBD/TODO: Do we need this?: - mEglLegacyDriver = eglSystemDriver; - } - Log.v(TAG, "Legacy GLES driver is '" + mEglLegacyDriver + "'"); - // Setup ANGLE and pass down ANGLE details to the C++ code Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle"); boolean useAngle = false; @@ -185,10 +147,6 @@ public class GraphicsEnvironment { useAngle = true; setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE, 0, packageName, getVulkanVersion(pm)); - } else if (mNoLegacyDriver) { - // TBD: The following should never happen--does it? - Log.e(TAG, "Unexpected problem with the ANGLE for use with: '" + packageName + "'"); - useAngle = true; } } Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); @@ -242,12 +200,10 @@ public class GraphicsEnvironment { private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) { if (TextUtils.isEmpty(packageName)) { Log.v(TAG, "No package name specified; use the system driver"); - return mAngleIsSystemDriver ? true : false; + return false; } - final int driverToUse = getDriverForPackage(context, coreSettings, packageName); - boolean yesOrNo = driverToUse == ANGLE_GL_DRIVER_TO_USE_ANGLE; - return yesOrNo; + return shouldUseAngleInternal(context, coreSettings, packageName); } private int getVulkanVersion(PackageManager pm) { @@ -455,43 +411,25 @@ public class GraphicsEnvironment { return ai; } - /** - * Return the appropriate "default" driver, unless overridden by isAngleEnabledByGameMode(). - */ - private int getDefaultDriverToUse(Context context, String packageName) { - if (mAngleIsSystemDriver || isAngleEnabledByGameMode(context, packageName)) { - return ANGLE_GL_DRIVER_TO_USE_ANGLE; - } else { - return ANGLE_GL_DRIVER_TO_USE_LEGACY; - } - } - /* * Determine which GLES "driver" should be used for the package, taking into account the * following factors (in priority order): * * 1) The semi-global switch (i.e. Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE; which is set by * the "angle_gl_driver_all_angle" setting; which forces a driver for all processes that - * start after the Java run time is up), if it forces a choice; otherwise ... + * start after the Java run time is up), if it forces a choice; * 2) The per-application switch (i.e. Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS and * Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES; which corresponds to the * “angle_gl_driver_selection_pkgs” and “angle_gl_driver_selection_values” settings); if it * forces a choice; - * - Workaround Note: ANGLE and Vulkan currently have issues with applications that use YUV - * target functionality. The ANGLE broadcast receiver code will apply a "deferlist" at - * the first boot of a newly-flashed device. However, there is a gap in time between - * when applications can start and when the deferlist is applied. For now, assume that - * if ANGLE is the system driver and Settings.Global.ANGLE_DEFERLIST is empty, that the - * deferlist has not yet been applied. In this case, select the Legacy driver. - * otherwise ... - * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; otherwise ... - * 4) The global switch (i.e. use the system driver, whether ANGLE or legacy; - * a.k.a. mAngleIsSystemDriver, which is set by the device’s “ro.hardware.egl” property) - * - * Factors 1 and 2 are decided by this method. Factors 3 and 4 are decided by - * getDefaultDriverToUse(). + * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; */ - private int getDriverForPackage(Context context, Bundle bundle, String packageName) { + private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) { + // Make sure we have a good package name + if (TextUtils.isEmpty(packageName)) { + return false; + } + // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE // should be forced on or off for "all appplications" final int allUseAngle; @@ -504,16 +442,7 @@ public class GraphicsEnvironment { } if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) { Log.v(TAG, "Turn on ANGLE for all applications."); - return ANGLE_GL_DRIVER_TO_USE_ANGLE; - } - if (allUseAngle == ANGLE_GL_DRIVER_ALL_LEGACY) { - Log.v(TAG, "Disable ANGLE for all applications."); - return ANGLE_GL_DRIVER_TO_USE_LEGACY; - } - - // Make sure we have a good package name - if (TextUtils.isEmpty(packageName)) { - return getDefaultDriverToUse(context, packageName); + return true; } // Get the per-application settings lists @@ -522,61 +451,46 @@ public class GraphicsEnvironment { contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS); final List<String> optInValues = getGlobalSettingsString( contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES); - final List<String> angleDeferlist = getGlobalSettingsString( - contentResolver, bundle, Settings.Global.ANGLE_DEFERLIST); Log.v(TAG, "Currently set values for:"); - Log.v(TAG, " angle_gl_driver_selection_pkgs =" + optInPackages); - Log.v(TAG, " angle_gl_driver_selection_values =" + optInValues); + Log.v(TAG, " angle_gl_driver_selection_pkgs=" + optInPackages); + Log.v(TAG, " angle_gl_driver_selection_values=" + optInValues); - // If ANGLE is the system driver AND the deferlist has not yet been applied, select the - // Legacy driver - if (mAngleIsSystemDriver && angleDeferlist.size() == 0) { - Log.v(TAG, "ANGLE deferlist (" + Settings.Global.ANGLE_DEFERLIST + ") has not been " - + "applied, defaulting to legacy driver"); - return ANGLE_GL_DRIVER_TO_USE_LEGACY; - } + mEnabledByGameMode = isAngleEnabledByGameMode(context, packageName); // Make sure we have good settings to use if (optInPackages.size() != optInValues.size()) { - Log.w(TAG, + Log.v(TAG, "Global.Settings values are invalid: " + "number of packages: " + optInPackages.size() + ", " + "number of values: " + optInValues.size()); - return getDefaultDriverToUse(context, packageName); + return mEnabledByGameMode; } - // See if this application is listed in the per-application settings lists + // See if this application is listed in the per-application settings list final int pkgIndex = getPackageIndex(packageName, optInPackages); if (pkgIndex < 0) { - // The application is NOT listed in the per-application settings lists; and so use the - // system driver (i.e. either ANGLE or the Legacy driver) - Log.v(TAG, "getDriverForPackage(): No per-application setting"); - return getDefaultDriverToUse(context, packageName); + Log.v(TAG, packageName + " is not listed in per-application setting"); + return mEnabledByGameMode; } mAngleOptInIndex = pkgIndex; - Log.v(TAG, - "getDriverForPackage(): using per-application switch: " - + optInValues.get(pkgIndex)); - // The application IS listed in the per-application settings lists; and so use the - // setting--choosing the current system driver if the setting is "default" (i.e. either - // ANGLE or the Legacy driver) - String rtnValue = optInValues.get(pkgIndex); + // The application IS listed in the per-application settings list; and so use the + // setting--choosing the current system driver if the setting is "default" + String optInValue = optInValues.get(pkgIndex); Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " - + "set to: '" + rtnValue + "'"); - if (rtnValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) { - return ANGLE_GL_DRIVER_TO_USE_ANGLE; - } else if (rtnValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE) - || rtnValue.equals(ANGLE_GL_DRIVER_CHOICE_LEGACY)) { - return ANGLE_GL_DRIVER_TO_USE_LEGACY; + + "set to: '" + optInValue + "'"); + if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) { + return true; + } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) { + return false; } else { // The user either chose default or an invalid value; go with the default driver or what - // the game dashboard indicates - return getDefaultDriverToUse(context, packageName); + // the game mode indicates + return mEnabledByGameMode; } } @@ -631,9 +545,7 @@ public class GraphicsEnvironment { * the C++ GraphicsEnv class. * * If ANGLE will be used, GraphicsEnv::setAngleInfo() will be called to enable ANGLE to be - * properly used. Otherwise, GraphicsEnv::setLegacyDriverInfo() will be called to - * enable the legacy GLES driver (e.g. when ANGLE is the system driver) to be identified and - * used. + * properly used. * * @param context * @param bundle @@ -646,7 +558,6 @@ public class GraphicsEnvironment { String packageName) { if (!shouldUseAngle(context, bundle, packageName)) { - setLegacyDriverInfo(packageName, mAngleIsSystemDriver, mEglLegacyDriver); return false; } @@ -655,13 +566,13 @@ public class GraphicsEnvironment { // If the developer has specified a debug package over ADB, attempt to find it String anglePkgName = getAngleDebugPackage(context, bundle); if (!anglePkgName.isEmpty()) { - Log.i(TAG, "ANGLE debug package enabled: " + anglePkgName); + Log.v(TAG, "ANGLE debug package enabled: " + anglePkgName); try { // Note the debug package does not have to be pre-installed angleInfo = pm.getApplicationInfo(anglePkgName, 0); } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "ANGLE debug package '" + anglePkgName + "' not installed"); - setLegacyDriverInfo(packageName, mAngleIsSystemDriver, mEglLegacyDriver); + // If the debug package is specified but not found, abort. + Log.v(TAG, "ANGLE debug package '" + anglePkgName + "' not installed"); return false; } } @@ -670,8 +581,7 @@ public class GraphicsEnvironment { if (angleInfo == null) { anglePkgName = getAnglePackageName(pm); if (TextUtils.isEmpty(anglePkgName)) { - Log.w(TAG, "Failed to find ANGLE package."); - setLegacyDriverInfo(packageName, mAngleIsSystemDriver, mEglLegacyDriver); + Log.v(TAG, "Failed to find ANGLE package."); return false; } @@ -681,8 +591,7 @@ public class GraphicsEnvironment { angleInfo = pm.getApplicationInfo(anglePkgName, PackageManager.MATCH_SYSTEM_ONLY); } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed"); - setLegacyDriverInfo(packageName, mAngleIsSystemDriver, mEglLegacyDriver); + Log.v(TAG, "ANGLE package '" + anglePkgName + "' not installed"); return false; } } @@ -697,14 +606,13 @@ public class GraphicsEnvironment { + abi; if (DEBUG) { - Log.v(TAG, "ANGLE package libs: " + paths); + Log.d(TAG, "ANGLE package libs: " + paths); } // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name, // and features to use. final String[] features = getAngleEglFeatures(context, bundle); - setAngleInfo( - paths, packageName, mAngleIsSystemDriver, ANGLE_GL_DRIVER_CHOICE_ANGLE, features); + setAngleInfo(paths, packageName, ANGLE_GL_DRIVER_CHOICE_ANGLE, features); return true; } @@ -994,9 +902,7 @@ public class GraphicsEnvironment { private static native void setGpuStats(String driverPackageName, String driverVersionName, long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion); private static native void setAngleInfo(String path, String appPackage, - boolean angleIsSystemDriver, String devOptIn, String[] features); - private static native void setLegacyDriverInfo( - String appPackage, boolean angleIsSystemDriver, String legacyDriverName); + String devOptIn, String[] features); private static native boolean getShouldUseAngle(String packageName); private static native boolean setInjectLayersPrSetDumpable(); private static native void nativeToggleAngleAsSystemDriver(boolean enabled); diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 4ce9184f0b96..ef3901082833 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -56,6 +56,7 @@ public final class UserHandle implements Parcelable { /** @hide A user id to indicate the currently active user */ @UnsupportedAppUsage + @TestApi public static final @UserIdInt int USER_CURRENT = -2; /** @hide A user handle to indicate the current user of the device */ diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 5bcbaa10e95b..8606687d8c68 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -4224,7 +4224,8 @@ public class UserManager { android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS }) - public List<UserInfo> getUsers() { + @TestApi + public @NonNull List<UserInfo> getUsers() { return getUsers(/*excludePartial= */ true, /* excludeDying= */ false, /* excludePreCreated= */ true); } @@ -4245,6 +4246,7 @@ public class UserManager { android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS }) + @TestApi public @NonNull List<UserInfo> getAliveUsers() { return getUsers(/*excludePartial= */ true, /* excludeDying= */ true, /* excludePreCreated= */ true); @@ -4271,8 +4273,7 @@ public class UserManager { * Returns information for all users on this device, based on the filtering parameters. * * @deprecated Pre-created users are deprecated and no longer supported. - * Use {@link #getUsers()}, {@link #getUsers(boolean)}, or {@link #getAliveUsers()} - * instead. + * Use {@link #getUsers()}, or {@link #getAliveUsers()} instead. * @hide */ @Deprecated diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 77c00676878c..ac6b2b23cdf5 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -1293,7 +1293,8 @@ public class CallLog { USER_MISSED_NO_VIBRATE, USER_MISSED_CALL_SCREENING_SERVICE_SILENCED, USER_MISSED_CALL_FILTERS_TIMEOUT, - USER_MISSED_NEVER_RANG + USER_MISSED_NEVER_RANG, + USER_MISSED_NOT_RUNNING }) @Retention(RetentionPolicy.SOURCE) public @interface MissedReason {} @@ -1391,6 +1392,13 @@ public class CallLog { public static final long USER_MISSED_NEVER_RANG = 1 << 23; /** + * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when + * the user receiving the call is not running (i.e. work profile paused). + * @hide + */ + public static final long USER_MISSED_NOT_RUNNING = 1 << 24; + + /** * Where the {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, * indicates factors which may have lead the user to miss the call. * <P>Type: INTEGER</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/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 90e8cedab622..4b761c105803 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -389,7 +389,7 @@ public class VoiceInteractionService extends Service { // It's still guaranteed to have been stopped. // This helps with cases where the voice interaction implementation is changed // by the user. - safelyShutdownAllHotwordDetectors(); + safelyShutdownAllHotwordDetectors(true); } /** @@ -715,7 +715,7 @@ public class VoiceInteractionService extends Service { synchronized (mLock) { if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) { // Allow only one concurrent recognition via the APIs. - safelyShutdownAllHotwordDetectors(); + safelyShutdownAllHotwordDetectors(false); } else { for (HotwordDetector detector : mActiveDetectors) { if (detector.isUsingSandboxedDetectionService() @@ -878,7 +878,7 @@ public class VoiceInteractionService extends Service { synchronized (mLock) { if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) { // Allow only one concurrent recognition via the APIs. - safelyShutdownAllHotwordDetectors(); + safelyShutdownAllHotwordDetectors(false); } else { for (HotwordDetector detector : mActiveDetectors) { if (!detector.isUsingSandboxedDetectionService()) { @@ -1062,11 +1062,14 @@ public class VoiceInteractionService extends Service { return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null; } - private void safelyShutdownAllHotwordDetectors() { + private void safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector) { synchronized (mLock) { mActiveDetectors.forEach(detector -> { try { - detector.destroy(); + if (detector != mActiveVisualQueryDetector.getInitializationDelegate() + || shouldShutDownVisualQueryDetector) { + detector.destroy(); + } } catch (Exception ex) { Log.i(TAG, "exception destroying HotwordDetector", ex); } @@ -1116,6 +1119,8 @@ public class VoiceInteractionService extends Service { pw.println(); }); } + pw.println("Available Model Enrollment Applications:"); + pw.println(" " + mKeyphraseEnrollmentInfo); } } } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 8d84e4413635..230f51191ee8 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -184,6 +184,7 @@ public abstract class WallpaperService extends Service { private static final long DIMMING_ANIMATION_DURATION_MS = 300L; + @GuardedBy("itself") private final ArrayMap<IBinder, IWallpaperEngineWrapper> mActiveEngines = new ArrayMap<>(); private Handler mBackgroundHandler; @@ -2514,10 +2515,12 @@ public abstract class WallpaperService extends Service { // if they are visible, so we need to toggle the state to get their attention. if (!mEngine.mDestroyed) { mEngine.detach(); - for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { - if (engineWrapper.mEngine != null && engineWrapper.mEngine.mVisible) { - engineWrapper.mEngine.doVisibilityChanged(false); - engineWrapper.mEngine.doVisibilityChanged(true); + synchronized (mActiveEngines) { + for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { + if (engineWrapper.mEngine != null && engineWrapper.mEngine.mVisible) { + engineWrapper.mEngine.doVisibilityChanged(false); + engineWrapper.mEngine.doVisibilityChanged(true); + } } } } @@ -2699,7 +2702,9 @@ public abstract class WallpaperService extends Service { IWallpaperEngineWrapper engineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType, isPreview, reqWidth, reqHeight, padding, displayId, which); - mActiveEngines.put(windowToken, engineWrapper); + synchronized (mActiveEngines) { + mActiveEngines.put(windowToken, engineWrapper); + } if (DEBUG) { Slog.v(TAG, "IWallpaperServiceWrapper Attaching window token " + windowToken); } @@ -2708,7 +2713,10 @@ public abstract class WallpaperService extends Service { @Override public void detach(IBinder windowToken) { - IWallpaperEngineWrapper engineWrapper = mActiveEngines.remove(windowToken); + IWallpaperEngineWrapper engineWrapper; + synchronized (mActiveEngines) { + engineWrapper = mActiveEngines.remove(windowToken); + } if (engineWrapper == null) { Log.w(TAG, "Engine for window token " + windowToken + " already detached"); return; @@ -2734,10 +2742,12 @@ public abstract class WallpaperService extends Service { public void onDestroy() { Trace.beginSection("WPMS.onDestroy"); super.onDestroy(); - for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { - engineWrapper.destroy(); + synchronized (mActiveEngines) { + for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { + engineWrapper.destroy(); + } + mActiveEngines.clear(); } - mActiveEngines.clear(); if (mBackgroundThread != null) { // onDestroy might be called without a previous onCreate if WallpaperService was // instantiated manually. While this is a misuse of the API, some things break @@ -2768,14 +2778,18 @@ public abstract class WallpaperService extends Service { @Override protected void dump(FileDescriptor fd, PrintWriter out, String[] args) { out.print("State of wallpaper "); out.print(this); out.println(":"); - for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { - Engine engine = engineWrapper.mEngine; - if (engine == null) { - Slog.w(TAG, "Engine for wrapper " + engineWrapper + " not attached"); - continue; - } - out.print(" Engine "); out.print(engine); out.println(":"); - engine.dump(" ", fd, out, args); + synchronized (mActiveEngines) { + for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) { + Engine engine = engineWrapper.mEngine; + if (engine == null) { + Slog.w(TAG, "Engine for wrapper " + engineWrapper + " not attached"); + continue; + } + out.print(" Engine "); + out.print(engine); + out.println(":"); + engine.dump(" ", fd, out, args); + } } } } diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index 196bac21ad74..cd767541e13a 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -1106,6 +1106,16 @@ public class DynamicLayout extends Layout { mTransformedTextUpdate.before = before; mTransformedTextUpdate.after = after; } + // When there is a transformed text, we have to reflow the DynamicLayout based on + // the transformed indices instead of the range in base text. + // For example, + // base text: abcd > abce + // updated range: where = 3, before = 1, after = 1 + // transformed text: abxxcd > abxxce + // updated range: where = 5, before = 1, after = 1 + // + // Because the transformedText is udapted simultaneously with the base text, + // the range must be transformed before the base text changes. transformedText.originalToTransformed(mTransformedTextUpdate); } } @@ -1113,9 +1123,20 @@ public class DynamicLayout extends Layout { public void onTextChanged(CharSequence s, int where, int before, int after) { final DynamicLayout dynamicLayout = mLayout.get(); if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) { - where = mTransformedTextUpdate.where; - before = mTransformedTextUpdate.before; - after = mTransformedTextUpdate.after; + if (mTransformedTextUpdate != null && mTransformedTextUpdate.where >= 0) { + where = mTransformedTextUpdate.where; + before = mTransformedTextUpdate.before; + after = mTransformedTextUpdate.after; + // Set where to -1 so that we know if beforeTextChanged is called. + mTransformedTextUpdate.where = -1; + } else { + // onTextChanged is called without beforeTextChanged. Reflow the entire text. + where = 0; + // We can't get the before length from the text, use the line end of the + // last line instead. + before = dynamicLayout.getLineEnd(dynamicLayout.getLineCount() - 1); + after = dynamicLayout.mDisplay.length(); + } } reflow(s, where, before, after); } diff --git a/core/java/android/text/method/InsertModeTransformationMethod.java b/core/java/android/text/method/InsertModeTransformationMethod.java index 0c933d996ff3..59b80f3a6468 100644 --- a/core/java/android/text/method/InsertModeTransformationMethod.java +++ b/core/java/android/text/method/InsertModeTransformationMethod.java @@ -37,6 +37,8 @@ import android.view.View; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; +import java.lang.reflect.Array; + /** * The transformation method used by handwriting insert mode. * This transformation will insert a placeholder string to the original text at the given @@ -309,26 +311,51 @@ public class InsertModeTransformationMethod implements TransformationMethod, Tex return ArrayUtils.emptyArray(type); } - final T[] spansOriginal; + T[] spansOriginal = null; if (mSpannedOriginal != null) { final int originalStart = transformedToOriginal(start, OffsetMapping.MAP_STRATEGY_CURSOR); final int originalEnd = transformedToOriginal(end, OffsetMapping.MAP_STRATEGY_CURSOR); + // We can't simply call SpannedString.getSpans(originalStart, originalEnd) here. + // When start == end SpannedString.getSpans returns spans whose spanEnd == start. + // For example, + // text: abcd span: [1, 3) + // getSpan(3, 3) will return the span [1, 3) but getSpan(3, 4) returns no span. + // + // This creates some special cases when originalStart == originalEnd. + // For example: + // original text: abcd span1: [1, 3) span2: [3, 4) span3: [3, 3) + // transformed text: abc\n\nd span1: [1, 3) span2: [5, 6) span3: [3, 3) + // Case 1: + // When start = 3 and end = 4, transformedText#getSpan(3, 4) should return span3. + // However, because originalStart == originalEnd == 3, originalText#getSpan(3, 3) + // returns span1, span2 and span3. + // + // Case 2: + // When start == end == 4, transformedText#getSpan(4, 4) should return nothing. + // However, because originalStart == originalEnd == 3, originalText#getSpan(3, 3) + // return span1, span2 and span3. + // + // Case 3: + // When start == end == 5, transformedText#getSpan(5, 5) should return span2. + // However, because originalStart == originalEnd == 3, originalText#getSpan(3, 3) + // return span1, span2 and span3. + // + // To handle the issue, we need to filter out the invalid spans. spansOriginal = mSpannedOriginal.getSpans(originalStart, originalEnd, type); - } else { - spansOriginal = null; + spansOriginal = ArrayUtils.filter(spansOriginal, + size -> (T[]) Array.newInstance(type, size), + span -> intersect(getSpanStart(span), getSpanEnd(span), start, end)); } - final T[] spansPlaceholder; + T[] spansPlaceholder = null; if (mSpannedPlaceholder != null && intersect(start, end, mEnd, mEnd + mPlaceholder.length())) { - final int placeholderStart = Math.max(start - mEnd, 0); - final int placeholderEnd = Math.min(end - mEnd, mPlaceholder.length()); + int placeholderStart = Math.max(start - mEnd, 0); + int placeholderEnd = Math.min(end - mEnd, mPlaceholder.length()); spansPlaceholder = mSpannedPlaceholder.getSpans(placeholderStart, placeholderEnd, type); - } else { - spansPlaceholder = null; } // TODO: sort the spans based on their priority. @@ -340,7 +367,10 @@ public class InsertModeTransformationMethod implements TransformationMethod, Tex if (mSpannedOriginal != null) { final int index = mSpannedOriginal.getSpanStart(tag); if (index >= 0) { - if (index < mEnd) { + // When originalSpanStart == originalSpanEnd == mEnd, the span should be + // considered "before" the placeholder text. So we return the originalSpanStart. + if (index < mEnd + || (index == mEnd && mSpannedOriginal.getSpanEnd(tag) == index)) { return index; } return index + mPlaceholder.length(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 96707350b183..6af160c04b4b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -9845,10 +9845,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * <p><b>Note:</b> Setting the mode as {@link #IMPORTANT_FOR_AUTOFILL_NO} or * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} does not guarantee the view (and its - * children) will be always be considered not important; for example, when the user explicitly - * makes an autofill request, all views are considered important. See - * {@link #isImportantForAutofill()} for more details about how the View's importance for - * autofill is used. + * children) will not be used for autofill purpose; for example, when the user explicitly + * makes an autofill request, all views are included in the ViewStructure, and starting in + * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} the system uses other factors along + * with importance to determine the autofill behavior. See {@link #isImportantForAutofill()} + * for more details about how the View's importance for autofill is used. * * @param mode {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, {@link #IMPORTANT_FOR_AUTOFILL_YES}, * {@link #IMPORTANT_FOR_AUTOFILL_NO}, {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}, @@ -9894,21 +9895,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <li>otherwise, it returns {@code false}. * </ol> * - * <p>When a view is considered important for autofill: - * <ul> - * <li>The view might automatically trigger an autofill request when focused on. - * <li>The contents of the view are included in the {@link ViewStructure} used in an autofill - * request. - * </ul> - * - * <p>On the other hand, when a view is considered not important for autofill: - * <ul> - * <li>The view never automatically triggers autofill requests, but it can trigger a manual - * request through {@link AutofillManager#requestAutofill(View)}. - * <li>The contents of the view are not included in the {@link ViewStructure} used in an - * autofill request, unless the request has the - * {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag. - * </ul> + * <p> The behavior of importances depends on Android version: + * <ol> + * <li>For {@link android.os.Build.VERSION_CODES#TIRAMISU} and below: + * <ol> + * <li>When a view is considered important for autofill: + * <ol> + * <li>The view might automatically trigger an autofill request when focused on. + * <li>The contents of the view are included in the {@link ViewStructure} used in an + * autofill request. + * </ol> + * <li>On the other hand, when a view is considered not important for autofill: + * <ol> + * <li>The view never automatically triggers autofill requests, but it can trigger a + * manual request through {@link AutofillManager#requestAutofill(View)}. + * <li>The contents of the view are not included in the {@link ViewStructure} used in + * an autofill request, unless the request has the + * {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag. + * </ol> + * </ol> + * <li>For {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above: + * <ol> + * <li>The system uses importance, along with other view properties and other optimization + * factors, to determine if a view should trigger autofill on focus. + * <li>The contents of {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, + * {@link #IMPORTANT_FOR_AUTOFILL_YES}, {@link #IMPORTANT_FOR_AUTOFILL_NO}, + * {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}, and + * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} views will be included in the + * {@link ViewStructure} used in an autofill request. + * </ol> + * </ol> * * @return whether the view is considered important for autofill. * @@ -25074,7 +25090,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int viewStateIndex = 0; if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED; if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED; - if (isFocused() && hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED; + if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED; if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED; if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED; if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 153bfde07758..3208b6281110 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -301,6 +301,14 @@ public final class ViewRootImpl implements ViewParent, SystemProperties.getBoolean("persist.wm.debug.caption_on_shell", true); /** + * Whether the client (system UI) is handling the transient gesture and the corresponding + * animation. + * @hide + */ + public static final boolean CLIENT_TRANSIENT = + SystemProperties.getBoolean("persist.wm.debug.client_transient", false); + + /** * Whether the client should compute the window frame on its own. * @hide */ diff --git a/core/java/android/view/autofill/AutofillClientController.java b/core/java/android/view/autofill/AutofillClientController.java index 3a8e8027b88e..2f7adaa29fed 100644 --- a/core/java/android/view/autofill/AutofillClientController.java +++ b/core/java/android/view/autofill/AutofillClientController.java @@ -19,6 +19,7 @@ package android.view.autofill; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; +import android.app.ActivityOptions; import android.app.Application; import android.content.ComponentName; import android.content.Intent; @@ -486,8 +487,11 @@ public final class AutofillClientController implements AutofillManager.AutofillC public void autofillClientAuthenticate(int authenticationId, IntentSender intent, Intent fillInIntent, boolean authenticateInline) { try { + ActivityOptions activityOptions = ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); mActivity.startIntentSenderForResult(intent, AUTO_FILL_AUTH_WHO_PREFIX, - authenticationId, fillInIntent, 0, 0, null); + authenticationId, fillInIntent, 0, 0, activityOptions.toBundle()); } catch (IntentSender.SendIntentException e) { Log.e(TAG, "authenticate() failed for intent:" + intent, e); } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index a6e9d4d2094f..5d121ad2957f 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1464,6 +1464,13 @@ public final class AutofillManager { } synchronized (mLock) { + if (mAllTrackedViews.contains(id)) { + // The id is tracked and will not trigger pre-fill request again. + return; + } + + // Add the id as tracked to avoid triggering fill request again and again. + mAllTrackedViews.add(id); if (mTrackedViews != null) { // To support the fill dialog can show for the autofillable Views in // different pages but in the same Activity. We need to reset the @@ -4064,11 +4071,6 @@ public final class AutofillManager { } void checkViewState(AutofillId id) { - if (mAllTrackedViews.contains(id)) { - return; - } - // Add the id as tracked to avoid triggering fill request again and again. - mAllTrackedViews.add(id); if (mHasNewTrackedView) { return; } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 3165654d806d..c28950662fb3 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -726,6 +726,12 @@ public class RemoteViews implements Parcelable, Filter { mActions.get(i).visitUris(visitor); } } + if (mLandscape != null) { + mLandscape.visitUris(visitor); + } + if (mPortrait != null) { + mPortrait.visitUris(visitor); + } } private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 34fe935b55a0..6c84f35f06f5 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -11808,8 +11808,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public boolean hasSelection() { final int selectionStart = getSelectionStart(); final int selectionEnd = getSelectionEnd(); + final int selectionMin; + final int selectionMax; + if (selectionStart < selectionEnd) { + selectionMin = selectionStart; + selectionMax = selectionEnd; + } else { + selectionMin = selectionEnd; + selectionMax = selectionStart; + } - return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd; + return selectionMin >= 0 && selectionMax > 0 && selectionMin != selectionMax; } String getSelectedText() { diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 617519b1b540..fdcb87ff5e3f 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -1468,6 +1468,11 @@ public class BatteryStatsHistory { mHistoryLastLastWritten.setTo(mHistoryLastWritten); final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence; mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur); + if (mHistoryLastWritten.time < mHistoryLastLastWritten.time - 60000) { + Slog.wtf(TAG, "Significantly earlier event written to battery history:" + + " time=" + mHistoryLastWritten.time + + " previous=" + mHistoryLastLastWritten.time); + } mHistoryLastWritten.tagsFirstOccurrence = hasTags; writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten); mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs; @@ -1908,12 +1913,6 @@ public class BatteryStatsHistory { in.setDataPosition(curPos + bufSize); } - if (DEBUG) { - StringBuilder sb = new StringBuilder(128); - sb.append("****************** OLD mHistoryBaseTimeMs: "); - TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); - Slog.i(TAG, sb.toString()); - } mHistoryBaseTimeMs = historyBaseTime; if (DEBUG) { StringBuilder sb = new StringBuilder(128); @@ -1922,11 +1921,10 @@ public class BatteryStatsHistory { Slog.i(TAG, sb.toString()); } - // We are just arbitrarily going to insert 1 minute from the sample of - // the last run until samples in this run. if (mHistoryBaseTimeMs > 0) { - long oldnow = mClock.elapsedRealtime(); - mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1; + long elapsedRealtimeMs = mClock.elapsedRealtime(); + mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs; + mHistoryBaseTimeMs = mHistoryBaseTimeMs - elapsedRealtimeMs + 1; if (DEBUG) { StringBuilder sb = new StringBuilder(128); sb.append("****************** ADJUSTED mHistoryBaseTimeMs: "); diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index c1445035ca33..f2776353fd1b 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -305,10 +305,17 @@ public class LatencyTracker { private final SparseArray<ActionProperties> mActionPropertiesMap = new SparseArray<>(); @GuardedBy("mLock") private boolean mEnabled; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = + this::updateProperties; // Wrapping this in a holder class achieves lazy loading behavior private static final class SLatencyTrackerHolder { - private static final LatencyTracker sLatencyTracker = new LatencyTracker(); + private static final LatencyTracker sLatencyTracker; + + static { + sLatencyTracker = new LatencyTracker(); + sLatencyTracker.startListeningForLatencyTrackerConfigChanges(); + } } public static LatencyTracker getInstance(Context context) { @@ -319,31 +326,16 @@ public class LatencyTracker { * Constructor for LatencyTracker * * <p>This constructor is only visible for test classes to inject their own consumer callbacks + * + * @param startListeningForPropertyChanges If set, constructor will register for device config + * property updates prior to returning. If not set, + * {@link #startListeningForLatencyTrackerConfigChanges} must be called + * to start listening. */ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) @VisibleForTesting public LatencyTracker() { mEnabled = DEFAULT_ENABLED; - - final Context context = ActivityThread.currentApplication(); - if (context != null - && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) { - // Post initialization to the background in case we're running on the main thread. - BackgroundThread.getHandler().post(() -> this.updateProperties( - DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER))); - DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER, - BackgroundThread.getExecutor(), this::updateProperties); - } else { - if (DEBUG) { - if (context == null) { - Log.d(TAG, "No application for " + ActivityThread.currentActivityThread()); - } else { - Log.d(TAG, "Initialized the LatencyTracker." - + " (No READ_DEVICE_CONFIG permission to change configs)" - + " enabled=" + mEnabled + ", package=" + context.getPackageName()); - } - } - } } private void updateProperties(DeviceConfig.Properties properties) { @@ -366,6 +358,54 @@ public class LatencyTracker { } /** + * Test method to start listening to {@link DeviceConfig} properties changes. + * + * <p>During testing, a {@link LatencyTracker} it is desired to stop and start listening for + * config updates. + * + * <p>This is not used for production usages of this class outside of testing as we are + * using a single static object. + */ + @VisibleForTesting + public void startListeningForLatencyTrackerConfigChanges() { + final Context context = ActivityThread.currentApplication(); + if (context != null + && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) { + // Post initialization to the background in case we're running on the main thread. + BackgroundThread.getHandler().post(() -> this.updateProperties( + DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER))); + DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER, + BackgroundThread.getExecutor(), mOnPropertiesChangedListener); + } else { + if (DEBUG) { + if (context == null) { + Log.d(TAG, "No application for " + ActivityThread.currentActivityThread()); + } else { + synchronized (mLock) { + Log.d(TAG, "Initialized the LatencyTracker." + + " (No READ_DEVICE_CONFIG permission to change configs)" + + " enabled=" + mEnabled + ", package=" + context.getPackageName()); + } + } + } + } + } + + /** + * Test method to stop listening to {@link DeviceConfig} properties changes. + * + * <p>During testing, a {@link LatencyTracker} it is desired to stop and start listening for + * config updates. + * + * <p>This is not used for production usages of this class outside of testing as we are + * using a single static object. + */ + @VisibleForTesting + public void stopListeningForLatencyTrackerConfigChanges() { + DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); + } + + /** * A helper method to translate action type to name. * * @param atomsProtoAction the action type defined in AtomsProto.java diff --git a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java index 8192ffd10230..f4e9e3064f3d 100644 --- a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java +++ b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java @@ -105,6 +105,13 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGr } if (recyclerView.requestChildRectangleOnScreen(anchor, input, true)) { + if (anchor.getParent() == null) { + // BUG(b/239050369): Check if the tracked anchor view is still attached. + Log.w(TAG, "Bug: anchor view " + anchor + " is detached after scrolling"); + resultConsumer.accept(result); // empty result + return; + } + int scrolled = prevAnchorTop - anchor.getTop(); // inverse of movement mScrollDelta += scrolled; // view.top-- is equivalent to parent.scrollY++ result.scrollDelta = mScrollDelta; diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index d9152d61ed8a..01dbceb38d3a 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -50,7 +50,7 @@ void setGpuStats_native(JNIEnv* env, jobject clazz, jstring driverPackageName, } void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName, - jboolean angleIsSystemDriver, jstring devOptIn, jobjectArray featuresObj) { + jstring devOptIn, jobjectArray featuresObj) { ScopedUtfChars pathChars(env, path); ScopedUtfChars appNameChars(env, appName); ScopedUtfChars devOptInChars(env, devOptIn); @@ -74,18 +74,7 @@ void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appNa } android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(), - angleIsSystemDriver, devOptInChars.c_str(), - features); -} - -void setLegacyDriverInfo_native(JNIEnv* env, jobject clazz, jstring appName, - jboolean angleIsSystemDriver, jstring legacyDriverName) { - ScopedUtfChars appNameChars(env, appName); - ScopedUtfChars legacyDriverNameChars(env, legacyDriverName); - - android::GraphicsEnv::getInstance().setLegacyDriverInfo(appNameChars.c_str(), - angleIsSystemDriver, - legacyDriverNameChars.c_str()); + devOptInChars.c_str(), features); } bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) { @@ -135,10 +124,8 @@ const JNINativeMethod g_methods[] = { {"setInjectLayersPrSetDumpable", "()Z", reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)}, {"setAngleInfo", - "(Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V", reinterpret_cast<void*>(setAngleInfo_native)}, - {"setLegacyDriverInfo", "(Ljava/lang/String;ZLjava/lang/String;)V", - reinterpret_cast<void*>(setLegacyDriverInfo_native)}, {"getShouldUseAngle", "(Ljava/lang/String;)Z", reinterpret_cast<void*>(shouldUseAngle_native)}, {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp index 850755a62fa6..55995df299b0 100644 --- a/core/jni/android_window_WindowInfosListener.cpp +++ b/core/jni/android_window_WindowInfosListener.cpp @@ -21,6 +21,7 @@ #include <android_runtime/Log.h> #include <gui/DisplayInfo.h> #include <gui/SurfaceComposerClient.h> +#include <gui/WindowInfosUpdate.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedLocalFrame.h> #include <utils/Log.h> @@ -91,8 +92,7 @@ struct WindowInfosListener : public gui::WindowInfosListener { WindowInfosListener(JNIEnv* env, jobject listener) : mListener(env->NewWeakGlobalRef(listener)) {} - void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos, - const std::vector<DisplayInfo>& displayInfos) override { + void onWindowInfosChanged(const gui::WindowInfosUpdate& update) override { JNIEnv* env = AndroidRuntime::getJNIEnv(); LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onWindowInfoChanged."); @@ -103,8 +103,10 @@ struct WindowInfosListener : public gui::WindowInfosListener { return; } - ScopedLocalRef<jobjectArray> jWindowHandlesArray(env, fromWindowInfos(env, windowInfos)); - ScopedLocalRef<jobjectArray> jDisplayInfoArray(env, fromDisplayInfos(env, displayInfos)); + ScopedLocalRef<jobjectArray> jWindowHandlesArray(env, + fromWindowInfos(env, update.windowInfos)); + ScopedLocalRef<jobjectArray> jDisplayInfoArray(env, + fromDisplayInfos(env, update.displayInfos)); env->CallVoidMethod(listener, gListenerClassInfo.onWindowInfosChanged, jWindowHandlesArray.get(), jDisplayInfoArray.get()); diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto index a776bd2a4330..a950a79d94fb 100644 --- a/core/proto/android/server/windowmanagertransitiontrace.proto +++ b/core/proto/android/server/windowmanagertransitiontrace.proto @@ -23,7 +23,7 @@ import "frameworks/base/core/proto/android/server/windowmanagerservice.proto"; option java_multiple_files = true; /* Represents a file full of transition entries. - Encoded, it should start with 0x09 0x54 0x52 0x4E 0x54 0x52 0x41 0x43 0x45 (TRNTRACE), such + Encoded, it should start with 0x09 0x54 0x52 0x4E 0x54 0x52 0x41 0x43 0x45 (.TRNTRACE), such that it can be easily identified. */ message TransitionTraceProto { @@ -38,28 +38,24 @@ message TransitionTraceProto { // Must be the first field, set to value in MagicNumber required fixed64 magic_number = 1; - // Transitions that don't have a finish time are considered aborted - repeated Transition finished_transitions = 2; - - // Additional debugging info only collected and dumped when explicitly requested to trace - repeated TransitionState transition_states = 3; - repeated TransitionInfo transition_info = 4; + repeated Transition transitions = 2; /* offset between real-time clock and elapsed time clock in nanoseconds. Calculated as: 1000000 * System.currentTimeMillis() - SystemClock.elapsedRealtimeNanos() */ - optional fixed64 real_to_elapsed_time_offset_nanos = 5; + optional fixed64 real_to_elapsed_time_offset_nanos = 3; } message Transition { - optional int32 id = 1; // Not dumped in always on tracing - required uint64 start_transaction_id = 2; - required uint64 finish_transaction_id = 3; - required int64 create_time_ns = 4; - required int64 send_time_ns = 5; - optional int64 finish_time_ns = 6; // consider aborted if not provided - required int32 type = 7; + required int32 id = 1; + optional uint64 start_transaction_id = 2; + optional uint64 finish_transaction_id = 3; + optional int64 create_time_ns = 4; + optional int64 send_time_ns = 5; + optional int64 finish_time_ns = 6; + optional int32 type = 7; repeated Target targets = 8; optional int32 flags = 9; + optional int64 abort_time_ns = 10; } message Target { @@ -68,40 +64,3 @@ message Target { optional int32 window_id = 3; // Not dumped in always on tracing optional int32 flags = 4; } - -message TransitionState { - enum State { - COLLECTING = 0; - PENDING = -1; - STARTED = 1; - PLAYING = 2; - ABORT = 3; - FINISHED = 4; - } - - required int64 time_ns = 1; - required int32 transition_id = 2; - required int32 transition_type = 3; - required State state = 4; - required int32 flags = 5; - repeated ChangeInfo change = 6; - repeated com.android.server.wm.IdentifierProto participants = 7; -} - -message ChangeInfo { - required com.android.server.wm.IdentifierProto window_identifier = 1; - required int32 transit_mode = 2; - required bool has_changed = 3; - required int32 change_flags = 4; - required int32 windowing_mode = 5; -} - -message TransitionInfo { - required int32 transition_id = 1; - repeated TransitionInfoChange change = 2; -} - -message TransitionInfoChange { - required int32 layer_id = 1; - required int32 mode = 2; -} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index fefa79f65201..7e0a36d30771 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4976,11 +4976,11 @@ android:protectionLevel="signature" /> <!-- Allows an application to subscribe to keyguard locked (i.e., showing) state. - <p>Protection level: internal|role - <p>Intended for use by ROLE_ASSISTANT only. + <p>Protection level: signature|role + <p>Intended for use by ROLE_ASSISTANT and signature apps only. --> <permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" - android:protectionLevel="internal|role"/> + android:protectionLevel="signature|role"/> <!-- Must be required by a {@link android.service.autofill.AutofillService}, to ensure that only the system can bind to it. diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 7de36a7113ae..6f7bc53e891c 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2791,7 +2791,7 @@ <flag name="noExcludeDescendants" value="0x8" /> </attr> - <!-- Boolean that hints the Android System that the view is credntial and associated with + <!-- Boolean that hints the Android System that the view is credential and associated with CredentialManager --> <attr name="isCredential" format="boolean" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index bf141b529479..17d84021816f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -774,11 +774,6 @@ we rely on gravity to determine the effective orientation. --> <bool name="config_deskDockEnablesAccelerometer">true</bool> - <!-- Control whether nosensor and locked orientation requests are respected from the app when - config_deskDockEnablesAccelerometer is set to false. - TODO(b/274763533): Consider making true by default and removing this. --> - <bool name="config_deskRespectsNoSensorAndLockedWithoutAccelerometer">false</bool> - <!-- Car dock behavior --> <!-- The number of degrees to rotate the display when the device is in a car dock. @@ -6440,4 +6435,8 @@ <bool name="config_persistBrightnessNitsForDefaultDisplay">false</bool> <!-- Whether to request the approval before commit sessions. --> <bool name="config_isPreApprovalRequestAvailable">true</bool> + + <!-- Whether the AOSP support for app cloning building blocks is to be enabled for the + device. --> + <bool name="config_enableAppCloningBuildingBlocks">true</bool> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c91e3cbc2ccb..e3697bba3f95 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -480,6 +480,7 @@ <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" /> <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsers" /> <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay" /> + <java-symbol type="bool" name="config_enableAppCloningBuildingBlocks" /> <java-symbol type="bool" name="config_enableTimeoutToDockUserWhenDocked" /> <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/> <java-symbol type="xml" name="config_user_types" /> @@ -1698,7 +1699,6 @@ <java-symbol type="bool" name="config_carDockEnablesAccelerometer" /> <java-symbol type="bool" name="config_customUserSwitchUi" /> <java-symbol type="bool" name="config_deskDockEnablesAccelerometer" /> - <java-symbol type="bool" name="config_deskRespectsNoSensorAndLockedWithoutAccelerometer" /> <java-symbol type="bool" name="config_disableMenuKeyInLockScreen" /> <java-symbol type="bool" name="config_enableCarDockHomeLaunch" /> <java-symbol type="bool" name="config_enableLockBeforeUnlockScreen" /> diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java index c7b82b1caca6..6a6a951c94c8 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java @@ -750,6 +750,15 @@ public final class TunerAdapterTest { } @Test + public void onTuneFailed_withCanceledResult() throws Exception { + mTunerCallback.onTuneFailed(RadioTuner.TUNER_RESULT_CANCELED, FM_SELECTOR); + + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed( + RadioTuner.TUNER_RESULT_CANCELED, FM_SELECTOR); + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_CANCELLED); + } + + @Test public void onProgramListChanged_forTunerCallbackAdapter() throws Exception { mTunerCallback.onProgramListChanged(); diff --git a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java index 76f417151001..5939c0609e18 100644 --- a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java +++ b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java @@ -119,6 +119,86 @@ public class DynamicLayoutOffsetMappingTest { assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 8); } + @Test + public void textWithOffsetMapping_blockBeforeTextChanged_deletion() { + final String text = "abcdef"; + final SpannableStringBuilder spannable = new TestNoBeforeTextChangeSpannableString(text); + final CharSequence transformedText = + new TestOffsetMapping(spannable, 5, "\n\n"); + + final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH) + .setAlignment(ALIGN_NORMAL) + .setIncludePad(false) + .setDisplayText(transformedText) + .build(); + + // delete "cd", original text becomes "abef" + spannable.delete(2, 4); + assertThat(transformedText.toString()).isEqualTo("abe\n\nf"); + assertLineRange(layout, /* lineBreaks */ 0, 4, 5, 6); + + // delete "abe", original text becomes "f" + spannable.delete(0, 3); + assertThat(transformedText.toString()).isEqualTo("\n\nf"); + assertLineRange(layout, /* lineBreaks */ 0, 1, 2, 3); + } + + @Test + public void textWithOffsetMapping_blockBeforeTextChanged_insertion() { + final String text = "abcdef"; + final SpannableStringBuilder spannable = new TestNoBeforeTextChangeSpannableString(text); + final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n"); + + final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH) + .setAlignment(ALIGN_NORMAL) + .setIncludePad(false) + .setDisplayText(transformedText) + .build(); + + spannable.insert(3, "x"); + assertThat(transformedText.toString()).isEqualTo("abcx\n\ndef"); + assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 9); + + spannable.insert(5, "x"); + assertThat(transformedText.toString()).isEqualTo("abcx\n\ndxef"); + assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 10); + } + + @Test + public void textWithOffsetMapping_blockBeforeTextChanged_replace() { + final String text = "abcdef"; + final SpannableStringBuilder spannable = new TestNoBeforeTextChangeSpannableString(text); + final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n"); + + final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH) + .setAlignment(ALIGN_NORMAL) + .setIncludePad(false) + .setDisplayText(transformedText) + .build(); + + spannable.replace(2, 4, "xx"); + assertThat(transformedText.toString()).isEqualTo("abxx\n\nef"); + assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 8); + } + + @Test + public void textWithOffsetMapping_onlyCallOnTextChanged_notCrash() { + String text = "abcdef"; + SpannableStringBuilder spannable = new SpannableStringBuilder(text); + CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n"); + + DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH) + .setAlignment(ALIGN_NORMAL) + .setIncludePad(false) + .setDisplayText(transformedText) + .build(); + + TextWatcher[] textWatcher = spannable.getSpans(0, spannable.length(), TextWatcher.class); + assertThat(textWatcher.length).isEqualTo(1); + + textWatcher[0].onTextChanged(spannable, 0, 2, 2); + } + private void assertLineRange(Layout layout, int... lineBreaks) { final int lineCount = lineBreaks.length - 1; assertThat(layout.getLineCount()).isEqualTo(lineCount); @@ -129,6 +209,50 @@ public class DynamicLayoutOffsetMappingTest { } /** + * A test SpannableStringBuilder that doesn't call beforeTextChanged. It's used to test + * DynamicLayout against some special cases where beforeTextChanged callback is not properly + * called. + */ + private static class TestNoBeforeTextChangeSpannableString extends SpannableStringBuilder { + + TestNoBeforeTextChangeSpannableString(CharSequence text) { + super(text); + } + + @Override + public void setSpan(Object what, int start, int end, int flags) { + if (what instanceof TextWatcher) { + super.setSpan(new TestNoBeforeTextChangeWatcherWrapper((TextWatcher) what), start, + end, flags); + } else { + super.setSpan(what, start, end, flags); + } + } + } + + /** A TextWatcherWrapper that blocks beforeTextChanged callback. */ + private static class TestNoBeforeTextChangeWatcherWrapper implements TextWatcher { + private final TextWatcher mTextWatcher; + + TestNoBeforeTextChangeWatcherWrapper(TextWatcher textWatcher) { + mTextWatcher = textWatcher; + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + mTextWatcher.onTextChanged(s, start, before, count); + } + + @Override + public void afterTextChanged(Editable s) { + mTextWatcher.afterTextChanged(s); + } + } + + /** * A test TransformedText that inserts some text at the given offset. */ private static class TestOffsetMapping implements OffsetMapping, CharSequence { diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java index 7706d9aebdcc..9ef137beebb2 100644 --- a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java +++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java @@ -224,6 +224,12 @@ public class InsertModeTransformationMethodTest { assertThat(spans0to2.length).isEqualTo(1); assertThat(spans0to2[0]).isEqualTo(span1); + // only span2 is in the range of [3, 4). + // note: span1 [0, 3) is not in the range because [3, 4) is not collapsed. + final TestSpan[] spans3to4 = transformedText.getSpans(3, 4, TestSpan.class); + assertThat(spans3to4.length).isEqualTo(1); + assertThat(spans3to4[0]).isEqualTo(span2); + // span1 and span2 are in the range of [1, 6). final TestSpan[] spans1to6 = transformedText.getSpans(1, 6, TestSpan.class); assertThat(spans1to6.length).isEqualTo(2); @@ -262,7 +268,7 @@ public class InsertModeTransformationMethodTest { text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - // In the transformedText, the new ranges of the spans are: + // In the transformedText "abc\uFFFD def", the new ranges of the spans are: // span1: [0, 3) // span2: [2, 5) // span3: [5, 6) @@ -277,6 +283,12 @@ public class InsertModeTransformationMethodTest { assertThat(spans0to2.length).isEqualTo(1); assertThat(spans0to2[0]).isEqualTo(span1); + // only span2 is in the range of [3, 4). + // note: span1 [0, 3) is not in the range because [3, 4) is not collapsed. + final TestSpan[] spans3to4 = transformedText.getSpans(3, 4, TestSpan.class); + assertThat(spans3to4.length).isEqualTo(1); + assertThat(spans3to4[0]).isEqualTo(span2); + // span1 and span2 are in the range of [1, 5). final TestSpan[] spans1to4 = transformedText.getSpans(1, 4, TestSpan.class); assertThat(spans1to4.length).isEqualTo(2); @@ -318,20 +330,143 @@ public class InsertModeTransformationMethodTest { } @Test + public void transformedText_getSpans_collapsedRange() { + final SpannableString text = new SpannableString(TEXT); + final TestSpan span1 = new TestSpan(); + final TestSpan span2 = new TestSpan(); + final TestSpan span3 = new TestSpan(); + + text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(span2, 3, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(span3, 3, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + // In the transformedText "abc\n\n def", the new ranges of the spans are: + // span1: [0, 3) + // span2: [3, 3) + // span3: [5, 6) + final InsertModeTransformationMethod transformationMethod = + new InsertModeTransformationMethod(3, false, null); + final Spanned transformedText = + (Spanned) transformationMethod.getTransformation(text, sView); + + // only span1 is in the range of [0, 0). + final TestSpan[] spans0to0 = transformedText.getSpans(0, 0, TestSpan.class); + assertThat(spans0to0.length).isEqualTo(1); + assertThat(spans0to0[0]).isEqualTo(span1); + + // span1 and span 2 are in the range of [3, 3). + final TestSpan[] spans3to3 = transformedText.getSpans(3, 3, TestSpan.class); + assertThat(spans3to3.length).isEqualTo(2); + assertThat(spans3to3[0]).isEqualTo(span1); + assertThat(spans3to3[1]).isEqualTo(span2); + + // only the span2 with collapsed range is in the range of [3, 4). + final TestSpan[] spans3to4 = transformedText.getSpans(3, 4, TestSpan.class); + assertThat(spans3to4.length).isEqualTo(1); + assertThat(spans3to4[0]).isEqualTo(span2); + + // no span is in the range of [4, 5). (span2 is not mistakenly included.) + final TestSpan[] spans4to5 = transformedText.getSpans(4, 5, TestSpan.class); + assertThat(spans4to5).isEmpty(); + + // only span3 is in the range of [4, 6). (span2 is not mistakenly included.) + final TestSpan[] spans4to6 = transformedText.getSpans(4, 6, TestSpan.class); + assertThat(spans4to6.length).isEqualTo(1); + assertThat(spans4to6[0]).isEqualTo(span3); + + // no span is in the range of [4, 4). + final TestSpan[] spans4to4 = transformedText.getSpans(4, 4, TestSpan.class); + assertThat(spans4to4.length).isEqualTo(0); + + // span3 is in the range of [5, 5). + final TestSpan[] spans5to5 = transformedText.getSpans(5, 5, TestSpan.class); + assertThat(spans5to5.length).isEqualTo(1); + assertThat(spans5to5[0]).isEqualTo(span3); + + // span3 is in the range of [6, 6). + final TestSpan[] spans6to6 = transformedText.getSpans(6, 6, TestSpan.class); + assertThat(spans6to6.length).isEqualTo(1); + assertThat(spans6to6[0]).isEqualTo(span3); + } + + @Test + public void transformedText_getSpans_collapsedRange_singleLine() { + final SpannableString text = new SpannableString(TEXT); + final TestSpan span1 = new TestSpan(); + final TestSpan span2 = new TestSpan(); + final TestSpan span3 = new TestSpan(); + + text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(span2, 3, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(span3, 3, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + // In the transformedText "abc\uFFFD def", the new ranges of the spans are: + // span1: [0, 3) + // span2: [3, 3) + // span3: [4, 5) + final InsertModeTransformationMethod transformationMethod = + new InsertModeTransformationMethod(3, true, null); + final Spanned transformedText = + (Spanned) transformationMethod.getTransformation(text, sView); + + // only span1 is in the range of [0, 0). + final TestSpan[] spans0to0 = transformedText.getSpans(0, 0, TestSpan.class); + assertThat(spans0to0.length).isEqualTo(1); + assertThat(spans0to0[0]).isEqualTo(span1); + + // span1 and span2 are in the range of [3, 3). + final TestSpan[] spans3to3 = transformedText.getSpans(3, 3, TestSpan.class); + assertThat(spans3to3.length).isEqualTo(2); + assertThat(spans3to3[0]).isEqualTo(span1); + assertThat(spans3to3[1]).isEqualTo(span2); + + // only the span2 with collapsed range is in the range of [3, 4). + final TestSpan[] spans3to4 = transformedText.getSpans(3, 4, TestSpan.class); + assertThat(spans3to4.length).isEqualTo(1); + assertThat(spans3to4[0]).isEqualTo(span2); + + // span3 is in the range of [4, 5). (span2 is not mistakenly included.) + final TestSpan[] spans4to5 = transformedText.getSpans(4, 5, TestSpan.class); + assertThat(spans4to5.length).isEqualTo(1); + assertThat(spans4to5[0]).isEqualTo(span3); + + // only span3 is in the range of [4, 6). (span2 is not mistakenly included.) + final TestSpan[] spans4to6 = transformedText.getSpans(4, 6, TestSpan.class); + assertThat(spans4to6.length).isEqualTo(1); + assertThat(spans4to6[0]).isEqualTo(span3); + + // span3 is in the range of [4, 4). + final TestSpan[] spans4to4 = transformedText.getSpans(4, 4, TestSpan.class); + assertThat(spans4to4.length).isEqualTo(1); + assertThat(spans4to4[0]).isEqualTo(span3); + + // span3 is in the range of [5, 5). + final TestSpan[] spans5to5 = transformedText.getSpans(5, 5, TestSpan.class); + assertThat(spans5to5.length).isEqualTo(1); + assertThat(spans5to5[0]).isEqualTo(span3); + } + + @Test public void transformedText_getSpanStartAndEnd() { final SpannableString text = new SpannableString(TEXT); final TestSpan span1 = new TestSpan(); final TestSpan span2 = new TestSpan(); final TestSpan span3 = new TestSpan(); + final TestSpan span4 = new TestSpan(); + final TestSpan span5 = new TestSpan(); text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(span4, 3, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(span5, 3, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // In the transformedText, the new ranges of the spans are: // span1: [0, 3) // span2: [2, 6) // span3: [6, 7) + // span4: [3, 3) + // span5: [5, 6) final InsertModeTransformationMethod transformationMethod = new InsertModeTransformationMethod(3, false, null); final Spanned transformedText = @@ -345,6 +480,12 @@ public class InsertModeTransformationMethodTest { assertThat(transformedText.getSpanStart(span3)).isEqualTo(6); assertThat(transformedText.getSpanEnd(span3)).isEqualTo(7); + + assertThat(transformedText.getSpanStart(span4)).isEqualTo(3); + assertThat(transformedText.getSpanEnd(span4)).isEqualTo(3); + + assertThat(transformedText.getSpanStart(span5)).isEqualTo(5); + assertThat(transformedText.getSpanEnd(span5)).isEqualTo(6); } @Test @@ -353,15 +494,21 @@ public class InsertModeTransformationMethodTest { final TestSpan span1 = new TestSpan(); final TestSpan span2 = new TestSpan(); final TestSpan span3 = new TestSpan(); + final TestSpan span4 = new TestSpan(); + final TestSpan span5 = new TestSpan(); text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(span4, 3, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(span5, 3, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // In the transformedText, the new ranges of the spans are: // span1: [0, 3) // span2: [2, 5) // span3: [5, 6) + // span4: [3. 3) + // span5: [4, 5) final InsertModeTransformationMethod transformationMethod = new InsertModeTransformationMethod(3, true, null); final Spanned transformedText = @@ -376,6 +523,12 @@ public class InsertModeTransformationMethodTest { assertThat(transformedText.getSpanStart(span3)).isEqualTo(5); assertThat(transformedText.getSpanEnd(span3)).isEqualTo(6); + assertThat(transformedText.getSpanStart(span4)).isEqualTo(3); + assertThat(transformedText.getSpanEnd(span4)).isEqualTo(3); + + assertThat(transformedText.getSpanStart(span5)).isEqualTo(4); + assertThat(transformedText.getSpanEnd(span5)).isEqualTo(5); + final ReplacementSpan[] replacementSpans = transformedText.getSpans(0, 8, ReplacementSpan.class); assertThat(transformedText.getSpanStart(replacementSpans[0])).isEqualTo(3); diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 31c5a761ac1f..963014e0bb50 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -24,6 +24,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -33,6 +37,8 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Looper; @@ -58,6 +64,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; /** * Tests for RemoteViews. @@ -703,4 +710,61 @@ public class RemoteViewsTest { return null; } } + + @Test + public void visitUris() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + + final Uri imageUri = Uri.parse("content://media/image"); + final Icon icon1 = Icon.createWithContentUri("content://media/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://media/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://media/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://media/icon4"); + views.setImageViewUri(R.id.image, imageUri); + views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUri)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + + @Test + public void visitUris_separateOrientation() { + final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://landscape/image"); + final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4"); + landscape.setImageViewUri(R.id.image, imageUriL); + landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews portrait = new RemoteViews(mPackage, 33); + final Uri imageUriP = Uri.parse("content://portrait/image"); + final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1"); + final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2"); + final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3"); + final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4"); + portrait.setImageViewUri(R.id.image, imageUriP); + portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P); + + RemoteViews views = new RemoteViews(landscape, portrait); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriP)); + verify(visitor, times(1)).accept(eq(icon1P.getUri())); + verify(visitor, times(1)).accept(eq(icon2P.getUri())); + verify(visitor, times(1)).accept(eq(icon3P.getUri())); + verify(visitor, times(1)).accept(eq(icon4P.getUri())); + } } diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java index 645324d57ea9..584ad205a55d 100644 --- a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java @@ -28,12 +28,12 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.provider.DeviceConfig; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; import com.android.internal.util.LatencyTracker.ActionProperties; import com.google.common.truth.Expect; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -48,7 +48,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -@SmallTest @RunWith(AndroidJUnit4.class) public class LatencyTrackerTest { private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__"; @@ -65,6 +64,11 @@ public class LatencyTrackerTest { mLatencyTracker = FakeLatencyTracker.create(); } + @After + public void tearDown() { + mLatencyTracker.stopListeningForLatencyTrackerConfigChanges(); + } + @Test public void testCujsMapToEnumsCorrectly() { List<Field> actions = getAllActionFields(); diff --git a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java index 61e976bee35e..76e69bf35aaf 100644 --- a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java +++ b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java @@ -25,8 +25,6 @@ import android.provider.DeviceConfig; import android.util.Log; import android.util.SparseArray; -import androidx.annotation.Nullable; - import com.android.internal.annotations.GuardedBy; import com.google.common.collect.ImmutableMap; @@ -51,15 +49,17 @@ public final class FakeLatencyTracker extends LatencyTracker { private final List<String> mPerfettoTraceNamesTriggered; private final AtomicReference<SparseArray<ActionProperties>> mLastPropertiesUpdate = new AtomicReference<>(); - @Nullable - @GuardedBy("mLock") - private Callable<Boolean> mShouldClosePropertiesUpdatedCallable = null; + private final AtomicReference<Callable<Boolean>> mShouldClosePropertiesUpdatedCallable = + new AtomicReference<>(); private final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable(); public static FakeLatencyTracker create() throws Exception { Log.i(TAG, "create"); disableForAllActions(); + Log.i(TAG, "done disabling all actions"); FakeLatencyTracker fakeLatencyTracker = new FakeLatencyTracker(); + Log.i(TAG, "done creating tracker object"); + fakeLatencyTracker.startListeningForLatencyTrackerConfigChanges(); // always return the fake in the disabled state and let the client control the desired state fakeLatencyTracker.waitForGlobalEnabledState(false); fakeLatencyTracker.waitForAllPropertiesEnableState(false); @@ -131,27 +131,25 @@ public final class FakeLatencyTracker extends LatencyTracker { @Override public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) { Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties); + mLastPropertiesUpdate.set(actionProperties); - synchronized (mLock) { - if (mShouldClosePropertiesUpdatedCallable != null) { - try { - boolean shouldClosePropertiesUpdated = - mShouldClosePropertiesUpdatedCallable.call(); - Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result=" - + shouldClosePropertiesUpdated); - if (shouldClosePropertiesUpdated) { - Log.i(TAG, "shouldClosePropertiesUpdatedCallable=true, opening condition"); - mShouldClosePropertiesUpdatedCallable = null; - mDeviceConfigPropertiesUpdated.open(); - } - } catch (Exception e) { - Log.e(TAG, "exception when calling callable", e); - throw new RuntimeException(e); + Callable<Boolean> shouldClosePropertiesUpdated = + mShouldClosePropertiesUpdatedCallable.get(); + if (shouldClosePropertiesUpdated != null) { + try { + boolean result = shouldClosePropertiesUpdated.call(); + Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result=" + result); + if (result) { + mShouldClosePropertiesUpdatedCallable.set(null); + mDeviceConfigPropertiesUpdated.open(); } - } else { - Log.i(TAG, "no conditional callable set, opening condition"); - mDeviceConfigPropertiesUpdated.open(); + } catch (Exception e) { + Log.e(TAG, "exception when calling callable", e); + throw new RuntimeException(e); } + } else { + Log.i(TAG, "no conditional callable set, opening condition"); + mDeviceConfigPropertiesUpdated.open(); } } @@ -175,107 +173,82 @@ public final class FakeLatencyTracker extends LatencyTracker { public void waitForAllPropertiesEnableState(boolean enabledState) throws Exception { Log.i(TAG, "waitForAllPropertiesEnableState: enabledState=" + enabledState); - synchronized (mLock) { - Log.i(TAG, "closing condition"); - mDeviceConfigPropertiesUpdated.close(); - // Update the callable to only close the properties updated condition when all the - // desired properties have been updated. The DeviceConfig callbacks may happen multiple - // times so testing the resulting updates is required. - mShouldClosePropertiesUpdatedCallable = () -> { - Log.i(TAG, "verifying if last properties update has all properties enable=" - + enabledState); - SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); - if (newProperties != null) { - for (int i = 0; i < newProperties.size(); i++) { - if (newProperties.get(i).isEnabled() != enabledState) { - return false; - } + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + waitForPropertiesCondition(() -> { + Log.i(TAG, "verifying if last properties update has all properties enable=" + + enabledState); + SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); + if (newProperties != null) { + for (int i = 0; i < newProperties.size(); i++) { + if (newProperties.get(i).isEnabled() != enabledState) { + return false; } } - return true; - }; - if (mShouldClosePropertiesUpdatedCallable.call()) { - return; } - } - Log.i(TAG, "waiting for condition"); - mDeviceConfigPropertiesUpdated.block(); + return true; + }); } public void waitForMatchingActionProperties(ActionProperties actionProperties) throws Exception { Log.i(TAG, "waitForMatchingActionProperties: actionProperties=" + actionProperties); - synchronized (mLock) { - Log.i(TAG, "closing condition"); - mDeviceConfigPropertiesUpdated.close(); - // Update the callable to only close the properties updated condition when all the - // desired properties have been updated. The DeviceConfig callbacks may happen multiple - // times so testing the resulting updates is required. - mShouldClosePropertiesUpdatedCallable = () -> { - Log.i(TAG, "verifying if last properties update contains matching property =" - + actionProperties); - SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); - if (newProperties != null) { - if (newProperties.size() > 0) { - return newProperties.get(actionProperties.getAction()).equals( - actionProperties); - } + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + waitForPropertiesCondition(() -> { + Log.i(TAG, "verifying if last properties update contains matching property =" + + actionProperties); + SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); + if (newProperties != null) { + if (newProperties.size() > 0) { + return newProperties.get(actionProperties.getAction()).equals( + actionProperties); } - return false; - }; - if (mShouldClosePropertiesUpdatedCallable.call()) { - return; } - } - Log.i(TAG, "waiting for condition"); - mDeviceConfigPropertiesUpdated.block(); + return false; + }); } public void waitForActionEnabledState(int action, boolean enabledState) throws Exception { Log.i(TAG, "waitForActionEnabledState:" + " action=" + action + ", enabledState=" + enabledState); - synchronized (mLock) { - Log.i(TAG, "closing condition"); - mDeviceConfigPropertiesUpdated.close(); - // Update the callable to only close the properties updated condition when all the - // desired properties have been updated. The DeviceConfig callbacks may happen multiple - // times so testing the resulting updates is required. - mShouldClosePropertiesUpdatedCallable = () -> { - Log.i(TAG, "verifying if last properties update contains action=" + action - + ", enabledState=" + enabledState); - SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); - if (newProperties != null) { - if (newProperties.size() > 0) { - return newProperties.get(action).isEnabled() == enabledState; - } + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + waitForPropertiesCondition(() -> { + Log.i(TAG, "verifying if last properties update contains action=" + action + + ", enabledState=" + enabledState); + SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); + if (newProperties != null) { + if (newProperties.size() > 0) { + return newProperties.get(action).isEnabled() == enabledState; } - return false; - }; - if (mShouldClosePropertiesUpdatedCallable.call()) { - return; } - } - Log.i(TAG, "waiting for condition"); - mDeviceConfigPropertiesUpdated.block(); + return false; + }); } public void waitForGlobalEnabledState(boolean enabledState) throws Exception { Log.i(TAG, "waitForGlobalEnabledState: enabledState=" + enabledState); - synchronized (mLock) { - Log.i(TAG, "closing condition"); - mDeviceConfigPropertiesUpdated.close(); - // Update the callable to only close the properties updated condition when all the - // desired properties have been updated. The DeviceConfig callbacks may happen multiple - // times so testing the resulting updates is required. - mShouldClosePropertiesUpdatedCallable = () -> { - //noinspection deprecation - return isEnabled() == enabledState; - }; - if (mShouldClosePropertiesUpdatedCallable.call()) { - return; - } + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + waitForPropertiesCondition(() -> { + //noinspection deprecation + return isEnabled() == enabledState; + }); + } + + public void waitForPropertiesCondition(Callable<Boolean> shouldClosePropertiesUpdatedCallable) + throws Exception { + mShouldClosePropertiesUpdatedCallable.set(shouldClosePropertiesUpdatedCallable); + mDeviceConfigPropertiesUpdated.close(); + if (!shouldClosePropertiesUpdatedCallable.call()) { + Log.i(TAG, "waiting for mDeviceConfigPropertiesUpdated condition"); + mDeviceConfigPropertiesUpdated.block(); } - Log.i(TAG, "waiting for condition"); - mDeviceConfigPropertiesUpdated.block(); + Log.i(TAG, "waitForPropertiesCondition: returning"); } } diff --git a/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto b/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto index 6e0110193a05..c82a70c9a44e 100644 --- a/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto +++ b/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto @@ -37,6 +37,9 @@ message WmShellTransitionTraceProto { required fixed64 magic_number = 1; repeated Transition transitions = 2; repeated HandlerMapping handlerMappings = 3; + /* offset between real-time clock and elapsed time clock in nanoseconds. + Calculated as: 1000000 * System.currentTimeMillis() - SystemClock.elapsedRealtimeNanos() */ + optional fixed64 real_to_elapsed_time_offset_nanos = 4; } message Transition { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index 19eff0e43169..1793a3d0feb4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -255,7 +255,7 @@ class ActivityEmbeddingAnimationSpec { private boolean shouldShowBackdrop(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change) { final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE, - mTransitionAnimation); + mTransitionAnimation, false); return a != null && a.getShowBackdrop(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index e8014af463bd..adc0c9c4322a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -479,7 +479,7 @@ public class BubbleExpandedView extends LinearLayout { void applyThemeAttrs() { final TypedArray ta = mContext.obtainStyledAttributes(new int[]{ android.R.attr.dialogCornerRadius, - android.R.attr.colorBackgroundFloating}); + com.android.internal.R.attr.materialColorSurfaceBright}); boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( mContext.getResources()); mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index e7dede757578..2832c553c20c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -412,7 +412,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange /** Releases and re-inflates {@link DividerView} on the root surface. */ public void update(SurfaceControl.Transaction t) { - if (!mInitialized) return; + if (!mInitialized) { + init(); + return; + } mSplitWindowManager.release(t); mImePositionProcessor.reset(); mSplitWindowManager.init(this, mInsetsState); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 838e37a905db..2bbd870f024d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -47,6 +47,8 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import dagger.Lazy; + import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; @@ -55,8 +57,6 @@ import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; -import dagger.Lazy; - /** * Controller to show/update compat UI components on Tasks based on whether the foreground * activities are in compatibility mode. @@ -284,13 +284,18 @@ public class CompatUIController implements OnDisplaysChangedListener, ShellTaskOrganizer.TaskListener taskListener) { CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId); if (layout != null) { - // UI already exists, update the UI layout. - if (!layout.updateCompatInfo(taskInfo, taskListener, - showOnDisplay(layout.getDisplayId()))) { - // The layout is no longer eligible to be shown, remove from active layouts. + if (layout.needsToBeRecreated(taskInfo, taskListener)) { mActiveCompatLayouts.remove(taskInfo.taskId); + layout.release(); + } else { + // UI already exists, update the UI layout. + if (!layout.updateCompatInfo(taskInfo, taskListener, + showOnDisplay(layout.getDisplayId()))) { + // The layout is no longer eligible to be shown, remove from active layouts. + mActiveCompatLayouts.remove(taskInfo.taskId); + } + return; } - return; } // Create a new UI layout. @@ -433,13 +438,18 @@ public class CompatUIController implements OnDisplaysChangedListener, private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { if (mActiveReachabilityEduLayout != null) { - // UI already exists, update the UI layout. - if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener, - showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) { - // The layout is no longer eligible to be shown, remove from active layouts. + if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) { + mActiveReachabilityEduLayout.release(); mActiveReachabilityEduLayout = null; + } else { + // UI already exists, update the UI layout. + if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener, + showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) { + // The layout is no longer eligible to be shown, remove from active layouts. + mActiveReachabilityEduLayout = null; + } + return; } - return; } // Create a new UI layout. final Context context = getOrCreateDisplayContext(taskInfo.displayId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index 659229228a57..d4778fa7a58a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -22,7 +22,6 @@ import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI; -import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.app.TaskInfo.CameraCompatControlState; @@ -53,9 +52,6 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; - @NonNull - private TaskInfo mTaskInfo; - // Remember the last reported states in case visibility changes due to keyguard or IME updates. @VisibleForTesting boolean mHasSizeCompat; @@ -77,7 +73,6 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration, Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) { super(context, taskInfo, syncQueue, taskListener, displayLayout); - mTaskInfo = taskInfo; mCallback = callback; mHasSizeCompat = taskInfo.topActivityInSizeCompat; mCameraCompatControlState = taskInfo.cameraCompatControlState; @@ -129,7 +124,6 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { @Override public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, boolean canShow) { - mTaskInfo = taskInfo; final boolean prevHasSizeCompat = mHasSizeCompat; final int prevCameraCompatControlState = mCameraCompatControlState; mHasSizeCompat = taskInfo.topActivityInSizeCompat; @@ -149,7 +143,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { /** Called when the restart button is clicked. */ void onRestartButtonClicked() { - mOnRestartButtonClicked.accept(Pair.create(mTaskInfo, getTaskListener())); + mOnRestartButtonClicked.accept(Pair.create(getLastTaskInfo(), getTaskListener())); } /** Called when the camera treatment button is clicked. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java index 9c4e79cd631b..180498c50c78 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java @@ -26,6 +26,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.content.Context; @@ -65,6 +66,9 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana private DisplayLayout mDisplayLayout; private final Rect mStableBounds; + @NonNull + private TaskInfo mTaskInfo; + /** * Utility class for adding and releasing a View hierarchy for this {@link * WindowlessWindowManager} to {@code mLeash}. @@ -83,6 +87,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout) { super(taskInfo.configuration, null /* rootSurface */, null /* hostInputToken */); + mTaskInfo = taskInfo; mContext = context; mSyncQueue = syncQueue; mTaskConfig = taskInfo.configuration; @@ -95,6 +100,17 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana } /** + * @return {@code true} if the instance of the specific {@link CompatUIWindowManagerAbstract} + * for the current task id needs to be recreated loading the related resources. This happens + * if the user switches between Light/Dark mode, if the device is docked/undocked or if the + * user switches between multi-window mode to fullscreen where the + * {@link ShellTaskOrganizer.TaskListener} implementation is different. + */ + boolean needsToBeRecreated(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { + return hasUiModeChanged(mTaskInfo, taskInfo) || hasTaskListenerChanged(taskListener); + } + + /** * Returns the z-order of this window which will be passed to the {@link SurfaceControl} once * {@link #attachToParentSurface} is called. * @@ -195,6 +211,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana @VisibleForTesting(visibility = PROTECTED) public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, boolean canShow) { + mTaskInfo = taskInfo; final Configuration prevTaskConfig = mTaskConfig; final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener; mTaskConfig = taskInfo.configuration; @@ -315,6 +332,11 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana updateSurfacePosition(); } + @Nullable + protected TaskInfo getLastTaskInfo() { + return mTaskInfo; + } + /** * Called following a change in the task bounds, display layout stable bounds, or the layout * direction. @@ -402,4 +424,12 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana protected final String getTag() { return getClass().getSimpleName(); } + + protected boolean hasTaskListenerChanged(ShellTaskOrganizer.TaskListener newTaskListener) { + return !mTaskListener.equals(newTaskListener); + } + + protected static boolean hasUiModeChanged(TaskInfo currentTaskInfo, TaskInfo newTaskInfo) { + return currentTaskInfo.configuration.uiMode != newTaskInfo.configuration.uiMode; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java index 959c50d5c640..9a67258ded2e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java @@ -19,7 +19,6 @@ package com.android.wm.shell.compatui; import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING; import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI; -import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.content.Context; @@ -69,9 +68,6 @@ class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { @VisibleForTesting LetterboxEduDialogLayout mLayout; - @NonNull - private TaskInfo mTaskInfo; - /** * The vertical margin between the dialog container and the task stable bounds (excluding * insets). @@ -99,7 +95,6 @@ class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { DialogAnimationController<LetterboxEduDialogLayout> animationController, DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) { super(context, taskInfo, syncQueue, taskListener, displayLayout); - mTaskInfo = taskInfo; mTransitions = transitions; mOnDismissCallback = onDismissCallback; mAnimationController = animationController; @@ -197,7 +192,7 @@ class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { mLayout.setDismissOnClickListener(null); mAnimationController.startExitAnimation(mLayout, () -> { release(); - mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener())); + mOnDismissCallback.accept(Pair.create(getLastTaskInfo(), getTaskListener())); }); } @@ -210,7 +205,6 @@ class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { @Override public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, boolean canShow) { - mTaskInfo = taskInfo; mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation; return super.updateCompatInfo(taskInfo, taskListener, canShow); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java index a18ab9154e01..95bb1fe1c986 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java @@ -20,7 +20,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI; -import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.content.Context; @@ -52,9 +51,6 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { private final ShellExecutor mMainExecutor; - @NonNull - private TaskInfo mTaskInfo; - private boolean mIsActivityLetterboxed; private int mLetterboxVerticalPosition; @@ -86,7 +82,6 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor) { super(context, taskInfo, syncQueue, taskListener, displayLayout); - mTaskInfo = taskInfo; mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled; mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition; mLetterboxHorizontalPosition = taskInfo.topActivityLetterboxHorizontalPosition; @@ -136,7 +131,6 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { @Override public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, boolean canShow) { - mTaskInfo = taskInfo; final boolean prevIsActivityLetterboxed = mIsActivityLetterboxed; final int prevLetterboxVerticalPosition = mLetterboxVerticalPosition; final int prevLetterboxHorizontalPosition = mLetterboxHorizontalPosition; @@ -222,14 +216,14 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { if (mLayout == null) { return; } - + final TaskInfo lastTaskInfo = getLastTaskInfo(); final boolean eligibleForDisplayHorizontalEducation = mForceUpdate - || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(mTaskInfo) + || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo) || (mHasUserDoubleTapped && (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION || mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION)); final boolean eligibleForDisplayVerticalEducation = mForceUpdate - || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(mTaskInfo) + || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo) || (mHasUserDoubleTapped && (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION || mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION)); @@ -241,7 +235,7 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { mLayout.handleVisibility(eligibleForDisplayHorizontalEducation, eligibleForDisplayVerticalEducation, mLetterboxVerticalPosition, mLetterboxHorizontalPosition, availableWidth, - availableHeight, mCompatUIConfiguration, mTaskInfo); + availableHeight, mCompatUIConfiguration, lastTaskInfo); if (!mHasLetterboxSizeChanged) { updateHideTime(); mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java index 51e5141a28af..a770da28fbd1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java @@ -19,7 +19,6 @@ package com.android.wm.shell.compatui; import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING; import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI; -import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.content.Context; @@ -67,9 +66,6 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { */ private final int mDialogVerticalMargin; - @NonNull - private TaskInfo mTaskInfo; - @Nullable @VisibleForTesting RestartDialogLayout mLayout; @@ -95,7 +91,6 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { DialogAnimationController<RestartDialogLayout> animationController, CompatUIConfiguration compatUIConfiguration) { super(context, taskInfo, syncQueue, taskListener, displayLayout); - mTaskInfo = taskInfo; mTransitions = transitions; mOnDismissCallback = onDismissCallback; mOnRestartCallback = onRestartCallback; @@ -125,7 +120,7 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { protected boolean eligibleToShowLayout() { // We don't show this dialog if the user has explicitly selected so clicking on a checkbox. return mRequestRestartDialog && !isTaskbarEduShowing() && (mLayout != null - || mCompatUIConfiguration.shouldShowRestartDialogAgain(mTaskInfo)); + || mCompatUIConfiguration.shouldShowRestartDialogAgain(getLastTaskInfo())); } @Override @@ -143,18 +138,6 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { mRequestRestartDialog = enabled; } - @Override - public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, - boolean canShow) { - mTaskInfo = taskInfo; - return super.updateCompatInfo(taskInfo, taskListener, canShow); - } - - boolean needsToBeRecreated(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { - return taskInfo.configuration.uiMode != mTaskInfo.configuration.uiMode - || !getTaskListener().equals(taskListener); - } - private void updateDialogMargins() { if (mLayout == null) { return; @@ -191,6 +174,7 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { // Dialog has already been released. return; } + final TaskInfo lastTaskInfo = getLastTaskInfo(); mLayout.setDismissOnClickListener(this::onDismiss); mLayout.setRestartOnClickListener(dontShowAgain -> { if (mLayout != null) { @@ -200,9 +184,9 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { }); } if (dontShowAgain) { - mCompatUIConfiguration.setDontShowRestartDialogAgain(mTaskInfo); + mCompatUIConfiguration.setDontShowRestartDialogAgain(lastTaskInfo); } - mOnRestartCallback.accept(Pair.create(mTaskInfo, getTaskListener())); + mOnRestartCallback.accept(Pair.create(lastTaskInfo, getTaskListener())); }); // Focus on the dialog title for accessibility. mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); @@ -216,7 +200,7 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { mLayout.setDismissOnClickListener(null); mAnimationController.startExitAnimation(mLayout, () -> { release(); - mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener())); + mOnDismissCallback.accept(Pair.create(getLastTaskInfo(), getTaskListener())); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 00cc57f0b99c..3ab175d3b68a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -23,6 +23,8 @@ import android.util.SparseArray import androidx.core.util.forEach import androidx.core.util.keyIterator import androidx.core.util.valueIterator +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.util.KtProtoLog import java.util.concurrent.Executor import java.util.function.Consumer @@ -140,6 +142,12 @@ class DesktopModeTaskRepository { val added = displayData.getOrCreate(displayId).activeTasks.add(taskId) if (added) { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTaskRepo: add active task=%d displayId=%d", + taskId, + displayId + ) activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) } } return added @@ -158,6 +166,9 @@ class DesktopModeTaskRepository { result = true } } + if (result) { + KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId) + } return result } @@ -221,6 +232,17 @@ class DesktopModeTaskRepository { displayData[displayId]?.visibleTasks?.remove(taskId) } val newCount = getVisibleTaskCount(displayId) + + if (prevCount != newCount) { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTaskRepo: update task visibility taskId=%d visible=%b displayId=%d", + taskId, + visible, + displayId + ) + } + // Check if count changed and if there was no tasks or this is the first task if (prevCount != newCount && (prevCount == 0 || newCount == 0)) { notifyVisibleTaskListeners(displayId, newCount > 0) @@ -244,6 +266,11 @@ class DesktopModeTaskRepository { * Add (or move if it already exists) the task to the top of the ordered list. */ fun addOrMoveFreeformTaskToTop(taskId: Int) { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTaskRepo: add or move task to top taskId=%d", + taskId + ) if (freeformTasksInZOrder.contains(taskId)) { freeformTasksInZOrder.remove(taskId) } @@ -254,6 +281,11 @@ class DesktopModeTaskRepository { * Remove the task from the ordered list. */ fun removeFreeformTask(taskId: Int) { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTaskRepo: remove freeform task from ordered list taskId=%d", + taskId + ) freeformTasksInZOrder.remove(taskId) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index b3109388da2c..91bb155d9d01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -39,7 +39,6 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerToken import android.window.WindowContainerTransaction import androidx.annotation.BinderThread -import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController @@ -56,6 +55,7 @@ import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.ShellSharedConstants import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.util.KtProtoLog import java.util.concurrent.Executor import java.util.function.Consumer @@ -91,7 +91,7 @@ class DesktopTasksController( } private fun onInit() { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController") + KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController") shellController.addExternalInterface( ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE, { createExternalInterface() }, @@ -102,7 +102,7 @@ class DesktopTasksController( /** Show all tasks, that are part of the desktop, on top of launcher */ fun showDesktopApps(displayId: Int) { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps") + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps") val wct = WindowContainerTransaction() // TODO(b/278084491): pass in display id bringDesktopAppsToFront(displayId, wct) @@ -130,8 +130,11 @@ class DesktopTasksController( /** Move a task to desktop */ fun moveToDesktop(task: RunningTaskInfo) { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId) - + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: moveToDesktop taskId=%d", + task.taskId + ) val wct = WindowContainerTransaction() // Bring other apps to front first bringDesktopAppsToFront(task.displayId, wct) @@ -147,10 +150,12 @@ class DesktopTasksController( * Moves a single task to freeform and sets the taskBounds to the passed in bounds, * startBounds */ - fun moveToFreeform( - taskInfo: RunningTaskInfo, - startBounds: Rect - ) { + fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: moveToFreeform with bounds taskId=%d", + taskInfo.taskId + ) val wct = WindowContainerTransaction() moveHomeTaskToFront(wct) addMoveToDesktopChanges(wct, taskInfo.getToken()) @@ -165,10 +170,12 @@ class DesktopTasksController( } /** Brings apps to front and sets freeform task bounds */ - private fun moveToDesktopWithAnimation( - taskInfo: RunningTaskInfo, - freeformBounds: Rect - ) { + private fun moveToDesktopWithAnimation(taskInfo: RunningTaskInfo, freeformBounds: Rect) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: moveToDesktop with animation taskId=%d", + taskInfo.taskId + ) val wct = WindowContainerTransaction() bringDesktopAppsToFront(taskInfo.displayId, wct) addMoveToDesktopChanges(wct, taskInfo.getToken()) @@ -190,7 +197,11 @@ class DesktopTasksController( /** Move a task to fullscreen */ fun moveToFullscreen(task: RunningTaskInfo) { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId) + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: moveToFullscreen taskId=%d", + task.taskId + ) val wct = WindowContainerTransaction() addMoveToFullscreenChanges(wct, task.token) @@ -206,6 +217,11 @@ class DesktopTasksController( * status bar area */ fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: cancelMoveToFreeform taskId=%d", + task.taskId + ) val wct = WindowContainerTransaction() addMoveToFullscreenChanges(wct, task.token) if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -218,6 +234,11 @@ class DesktopTasksController( } private fun moveToFullscreenWithAnimation(task: RunningTaskInfo, position: Point) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: moveToFullscreen with animation taskId=%d", + task.taskId + ) val wct = WindowContainerTransaction() addMoveToFullscreenChanges(wct, task.token) @@ -230,8 +251,14 @@ class DesktopTasksController( } } - /** Move a task to the front **/ + /** Move a task to the front */ fun moveTaskToFront(taskInfo: RunningTaskInfo) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: moveTaskToFront taskId=%d", + taskInfo.taskId + ) + val wct = WindowContainerTransaction() wct.reorder(taskInfo.token, true) if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -255,10 +282,10 @@ class DesktopTasksController( fun moveToNextDisplay(taskId: Int) { val task = shellTaskOrganizer.getRunningTaskInfo(taskId) if (task == null) { - ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId) + KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId) return } - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d", + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d", taskId, task.displayId) val displayIds = rootTaskDisplayAreaOrganizer.displayIds.sorted() @@ -269,7 +296,7 @@ class DesktopTasksController( newDisplayId = displayIds.firstOrNull { displayId -> displayId < task.displayId } } if (newDisplayId == null) { - ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found") + KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found") return } moveToDisplay(task, newDisplayId) @@ -281,17 +308,17 @@ class DesktopTasksController( * No-op if task is already on that display per [RunningTaskInfo.displayId]. */ private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d", + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d", task.taskId, displayId) if (task.displayId == displayId) { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display") + KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display") return } val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId) if (displayAreaInfo == null) { - ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found") + KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found") return } @@ -316,7 +343,7 @@ class DesktopTasksController( } private fun bringDesktopAppsToFront(displayId: Int, wct: WindowContainerTransaction) { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront") + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: bringDesktopAppsToFront") val activeTasks = desktopModeTaskRepository.getActiveTasks(displayId) // First move home to front and then other tasks on top of it @@ -397,9 +424,9 @@ class DesktopTasksController( if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) { // If there are any visible desktop tasks, switch the task to freeform if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { - ProtoLog.d( + KtProtoLog.d( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController#handleRequest: switch fullscreen task to freeform," + + "DesktopTasksController: switch fullscreen task to freeform on transition" + " taskId=%d", task.taskId ) @@ -414,9 +441,9 @@ class DesktopTasksController( // If no visible desktop tasks, switch this task to freeform as the transition came // outside of this controller if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { - ProtoLog.d( + KtProtoLog.d( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController#handleRequest: switch freeform task to fullscreen," + + "DesktopTasksController: switch freeform task to fullscreen oon transition" + " taskId=%d", task.taskId ) @@ -627,8 +654,6 @@ class DesktopTasksController( } } - - /** The interface for calls from outside the host process. */ @BinderThread private class IDesktopModeImpl(private var controller: DesktopTasksController?) : diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS index ccbb9cf298a2..a3803ed82844 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS @@ -1,3 +1,4 @@ # WM shell sub-module freeform owners atsjenk@google.com +jorgegil@google.com madym@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index 24d0b996a3cb..f51eb5299dd9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -180,6 +180,35 @@ public class PipBoundsAlgorithm { return null; } + + /** + * Returns the source hint rect if it is valid (if provided and is contained by the current + * task bounds, while not smaller than the destination bounds). + */ + @Nullable + public static Rect getValidSourceHintRect(PictureInPictureParams params, Rect sourceBounds, + Rect destinationBounds) { + Rect sourceRectHint = getValidSourceHintRect(params, sourceBounds); + if (!isSourceRectHintValidForEnterPip(sourceRectHint, destinationBounds)) { + sourceRectHint = null; + } + return sourceRectHint; + } + + /** + * This is a situation in which the source rect hint on at least one axis is smaller + * than the destination bounds, which represents a problem because we would have to scale + * up that axis to fit the bounds. So instead, just fallback to the non-source hint + * animation in this case. + * + * @return {@code false} if the given source is too small to use for the entering animation. + */ + static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) { + return sourceRectHint != null + && sourceRectHint.width() > destinationBounds.width() + && sourceRectHint.height() > destinationBounds.height(); + } + public float getDefaultAspectRatio() { return mDefaultAspectRatio; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 6cedcf534f3b..363d6759f8d0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -1657,8 +1657,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, "%s: Abort animation, invalid leash", TAG); return null; } - if (isInPipDirection(direction) - && !isSourceRectHintValidForEnterPip(sourceHintRect, destinationBounds)) { + if (isInPipDirection(direction) && !PipBoundsAlgorithm + .isSourceRectHintValidForEnterPip(sourceHintRect, destinationBounds)) { // The given source rect hint is too small for enter PiP animation, reset it to null. sourceHintRect = null; } @@ -1757,20 +1757,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** - * This is a situation in which the source rect hint on at least one axis is smaller - * than the destination bounds, which represents a problem because we would have to scale - * up that axis to fit the bounds. So instead, just fallback to the non-source hint - * animation in this case. - * - * @return {@code false} if the given source is too small to use for the entering animation. - */ - private boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) { - return sourceRectHint != null - && sourceRectHint.width() > destinationBounds.width() - && sourceRectHint.height() > destinationBounds.height(); - } - - /** * Sync with {@link SplitScreenController} on destination bounds if PiP is going to * split screen. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 98db707d1105..046d6fce443b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -470,6 +470,7 @@ public class PipTransition extends PipTransitionController { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange) { TransitionInfo.Change pipChange = pipTaskChange; + SurfaceControl activitySc = null; if (mCurrentPipTaskToken == null) { ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG); @@ -482,6 +483,7 @@ public class PipTransition extends PipTransitionController { if (mCurrentPipTaskToken.equals(change.getLastParent())) { // Find the activity that is exiting PiP. pipChange = change; + activitySc = change.getLeash(); break; } } @@ -498,17 +500,36 @@ public class PipTransition extends PipTransitionController { // case it may not be in the screen coordinate. // Reparent the pip leash to the root with max layer so that we can animate it outside of // parent crop, and make sure it is not covered by other windows. - final SurfaceControl pipLeash = pipChange.getLeash(); - final int rootIdx = TransitionUtil.rootIndexFor(pipChange, info); - startTransaction.reparent(pipLeash, info.getRoot(rootIdx).getLeash()); + final TransitionInfo.Root root = TransitionUtil.getRootFor(pipChange, info); + final SurfaceControl pipLeash; + if (activitySc != null) { + // Use a local leash to animate activity in case the activity has letterbox which may + // be broken by PiP animation, e.g. always end at 0,0 in parent and unable to include + // letterbox area in crop bounds. + final SurfaceControl activitySurface = pipChange.getLeash(); + pipLeash = new SurfaceControl.Builder() + .setName(activitySc + "_pip-leash") + .setContainerLayer() + .setHidden(false) + .setParent(root.getLeash()) + .build(); + startTransaction.reparent(activitySurface, pipLeash); + // Put the activity at local position with offset in case it is letterboxed. + final Point activityOffset = pipChange.getEndRelOffset(); + startTransaction.setPosition(activitySc, activityOffset.x, activityOffset.y); + } else { + pipLeash = pipChange.getLeash(); + startTransaction.reparent(pipLeash, root.getLeash()); + } startTransaction.setLayer(pipLeash, Integer.MAX_VALUE); // Note: because of this, the bounds to animate should be translated to the root coordinate. - final Point offset = info.getRoot(rootIdx).getOffset(); + final Point offset = root.getOffset(); final Rect currentBounds = mPipBoundsState.getBounds(); currentBounds.offset(-offset.x, -offset.y); startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top); final WindowContainerToken pipTaskToken = pipChange.getContainer(); + final boolean useLocalLeash = activitySc != null; final boolean toFullscreen = pipChange.getEndAbsBounds().equals( mPipBoundsState.getDisplayBounds()); mFinishCallback = (wct, wctCB) -> { @@ -518,6 +539,14 @@ public class PipTransition extends PipTransitionController { wct.setBounds(pipTaskToken, null); mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP); } + if (useLocalLeash) { + if (mPipAnimationController.isAnimating()) { + mPipAnimationController.getCurrentAnimator().end(); + } + // Make sure the animator don't use the released leash, e.g. mergeAnimation. + mPipAnimationController.resetAnimatorState(); + finishTransaction.remove(pipLeash); + } finishCallback.onTransitionFinished(wct, wctCB); }; mFinishTransaction = finishTransaction; @@ -545,7 +574,8 @@ public class PipTransition extends PipTransitionController { // Set the initial frame as scaling the end to the start. final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds()); destinationBounds.offset(-offset.x, -offset.y); - startTransaction.setWindowCrop(pipLeash, destinationBounds); + startTransaction.setWindowCrop(pipLeash, destinationBounds.width(), + destinationBounds.height()); mSurfaceTransactionHelper.scale(startTransaction, pipLeash, destinationBounds, currentBounds); startTransaction.apply(); @@ -764,7 +794,7 @@ public class PipTransition extends PipTransitionController { final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds(); int rotationDelta = deltaRotation(startRotation, endRotation); Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( - taskInfo.pictureInPictureParams, currentBounds); + taskInfo.pictureInPictureParams, currentBounds, destinationBounds); if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { // Need to get the bounds of new rotation in old rotation for fixed rotation, computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index af52350f5b48..c654c1b0d431 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -337,6 +337,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return isTaskInSplitScreen(taskId) && isSplitScreenVisible(); } + /** Check whether the task is the single-top root or the root of one of the stages. */ + public boolean isTaskRootOrStageRoot(int taskId) { + return mStageCoordinator.isRootOrStageRoot(taskId); + } + public @SplitPosition int getSplitPosition(int taskId) { return mStageCoordinator.getSplitPosition(taskId); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 5c2f1438c08e..749549d1ca55 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -377,6 +377,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return STAGE_TYPE_UNDEFINED; } + boolean isRootOrStageRoot(int taskId) { + if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskId) { + return true; + } + return mMainStage.isRootTaskId(taskId) || mSideStage.isRootTaskId(taskId); + } + boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition, WindowContainerTransaction wct) { prepareEnterSplitScreen(wct, task, stagePosition); @@ -1538,7 +1545,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } void finishEnterSplitScreen(SurfaceControl.Transaction t) { - mSplitLayout.init(); + mSplitLayout.update(t); setDividerVisibility(true, t); // Ensure divider surface are re-parented back into the hierarchy at the end of the // transition. See Transition#buildFinishTransaction for more detail. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 18b09b090794..e2e9270cd6cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -286,6 +286,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } } + boolean isRootTaskId(int taskId) { + return mRootTaskInfo != null && mRootTaskInfo.taskId == taskId; + } + void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY, boolean immediately) { if (mSplitDecorManager != null && mRootTaskInfo != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index 94190c74f3e9..d27933e2f800 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -453,7 +453,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { return; } - finishTransaction.reparent(mTaskLeash, null).apply(); + finishTransaction.reparent(mTaskLeash, null); if (mListener != null) { final int taskId = mTaskInfo.taskId; @@ -490,13 +490,11 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { if (mSurfaceCreated) { // Surface is ready, so just reparent the task to this surface control startTransaction.reparent(mTaskLeash, mSurfaceControl) - .show(mTaskLeash) - .apply(); + .show(mTaskLeash); // Also reparent on finishTransaction since the finishTransaction will reparent back // to its "original" parent by default. finishTransaction.reparent(mTaskLeash, mSurfaceControl) - .setPosition(mTaskLeash, 0, 0) - .apply(); + .setPosition(mTaskLeash, 0, 0); mTaskViewTransitions.updateBoundsState(this, mTaskViewBase.getCurrentBoundsOnScreen()); mTaskViewTransitions.updateVisibilityState(this, true /* visible */); wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index 689f9e1a3eda..fe2faaf79a1a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -363,7 +363,8 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { continue; } startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl()); - finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl()); + finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl()) + .setPosition(chg.getLeash(), 0, 0); changesHandled++; } } @@ -377,7 +378,6 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { } // No animation, just show it immediately. startTransaction.apply(); - finishTransaction.apply(); finishCallback.onTransitionFinished(wct, null /* wctCB */); startNextTransition(); return true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 1ee52ae00645..21dca95d056a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -24,6 +24,7 @@ import static android.app.ActivityOptions.ANIM_SCALE_UP; import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE; @@ -36,12 +37,8 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; -import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_RELAUNCH; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; @@ -92,7 +89,6 @@ import android.view.Choreographer; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowManager; -import android.view.WindowManager.TransitionType; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Transformation; @@ -329,6 +325,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { @ColorInt int backgroundColorForTransition = 0; final int wallpaperTransit = getWallpaperTransitType(info); boolean isDisplayRotationAnimationStarted = false; + final boolean isDreamTransition = isDreamTransition(info); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY @@ -343,9 +341,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { continue; } final boolean isTask = change.getTaskInfo() != null; + final int mode = change.getMode(); boolean isSeamlessDisplayChange = false; - if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) { + if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) { if (info.getType() == TRANSIT_CHANGE) { final int anim = getRotationAnimationHint(change, info, mDisplayController); isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS; @@ -361,7 +360,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - if (change.getMode() == TRANSIT_CHANGE) { + if (mode == TRANSIT_CHANGE) { // If task is child task, only set position in parent and update crop when needed. if (isTask && change.getParent() != null && info.getChange(change.getParent()).getTaskInfo() != null) { @@ -410,8 +409,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // Hide the invisible surface directly without animating it if there is a display // rotation animation playing. - if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType( - change.getMode())) { + if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType(mode)) { startTransaction.hide(change.getLeash()); continue; } @@ -424,16 +422,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // Don't animate anything that isn't independent. if (!TransitionInfo.isIndependent(change, info)) continue; - Animation a = loadAnimation(info, change, wallpaperTransit); + Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition); if (a != null) { if (isTask) { - final @TransitionType int type = info.getType(); - final boolean isOpenOrCloseTransition = type == TRANSIT_OPEN - || type == TRANSIT_CLOSE - || type == TRANSIT_TO_FRONT - || type == TRANSIT_TO_BACK; final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0; - if (isOpenOrCloseTransition && !isTranslucent + if (!isTranslucent && TransitionUtil.isOpenOrCloseMode(mode) + && TransitionUtil.isOpenOrCloseMode(info.getType()) && wallpaperTransit == WALLPAPER_TRANSITION_NONE) { // Use the overview background as the background for the animation final Context uiContext = ActivityThread.currentActivityThread() @@ -458,7 +452,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { backgroundColorForTransition); if (!isTask && a.hasExtension()) { - if (!TransitionUtil.isOpeningType(change.getMode())) { + if (!TransitionUtil.isOpeningType(mode)) { // Can screenshot now (before startTransaction is applied) edgeExtendWindow(change, a, startTransaction, finishTransaction); } else { @@ -469,7 +463,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - final Rect clipRect = TransitionUtil.isClosingType(change.getMode()) + final Rect clipRect = TransitionUtil.isClosingType(mode) ? new Rect(mRotator.getEndBoundsInStartRotation(change)) : new Rect(change.getEndAbsBounds()); clipRect.offsetTo(0, 0); @@ -519,6 +513,18 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return true; } + private static boolean isDreamTransition(@NonNull TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() != null + && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM) { + return true; + } + } + + return false; + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @@ -572,7 +578,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { @Nullable private Animation loadAnimation(@NonNull TransitionInfo info, - @NonNull TransitionInfo.Change change, int wallpaperTransit) { + @NonNull TransitionInfo.Change change, int wallpaperTransit, + boolean isDreamTransition) { Animation a; final int type = info.getType(); @@ -630,7 +637,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // If there's a scene-transition, then jump-cut. return null; } else { - a = loadAttributeAnimation(info, change, wallpaperTransit, mTransitionAnimation); + a = loadAttributeAnimation( + info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition); } if (a != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java index 0cede902f034..e27e4f990407 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java @@ -28,7 +28,6 @@ import android.os.Trace; import android.util.Log; import com.android.internal.util.TraceBuffer; -import com.android.wm.shell.nano.HandlerMapping; import com.android.wm.shell.sysui.ShellCommandHandler; import com.google.protobuf.nano.MessageNano; @@ -41,6 +40,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Queue; +import java.util.concurrent.TimeUnit; /** * Helper class to collect and dump transition traces. @@ -241,6 +241,10 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler { new com.android.wm.shell.nano.WmShellTransitionTraceProto(); proto.magicNumber = MAGIC_NUMBER_VALUE; writeHandlerMappingToProto(proto); + long timeOffsetNs = + TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()) + - SystemClock.elapsedRealtimeNanos(); + proto.realToElapsedTimeOffsetNanos = timeOffsetNs; int pid = android.os.Process.myPid(); LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath() + " from process " + pid); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index 0f4645c0fdab..19d8384ace41 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -18,7 +18,6 @@ package com.android.wm.shell.transition; import static android.app.ActivityOptions.ANIM_FROM_STYLE; import static android.app.ActivityOptions.ANIM_NONE; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -63,7 +62,7 @@ public class TransitionAnimationHelper { @Nullable public static Animation loadAttributeAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, int wallpaperTransit, - @NonNull TransitionAnimation transitionAnimation) { + @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) { final int type = info.getType(); final int changeMode = change.getMode(); final int changeFlags = change.getFlags(); @@ -71,11 +70,9 @@ public class TransitionAnimationHelper { final boolean isTask = change.getTaskInfo() != null; final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); final int overrideType = options != null ? options.getType() : ANIM_NONE; - final boolean isDream = - isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM; int animAttr = 0; boolean translucent = false; - if (isDream) { + if (isDreamTransition) { if (type == TRANSIT_OPEN) { animAttr = enter ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index fda943d7dc02..ab27c55f20dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -501,7 +501,9 @@ public class Transitions implements RemoteCallable<Transitions>, */ private static void setupAnimHierarchy(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { - boolean isOpening = isOpeningType(info.getType()); + final int type = info.getType(); + final boolean isOpening = isOpeningType(type); + final boolean isClosing = isClosingType(type); for (int i = 0; i < info.getRootCount(); ++i) { t.show(info.getRoot(i).getLeash()); } @@ -554,7 +556,13 @@ public class Transitions implements RemoteCallable<Transitions>, layer = zSplitLine + numChanges - i; } } else { // CHANGE or other - layer = zSplitLine + numChanges - i; + if (isClosing) { + // Put below CLOSE mode. + layer = zSplitLine - i; + } else { + // Put above CLOSE mode. + layer = zSplitLine + numChanges - i; + } } t.setLayer(leash, layer); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt new file mode 100644 index 000000000000..9b48a542720c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.util + +import android.util.Log +import com.android.internal.protolog.common.IProtoLogGroup +import com.android.wm.shell.protolog.ShellProtoLogImpl + +/** + * Log messages using an API similar to [com.android.internal.protolog.common.ProtoLog]. Useful for + * logging from Kotlin classes as ProtoLog does not have support for Kotlin. + * + * All messages are logged to logcat if logging is enabled for that [IProtoLogGroup]. + */ +// TODO(b/168581922): remove once ProtoLog adds support for Kotlin +class KtProtoLog { + companion object { + /** @see [com.android.internal.protolog.common.ProtoLog.d] */ + fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) { + if (ShellProtoLogImpl.isEnabled(group)) { + Log.d(group.tag, String.format(messageString, *args)) + } + } + + /** @see [com.android.internal.protolog.common.ProtoLog.v] */ + fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) { + if (ShellProtoLogImpl.isEnabled(group)) { + Log.v(group.tag, String.format(messageString, *args)) + } + } + + /** @see [com.android.internal.protolog.common.ProtoLog.i] */ + fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) { + if (ShellProtoLogImpl.isEnabled(group)) { + Log.i(group.tag, String.format(messageString, *args)) + } + } + + /** @see [com.android.internal.protolog.common.ProtoLog.w] */ + fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) { + if (ShellProtoLogImpl.isEnabled(group)) { + Log.w(group.tag, String.format(messageString, *args)) + } + } + + /** @see [com.android.internal.protolog.common.ProtoLog.e] */ + fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) { + if (ShellProtoLogImpl.isEnabled(group)) { + Log.e(group.tag, String.format(messageString, *args)) + } + } + + /** @see [com.android.internal.protolog.common.ProtoLog.wtf] */ + fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) { + if (ShellProtoLogImpl.isEnabled(group)) { + Log.wtf(group.tag, String.format(messageString, *args)) + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java index 7d05c0e62e13..402b0ce7c87c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java @@ -68,6 +68,12 @@ public class TransitionUtil { return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK; } + /** Returns {@code true} if the transition is opening or closing mode. */ + public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) { + return mode == TRANSIT_OPEN || mode == TRANSIT_CLOSE + || mode == TRANSIT_TO_FRONT || mode == TRANSIT_TO_BACK; + } + /** Returns {@code true} if the transition has a display change. */ public static boolean hasDisplayChange(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { @@ -311,7 +317,7 @@ public class TransitionUtil { private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change, SurfaceControl leash) { - return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */, + return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()), leash, false /* isTranslucent */, null /* clipRect */, null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */, new android.graphics.Point(0, 0) /* position */, change.getStartAbsBounds(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 2bb3cceb257f..39fb7936747e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; -import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.util.SparseArray; @@ -186,8 +185,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); - final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback( - windowDecoration, taskInfo); + final DragPositioningCallback dragPositioningCallback = + new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController, + null /* disallowedAreaForEndBounds */); final CaptionTouchEventListener touchEventListener = new CaptionTouchEventListener(taskInfo, dragPositioningCallback); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); @@ -198,17 +198,6 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { setupCaptionColor(taskInfo, windowDecoration); } - private FluidResizeTaskPositioner createDragPositioningCallback( - CaptionWindowDecoration windowDecoration, RunningTaskInfo taskInfo) { - final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width(); - final int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId) - .stableInsets().top; - final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth, - statusBarHeight); - return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, - mDisplayController, disallowedAreaForEndBounds); - } - private class CaptionTouchEventListener implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 0b821844e46c..54babce13205 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -760,6 +760,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; + if (mSplitScreenController.isPresent() + && mSplitScreenController.get().isTaskRootOrStageRoot(taskInfo.taskId)) { + return false; + } return DesktopModeStatus.isProto2Enabled() && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD && mDisplayController.getDisplayContext(taskInfo.displayId) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java index 9bcb77f03abd..9f79d212a7b9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java @@ -21,6 +21,8 @@ import android.graphics.Rect; import android.view.SurfaceControl; import android.window.WindowContainerTransaction; +import androidx.annotation.Nullable; + import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -42,24 +44,24 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { private final Rect mRepositionTaskBounds = new Rect(); // If a task move (not resize) finishes in this region, the positioner will not attempt to // finalize the bounds there using WCT#setBounds - private final Rect mDisallowedAreaForEndBounds = new Rect(); + private final Rect mDisallowedAreaForEndBounds; private boolean mHasDragResized; private int mCtrlType; FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, - DisplayController displayController, Rect disallowedAreaForEndBounds) { + DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds) { this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds, dragStartListener -> {}, SurfaceControl.Transaction::new); } FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, - DisplayController displayController, Rect disallowedAreaForEndBounds, + DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds, DragPositioningCallbackUtility.DragStartListener dragStartListener, Supplier<SurfaceControl.Transaction> supplier) { mTaskOrganizer = taskOrganizer; mWindowDecoration = windowDecoration; mDisplayController = displayController; - mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds); + mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds); mDragStartListener = dragStartListener; mTransactionSupplier = supplier; } diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index 64dfc3ef845d..deebad545c5e 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -8,3 +8,4 @@ madym@google.com hwwang@google.com chenghsiuchang@google.com atsjenk@google.com +jorgegil@google.com diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index 4de529885565..55781f1b4d6f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager; import android.app.TaskInfo; +import android.content.res.Configuration; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.util.Pair; @@ -455,12 +456,21 @@ public class CompatUIWindowManagerTest extends ShellTestCase { verify(mLayout).setCameraCompatHintVisibility(/* show= */ true); } + @Test + public void testWhenDockedStateHasChanged_needsToBeRecreated() { + ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); + newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK; + + Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener)); + } + private static TaskInfo createTaskInfo(boolean hasSizeCompat, @TaskInfo.CameraCompatControlState int cameraCompatControlState) { ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.taskId = TASK_ID; taskInfo.topActivityInSizeCompat = hasSizeCompat; taskInfo.cameraCompatControlState = cameraCompatControlState; + taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK; return taskInfo; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java index 5bcc72e73cb9..973a99c269ea 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNull; import android.app.ActivityManager; import android.app.TaskInfo; +import android.content.res.Configuration; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; @@ -31,6 +32,8 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; +import junit.framework.Assert; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,63 +49,61 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @SmallTest public class ReachabilityEduWindowManagerTest extends ShellTestCase { - - private static final int USER_ID = 1; - private static final int TASK_ID = 1; - @Mock private SyncTransactionQueue mSyncTransactionQueue; @Mock private ShellTaskOrganizer.TaskListener mTaskListener; @Mock - private CompatUIController.CompatUICallback mCallback; - @Mock private CompatUIConfiguration mCompatUIConfiguration; @Mock private DisplayLayout mDisplayLayout; - private TestShellExecutor mExecutor; + private TaskInfo mTaskInfo; + private ReachabilityEduWindowManager mWindowManager; @Before public void setUp() { MockitoAnnotations.initMocks(this); mExecutor = new TestShellExecutor(); + mTaskInfo = new ActivityManager.RunningTaskInfo(); + mTaskInfo.configuration.uiMode = + (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK) + | Configuration.UI_MODE_NIGHT_NO; + mTaskInfo.configuration.uiMode = + (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_TYPE_MASK) + | Configuration.UI_MODE_TYPE_NORMAL; + mWindowManager = createReachabilityEduWindowManager(mTaskInfo); } @Test public void testCreateLayout_notEligible_doesNotCreateLayout() { - final ReachabilityEduWindowManager windowManager = createReachabilityEduWindowManager( - createTaskInfo(/* userId= */ USER_ID, /*isLetterboxDoubleTapEnabled */ false)); - - assertFalse(windowManager.createLayout(/* canShow= */ true)); + assertFalse(mWindowManager.createLayout(/* canShow= */ true)); - assertNull(windowManager.mLayout); + assertNull(mWindowManager.mLayout); } - private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) { - return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue, - mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor); + @Test + public void testWhenDockedStateHasChanged_needsToBeRecreated() { + ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); + newTaskInfo.configuration.uiMode = + (newTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_TYPE_MASK) + | Configuration.UI_MODE_TYPE_DESK; + + Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener)); } - private static TaskInfo createTaskInfo(int userId, boolean isLetterboxDoubleTapEnabled) { - return createTaskInfo(userId, /* isLetterboxDoubleTapEnabled */ isLetterboxDoubleTapEnabled, - /* topActivityLetterboxVerticalPosition */ -1, - /* topActivityLetterboxHorizontalPosition */ -1, - /* topActivityLetterboxWidth */ -1, - /* topActivityLetterboxHeight */ -1); + @Test + public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() { + ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); + mTaskInfo.configuration.uiMode = + (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK) + | Configuration.UI_MODE_NIGHT_YES; + + Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener)); } - private static TaskInfo createTaskInfo(int userId, boolean isLetterboxDoubleTapEnabled, - int topActivityLetterboxVerticalPosition, int topActivityLetterboxHorizontalPosition, - int topActivityLetterboxWidth, int topActivityLetterboxHeight) { - ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); - taskInfo.userId = userId; - taskInfo.taskId = TASK_ID; - taskInfo.isLetterboxDoubleTapEnabled = isLetterboxDoubleTapEnabled; - taskInfo.topActivityLetterboxVerticalPosition = topActivityLetterboxVerticalPosition; - taskInfo.topActivityLetterboxHorizontalPosition = topActivityLetterboxHorizontalPosition; - taskInfo.topActivityLetterboxWidth = topActivityLetterboxWidth; - taskInfo.topActivityLetterboxHeight = topActivityLetterboxHeight; - return taskInfo; + private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) { + return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue, + mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java new file mode 100644 index 000000000000..9f109a1d0f50 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui; + +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.content.res.Configuration; +import android.testing.AndroidTestingRunner; +import android.util.Pair; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.transition.Transitions; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.Consumer; + +/** + * Tests for {@link RestartDialogWindowManager}. + * + * Build/Install/Run: + * atest WMShellUnitTests:RestartDialogWindowManagerTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class RestartDialogWindowManagerTest extends ShellTestCase { + + @Mock + private SyncTransactionQueue mSyncTransactionQueue; + @Mock private ShellTaskOrganizer.TaskListener mTaskListener; + @Mock private CompatUIConfiguration mCompatUIConfiguration; + @Mock private Transitions mTransitions; + @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartCallback; + @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback; + private RestartDialogWindowManager mWindowManager; + private TaskInfo mTaskInfo; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTaskInfo = new ActivityManager.RunningTaskInfo(); + mTaskInfo.configuration.uiMode = + (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK) + | Configuration.UI_MODE_NIGHT_NO; + mTaskInfo.configuration.uiMode = + (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_TYPE_MASK) + | Configuration.UI_MODE_TYPE_NORMAL; + mWindowManager = new RestartDialogWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, + mTaskListener, new DisplayLayout(), mTransitions, mOnRestartCallback, + mOnDismissCallback, mCompatUIConfiguration); + } + + @Test + public void testWhenDockedStateHasChanged_needsToBeRecreated() { + ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); + newTaskInfo.configuration.uiMode = + (newTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_TYPE_MASK) + | Configuration.UI_MODE_TYPE_DESK; + + Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener)); + } + + @Test + public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() { + ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); + mTaskInfo.configuration.uiMode = + (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK) + | Configuration.UI_MODE_NIGHT_YES; + + Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener)); + } +} diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 88e351963148..e21d6fb2fe14 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -45,14 +45,13 @@ const ui::Transform kIdentityTransform; // --- PointerController::DisplayInfoListener --- void PointerController::DisplayInfoListener::onWindowInfosChanged( - const std::vector<android::gui::WindowInfo>&, - const std::vector<android::gui::DisplayInfo>& displayInfos) { + const gui::WindowInfosUpdate& update) { std::scoped_lock lock(mLock); if (mPointerController == nullptr) return; // PointerController uses DisplayInfoListener's lock. base::ScopedLockAssertion assumeLocked(mPointerController->getLock()); - mPointerController->onDisplayInfosChangedLocked(displayInfos); + mPointerController->onDisplayInfosChangedLocked(update.displayInfos); } void PointerController::DisplayInfoListener::onPointerControllerDestroyed() { diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index ca14b6e9bfdc..62ee74331302 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -19,6 +19,7 @@ #include <PointerControllerInterface.h> #include <gui/DisplayEventReceiver.h> +#include <gui/WindowInfosUpdate.h> #include <input/DisplayViewport.h> #include <input/Input.h> #include <utils/BitSet.h> @@ -114,8 +115,7 @@ private: class DisplayInfoListener : public gui::WindowInfosListener { public: explicit DisplayInfoListener(PointerController* pc) : mPointerController(pc){}; - void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>&, - const std::vector<android::gui::DisplayInfo>&) override; + void onWindowInfosChanged(const gui::WindowInfosUpdate&) override; void onPointerControllerDestroyed(); // This lock is also used by PointerController. See PointerController::getLock(). diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 2378d42793a1..85747514aa03 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -343,7 +343,7 @@ TEST_F(PointerControllerWindowInfoListenerTest, localListenerCopy = registeredListener; } EXPECT_EQ(nullptr, registeredListener) << "WindowInfosListener was not unregistered"; - localListenerCopy->onWindowInfosChanged({}, {}); + localListenerCopy->onWindowInfosChanged({{}, {}, 0, 0}); } } // namespace android diff --git a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl index 531b3ae0c230..bc6a2591f386 100644 --- a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl @@ -80,14 +80,16 @@ interface ISoundTriggerMiddlewareService { * This implies that the caller must clear its caller identity to protect from the case where * it resides in the same process as the callee. * - The identity of the entity on behalf of which module operations are to be performed. - * + * @param isTrusted - {@code true} if the middleware should not audit data delivery, since the + * callback is being delivered to another trusted component which will audit access. * listModules() must be called prior to calling this method and the provided handle must be * one of the handles from the returned list. */ ISoundTriggerModule attachAsMiddleman(int handle, in Identity middlemanIdentity, in Identity originatorIdentity, - ISoundTriggerCallback callback); + ISoundTriggerCallback callback, + boolean isTrusted); /** * Attach an injection interface interface to the ST mock HAL. diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java index 0289aa3b203d..3394dd05ef8c 100644 --- a/media/java/android/media/audiopolicy/AudioProductStrategy.java +++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java @@ -203,10 +203,16 @@ public final class AudioProductStrategy implements Parcelable { AudioProductStrategy thatStrategy = (AudioProductStrategy) o; - return mName == thatStrategy.mName && mId == thatStrategy.mId + return mId == thatStrategy.mId + && Objects.equals(mName, thatStrategy.mName) && Arrays.equals(mAudioAttributesGroups, thatStrategy.mAudioAttributesGroups); } + @Override + public int hashCode() { + return Objects.hash(mId, mName, Arrays.hashCode(mAudioAttributesGroups)); + } + /** * @param name of the product strategy * @param id of the product strategy @@ -460,6 +466,12 @@ public final class AudioProductStrategy implements Parcelable { && Arrays.equals(mAudioAttributes, thatAag.mAudioAttributes); } + @Override + public int hashCode() { + return Objects.hash(mVolumeGroupId, mLegacyStreamType, + Arrays.hashCode(mAudioAttributes)); + } + public int getStreamType() { return mLegacyStreamType; } diff --git a/media/java/android/media/tv/AdBuffer.java b/media/java/android/media/tv/AdBuffer.java index 230d763a948d..f2b772fff1d3 100644 --- a/media/java/android/media/tv/AdBuffer.java +++ b/media/java/android/media/tv/AdBuffer.java @@ -22,6 +22,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.SharedMemory; +import java.io.IOException; + /** * Buffer for advertisement data. */ @@ -57,6 +59,16 @@ public final class AdBuffer implements Parcelable { this.mFlags = flags; } + /** @hide **/ + public static AdBuffer dupAdBuffer(AdBuffer buffer) throws IOException { + if (buffer == null) { + return null; + } + return new AdBuffer(buffer.mId, buffer.mMimeType, + SharedMemory.fromFileDescriptor(buffer.mBuffer.getFdDup()), buffer.mOffset, + buffer.mLength, buffer.mPresentationTimeUs, buffer.mFlags); + } + /** * Gets corresponding AD request ID. * diff --git a/media/java/android/media/tv/AdRequest.java b/media/java/android/media/tv/AdRequest.java index d8cddfcbcb9e..5f6b2b5edee6 100644 --- a/media/java/android/media/tv/AdRequest.java +++ b/media/java/android/media/tv/AdRequest.java @@ -72,6 +72,22 @@ public final class AdRequest implements Parcelable { private final Bundle mMetadata; private final Uri mUri; + /** + * The key for video metadata. + * + * @see #getMetadata() + * @hide + */ + public static final String KEY_VIDEO_METADATA = "key_video_metadata"; + + /** + * The key for audio metadata. + * + * @see #getMetadata() + * @hide + */ + public static final String KEY_AUDIO_METADATA = "key_audio_metadata"; + public AdRequest(int id, @RequestType int requestType, @Nullable ParcelFileDescriptor fileDescriptor, long startTime, long stopTime, long echoInterval, @Nullable String mediaFileType, @NonNull Bundle metadata) { diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index f344fd3e797b..631ab9a2693d 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -3737,6 +3737,10 @@ public final class TvInputManager { mService.notifyAdBufferReady(mToken, buffer, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } finally { + if (buffer != null) { + buffer.getSharedMemory().close(); + } } } diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java index 06d1acd4567c..7cce84a1ee16 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java @@ -1608,6 +1608,10 @@ public final class TvInteractiveAppManager { mService.notifyAdBufferConsumed(mToken, buffer, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } finally { + if (buffer != null) { + buffer.getSharedMemory().close(); + } } } diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index 06dfe4fffefe..ec85cc7af499 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -69,6 +69,7 @@ import android.widget.FrameLayout; import com.android.internal.os.SomeArgs; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -1973,9 +1974,9 @@ public abstract class TvInteractiveAppService extends Service { "notifyAdBufferReady(buffer=" + buffer + ")"); } if (mSessionCallback != null) { - mSessionCallback.onAdBufferReady(buffer); + mSessionCallback.onAdBufferReady(AdBuffer.dupAdBuffer(buffer)); } - } catch (RemoteException e) { + } catch (RemoteException | IOException e) { Log.w(TAG, "error in notifyAdBuffer", e); } } diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java index b66545a6558e..266faae489dd 100644 --- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java @@ -29,11 +29,14 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.audiopolicy.AudioProductStrategy; import android.media.audiopolicy.AudioVolumeGroup; +import android.os.Parcel; import android.platform.test.annotations.Presubmit; import android.util.Log; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.testing.EqualsTester; + import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -231,4 +234,26 @@ public class AudioProductStrategyTest { } } } + + @Test + public void testEquals() { + final EqualsTester equalsTester = new EqualsTester(); + + AudioProductStrategy.getAudioProductStrategies().forEach( + strategy -> equalsTester.addEqualityGroup(strategy, + writeToAndFromParcel(strategy))); + + equalsTester.testEquals(); + } + + private static AudioProductStrategy writeToAndFromParcel( + AudioProductStrategy audioProductStrategy) { + Parcel parcel = Parcel.obtain(); + audioProductStrategy.writeToParcel(parcel, /*flags=*/0); + parcel.setDataPosition(0); + AudioProductStrategy unmarshalledAudioProductStrategy = + AudioProductStrategy.CREATOR.createFromParcel(parcel); + parcel.recycle(); + return unmarshalledAudioProductStrategy; + } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 0f5280efcdc0..78ee81921f25 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -49,6 +49,7 @@ class CredentialManagerRepo( private val context: Context, intent: Intent, userConfigRepo: UserConfigRepo, + isNewActivity: Boolean, ) { val requestInfo: RequestInfo? private val providerEnabledList: List<ProviderData> @@ -125,7 +126,8 @@ class CredentialManagerRepo( isPasskeyFirstUse = isPasskeyFirstUse, )!!, getCredentialUiState = null, - cancelRequestState = cancelUiRequestState + cancelRequestState = cancelUiRequestState, + isInitialRender = isNewActivity, ) } RequestInfo.TYPE_GET -> { @@ -140,7 +142,8 @@ class CredentialManagerRepo( if (autoSelectEntry == null) ProviderActivityState.NOT_APPLICABLE else ProviderActivityState.READY_TO_LAUNCH, isAutoSelectFlow = autoSelectEntry != null, - cancelRequestState = cancelUiRequestState + cancelRequestState = cancelUiRequestState, + isInitialRender = isNewActivity, ) } else -> { @@ -149,6 +152,7 @@ class CredentialManagerRepo( createCredentialUiState = null, getCredentialUiState = null, cancelRequestState = cancelUiRequestState, + isInitialRender = isNewActivity, ) } else { throw IllegalStateException("Unrecognized request type: ${requestInfo?.type}") diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index 54a8678df9cc..c409ba6aa78e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -62,7 +62,8 @@ class CredentialSelectorActivity : ComponentActivity() { return } val userConfigRepo = UserConfigRepo(this) - val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo) + val credManRepo = CredentialManagerRepo( + this, intent, userConfigRepo, isNewActivity = true) val backPressedCallback = object : OnBackPressedCallback( true // default to enabled @@ -103,7 +104,8 @@ class CredentialSelectorActivity : ComponentActivity() { } } else { val userConfigRepo = UserConfigRepo(this) - val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo) + val credManRepo = CredentialManagerRepo( + this, intent, userConfigRepo, isNewActivity = false) viewModel.onNewCredentialManagerRepo(credManRepo) } } catch (e: Exception) { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index 081490e50907..e96e536c00d5 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -53,6 +53,7 @@ data class UiState( // launched immediately, and canceling it will cancel the whole UI flow. val isAutoSelectFlow: Boolean = false, val cancelRequestState: CancelUiRequestState?, + val isInitialRender: Boolean, ) data class CancelUiRequestState( @@ -82,6 +83,10 @@ class CredentialSelectorViewModel( uiState = uiState.copy(dialogState = DialogState.COMPLETE) } + fun onInitialRenderComplete() { + uiState = uiState.copy(isInitialRender = false) + } + fun onCancellationUiRequested(appDisplayName: String?) { uiState = uiState.copy(cancelRequestState = CancelUiRequestState(appDisplayName)) } @@ -96,7 +101,7 @@ class CredentialSelectorViewModel( fun onNewCredentialManagerRepo(credManRepo: CredentialManagerRepo) { this.credManRepo = credManRepo - uiState = credManRepo.initState() + uiState = credManRepo.initState().copy(isInitialRender = false) if (this.credManRepo.requestInfo?.token != credManRepo.requestInfo?.token) { this.uiMetrics.resetInstanceId() @@ -114,7 +119,12 @@ class CredentialSelectorViewModel( uiState = uiState.copy(providerActivityState = ProviderActivityState.PENDING) val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent) .setFillInIntent(entry.fillInIntent).build() - launcher.launch(intentSenderRequest) + try { + launcher.launch(intentSenderRequest) + } catch (e: Exception) { + Log.w(Constants.LOG_TAG, "Failed to launch provider UI: $e") + onInternalError() + } } else { Log.d(Constants.LOG_TAG, "No provider UI to launch") onInternalError() diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index a35310cdad95..ba20a5e779fd 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -213,7 +213,7 @@ class GetFlowUtils { } } return com.android.credentialmanager.getflow.RequestDisplayInfo( - appName = originName + appName = originName?.ifEmpty { null } ?: getAppLabel(context.packageManager, requestInfo.appPackageName) ?: return null, preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials, @@ -286,7 +286,8 @@ class GetFlowUtils { pendingIntent = credentialEntry.pendingIntent, fillInIntent = it.frameworkExtrasIntent, credentialType = CredentialType.UNKNOWN, - credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), + credentialTypeDisplayName = + credentialEntry.typeDisplayName?.toString().orEmpty(), userName = credentialEntry.title.toString(), displayName = credentialEntry.subtitle?.toString(), icon = credentialEntry.icon.loadDrawable(context), @@ -461,7 +462,7 @@ class CreateFlowUtils { if (requestInfo == null) { return null } - val appLabel = originName + val appLabel = originName?.ifEmpty { null } ?: getAppLabel(context.packageManager, requestInfo.appPackageName) ?: return null val createCredentialRequest = requestInfo.createCredentialRequest ?: return null @@ -660,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/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt index 339968158a91..bd4375f4513e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt @@ -20,10 +20,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.ColumnScope import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -40,9 +36,10 @@ import kotlinx.coroutines.launch @Composable fun ModalBottomSheet( sheetContent: @Composable ColumnScope.() -> Unit, - onDismiss: () -> Unit + onDismiss: () -> Unit, + isInitialRender: Boolean, + onInitialRenderComplete: () -> Unit, ) { - var isInitialRender by remember { mutableStateOf(true) } val scope = rememberCoroutineScope() val state = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Hidden, @@ -64,7 +61,7 @@ fun ModalBottomSheet( LaunchedEffect(state.currentValue) { if (state.currentValue == ModalBottomSheetValue.Hidden) { if (isInitialRender) { - isInitialRender = false + onInitialRenderComplete() scope.launch { state.show() } } else { onDismiss() diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index d16120fee452..a378f1c16753 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -164,7 +164,9 @@ fun CreateCredentialScreen( } } }, - onDismiss = viewModel::onUserCancel + onDismiss = viewModel::onUserCancel, + isInitialRender = viewModel.uiState.isInitialRender, + onInitialRenderComplete = viewModel::onInitialRenderComplete, ) } @@ -436,7 +438,8 @@ fun CreationSelectionCard( }, ) } - if (createOptionInfo.footerDescription != null) { + val footerDescription = createOptionInfo.footerDescription + if (footerDescription != null && footerDescription.length > 0) { item { Divider( thickness = 1.dp, @@ -446,7 +449,7 @@ fun CreationSelectionCard( } item { Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) { - BodySmallText(text = createOptionInfo.footerDescription) + BodySmallText(text = footerDescription) } } } 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/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 31af7aa78c69..40c99ee07d69 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -148,6 +148,8 @@ fun GetCredentialScreen( } }, onDismiss = viewModel::onUserCancel, + isInitialRender = viewModel.uiState.isInitialRender, + onInitialRenderComplete = viewModel::onInitialRenderComplete, ) } } diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp index e9000a8e4b0f..df43863fdd84 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp @@ -27,6 +27,7 @@ android_library { "//apex_available:platform", "com.android.adservices", "com.android.cellbroadcast", + "com.android.devicelock", "com.android.extservices", "com.android.permission", "com.android.healthfitness", diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp index d26ad1c77f1b..e6fb720ba27f 100644 --- a/packages/SettingsLib/SettingsTheme/Android.bp +++ b/packages/SettingsLib/SettingsTheme/Android.bp @@ -16,6 +16,7 @@ android_library { apex_available: [ "//apex_available:platform", "com.android.cellbroadcast", + "com.android.devicelock", "com.android.extservices", "com.android.permission", "com.android.adservices", diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp index 0f9f7816d1dd..48cc75d41285 100644 --- a/packages/SettingsLib/SettingsTransition/Android.bp +++ b/packages/SettingsLib/SettingsTransition/Android.bp @@ -22,6 +22,7 @@ android_library { "//apex_available:platform", "com.android.adservices", "com.android.cellbroadcast", + "com.android.devicelock", "com.android.extservices", "com.android.permission", "com.android.healthfitness", diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt index 621e6eaf527f..0f5862a9829d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt @@ -20,6 +20,7 @@ package com.android.settingslib.spa.framework import android.content.Intent import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.annotation.VisibleForTesting @@ -40,6 +41,7 @@ import androidx.navigation.NavGraph.Companion.findStartDestination import com.android.settingslib.spa.R import com.android.settingslib.spa.framework.common.LogCategory import com.android.settingslib.spa.framework.common.NullPageProvider +import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory @@ -51,7 +53,7 @@ import com.android.settingslib.spa.framework.compose.composable import com.android.settingslib.spa.framework.compose.localNavController import com.android.settingslib.spa.framework.compose.rememberAnimatedNavController import com.android.settingslib.spa.framework.theme.SettingsTheme -import com.android.settingslib.spa.framework.util.PageWithEvent +import com.android.settingslib.spa.framework.util.PageLogger import com.android.settingslib.spa.framework.util.getDestination import com.android.settingslib.spa.framework.util.getEntryId import com.android.settingslib.spa.framework.util.getSessionName @@ -87,25 +89,50 @@ open class BrowseActivity : ComponentActivity() { setContent { SettingsTheme { val sppRepository by spaEnvironment.pageProviderRepository - BrowseContent(sppRepository, intent) + BrowseContent( + sppRepository = sppRepository, + isPageEnabled = ::isPageEnabled, + initialIntent = intent, + ) } } } + + open fun isPageEnabled(page: SettingsPage) = page.isEnabled() } @VisibleForTesting @Composable -fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: Intent? = null) { +internal fun BrowseContent( + sppRepository: SettingsPageProviderRepository, + isPageEnabled: (SettingsPage) -> Boolean, + initialIntent: Intent?, +) { val navController = rememberAnimatedNavController() CompositionLocalProvider(navController.localNavController()) { val controller = LocalNavController.current as NavControllerWrapperImpl - controller.NavContent(sppRepository.getAllProviders()) + controller.NavContent(sppRepository.getAllProviders()) { page -> + if (remember { isPageEnabled(page) }) { + LaunchedEffect(Unit) { + Log.d(TAG, "Launching page ${page.sppName}") + } + page.PageLogger() + page.UiLayout() + } else { + LaunchedEffect(Unit) { + controller.navigateBack() + } + } + } controller.InitialDestination(initialIntent, sppRepository.getDefaultStartPage()) } } @Composable -private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<SettingsPageProvider>) { +private fun NavControllerWrapperImpl.NavContent( + allProvider: Collection<SettingsPageProvider>, + content: @Composable (SettingsPage) -> Unit, +) { AnimatedNavHost( navController = navController, startDestination = NullPageProvider.name, @@ -139,7 +166,7 @@ private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<Settings }, ) { navBackStackEntry -> val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) } - page.PageWithEvent() + content(page) } } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt index dde4e0464597..a9e5e393000e 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt @@ -29,14 +29,12 @@ import com.android.settingslib.spa.framework.compose.LocalNavController import com.android.settingslib.spa.framework.compose.NavControllerWrapper @Composable -internal fun SettingsPage.PageWithEvent() { - if (!isEnabled()) return +internal fun SettingsPage.PageLogger() { val navController = LocalNavController.current LifecycleEffect( onStart = { logPageEvent(LogEvent.PAGE_ENTER, navController) }, onStop = { logPageEvent(LogEvent.PAGE_LEAVE, navController) }, ) - UiLayout() } private fun SettingsPage.logPageEvent(event: LogEvent, navController: NavControllerWrapper) { diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt index 218f5691e881..92d3411b72f3 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt @@ -26,6 +26,7 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.framework.common.LogCategory import com.android.settingslib.spa.framework.common.LogEvent +import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest @@ -38,8 +39,6 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -const val WAIT_UNTIL_TIMEOUT = 1000L - @RunWith(AndroidJUnit4::class) class BrowseActivityTest { @get:Rule @@ -49,19 +48,26 @@ class BrowseActivityTest { private val spaLogger = SpaLoggerForTest() @Test - fun testBrowsePage() { - spaLogger.reset() - val spaEnvironment = - SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger) + fun browseContent_onNavigate_logPageEvent() { + val spaEnvironment = SpaEnvironmentForTest( + context = context, + rootPages = listOf(SppHome.createSettingsPage()), + logger = spaLogger, + ) SpaEnvironmentFactory.reset(spaEnvironment) - val sppRepository by spaEnvironment.pageProviderRepository val sppHome = sppRepository.getProviderOrNull("SppHome")!! val pageHome = sppHome.createSettingsPage() val sppLayer1 = sppRepository.getProviderOrNull("SppLayer1")!! val pageLayer1 = sppLayer1.createSettingsPage() - composeTestRule.setContent { BrowseContent(sppRepository) } + composeTestRule.setContent { + BrowseContent( + sppRepository = sppRepository, + isPageEnabled = SettingsPage::isEnabled, + initialIntent = null, + ) + } composeTestRule.onNodeWithText(sppHome.getTitle(null)).assertIsDisplayed() spaLogger.verifyPageEvent(pageHome.id, 1, 0) @@ -69,7 +75,7 @@ class BrowseActivityTest { // click to layer1 page composeTestRule.onNodeWithText("SppHome to Layer1").assertIsDisplayed().performClick() - waitUntil(WAIT_UNTIL_TIMEOUT) { + waitUntil { composeTestRule.onAllNodesWithText(sppLayer1.getTitle(null)) .fetchSemanticsNodes().size == 1 } @@ -78,18 +84,24 @@ class BrowseActivityTest { } @Test - fun testBrowseDisabledPage() { - spaLogger.reset() + fun browseContent_whenDisabled_noLogPageEvent() { val spaEnvironment = SpaEnvironmentForTest( - context, listOf(SppDisabled.createSettingsPage()), logger = spaLogger + context = context, + rootPages = listOf(SppDisabled.createSettingsPage()), + logger = spaLogger, ) SpaEnvironmentFactory.reset(spaEnvironment) - val sppRepository by spaEnvironment.pageProviderRepository val sppDisabled = sppRepository.getProviderOrNull("SppDisabled")!! val pageDisabled = sppDisabled.createSettingsPage() - composeTestRule.setContent { BrowseContent(sppRepository) } + composeTestRule.setContent { + BrowseContent( + sppRepository = sppRepository, + isPageEnabled = SettingsPage::isEnabled, + initialIntent = null, + ) + } composeTestRule.onNodeWithText(sppDisabled.getTitle(null)).assertDoesNotExist() spaLogger.verifyPageEvent(pageDisabled.id, 0, 0) diff --git a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml index 5396de0ed70b..17c139bb3ff0 100644 --- a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml +++ b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml @@ -18,6 +18,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.settingslib.spaprivileged"> <uses-permission android:name="android.permission.MANAGE_USERS" /> - <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" /> </manifest> diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt index 338b16dec2d3..e9da88ed7e83 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt @@ -46,7 +46,7 @@ data class AppOpPermissionRecord( ) : AppRecord abstract class AppOpPermissionListModel( - private val context: Context, + protected val context: Context, private val packageManagers: IPackageManagers = PackageManagers, ) : TogglePermissionAppListModel<AppOpPermissionRecord> { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt index 23270c147d85..eb3d7dc84dba 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt @@ -55,6 +55,7 @@ class AppOpsControllerTest { @Before fun setUp() { whenever(context.appOpsManager).thenReturn(appOpsManager) + whenever(context.packageManager).thenReturn(packageManager) doNothing().`when`(packageManager) .updatePermissionFlags(anyString(), anyString(), anyInt(), anyInt(), any()) } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt index af59a5547db1..c54f4f87681d 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt @@ -19,6 +19,7 @@ package com.android.settingslib.spaprivileged.template.app import android.app.AppOpsManager import android.content.Context import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager import androidx.compose.runtime.State import androidx.compose.ui.test.junit4.createComposeRule import androidx.lifecycle.MutableLiveData @@ -38,6 +39,10 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.anyString +import org.mockito.Mockito.doNothing import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.Spy @@ -57,11 +62,16 @@ class AppOpPermissionAppListTest { @Mock private lateinit var appOpsManager: AppOpsManager + @Mock private lateinit var packageManager: PackageManager + private lateinit var listModel: TestAppOpPermissionAppListModel @Before fun setUp() { whenever(context.appOpsManager).thenReturn(appOpsManager) + whenever(context.packageManager).thenReturn(packageManager) + doNothing().`when`(packageManager) + .updatePermissionFlags(anyString(), anyString(), anyInt(), anyInt(), any()) listModel = TestAppOpPermissionAppListModel() } diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp index eca116586fbe..5d09a1a926d8 100644 --- a/packages/SettingsLib/TopIntroPreference/Android.bp +++ b/packages/SettingsLib/TopIntroPreference/Android.bp @@ -23,6 +23,7 @@ android_library { apex_available: [ "//apex_available:platform", "com.android.cellbroadcast", + "com.android.devicelock", "com.android.healthfitness", ], } diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp index 33ba64ac2cb7..dc2b52d24462 100644 --- a/packages/SettingsLib/Utils/Android.bp +++ b/packages/SettingsLib/Utils/Android.bp @@ -25,6 +25,7 @@ android_library { "//apex_available:platform", "com.android.adservices", "com.android.cellbroadcast", + "com.android.devicelock", "com.android.extservices", "com.android.healthfitness", "com.android.permission", diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index e9aded0838d9..2372c802168c 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -67,6 +67,8 @@ <!-- SignalDrawable --> <dimen name="signal_icon_size">15dp</dimen> + <!-- The size of the roaming icon in the top-left corner of the signal icon. --> + <dimen name="signal_icon_size_roaming">8dp</dimen> <!-- Size of nearby icon --> <dimen name="bt_nearby_icon_size">24dp</dimen> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 214c9035fc8e..4e7579277aee 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -244,7 +244,7 @@ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the Hearing Aid profile. --> <string name="bluetooth_profile_hearing_aid">Hearing Aids</string> <!-- Bluetooth settings. The user-visible string that is used whenever referring to the LE audio profile. --> - <string name="bluetooth_profile_le_audio">LE audio</string> + <string name="bluetooth_profile_le_audio">LE audio (experimental)</string> <!-- Bluetooth settings. Connection options screen. The summary for the Hearing Aid checkbox preference when Hearing Aid is connected. --> <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aids</string> <!-- Bluetooth settings. Connection options screen. The summary for the LE audio checkbox preference when LE audio is connected. --> diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java index ac9cdacec598..e034254e64ec 100644 --- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java +++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java @@ -95,9 +95,6 @@ public class QrDecorateView extends View { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - if (!isLaidOut()) { - return; - } if (mMaskBitmap == null) { mMaskBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); mMaskCanvas = new Canvas(mMaskBitmap); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index adaf4a1d3ab5..c45d77471932 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -343,7 +343,12 @@ public class WifiStatusTracker { } @Nullable - private WifiInfo getMainOrUnderlyingWifiInfo(NetworkCapabilities networkCapabilities) { + private WifiInfo getMainOrUnderlyingWifiInfo( + @Nullable NetworkCapabilities networkCapabilities) { + if (networkCapabilities == null) { + return null; + } + WifiInfo mainWifiInfo = getMainWifiInfo(networkCapabilities); if (mainWifiInfo != null) { return mainWifiInfo; @@ -376,7 +381,10 @@ public class WifiStatusTracker { } @Nullable - private WifiInfo getMainWifiInfo(NetworkCapabilities networkCapabilities) { + private WifiInfo getMainWifiInfo(@Nullable NetworkCapabilities networkCapabilities) { + if (networkCapabilities == null) { + return null; + } boolean canHaveWifiInfo = networkCapabilities.hasTransport(TRANSPORT_WIFI) || networkCapabilities.hasTransport(TRANSPORT_CELLULAR); if (!canHaveWifiInfo) { @@ -402,7 +410,11 @@ public class WifiStatusTracker { getMainOrUnderlyingWifiInfo(networkCapabilities)); } - private boolean connectionIsWifi(NetworkCapabilities networkCapabilities, WifiInfo wifiInfo) { + private boolean connectionIsWifi( + @Nullable NetworkCapabilities networkCapabilities, WifiInfo wifiInfo) { + if (networkCapabilities == null) { + return false; + } return wifiInfo != null || networkCapabilities.hasTransport(TRANSPORT_WIFI); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java index 6e975cf9d8f3..5a9a9d154070 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java @@ -305,4 +305,16 @@ public class WifiStatusTrackerTest { assertThat(mWifiStatusTracker.isDefaultNetwork).isTrue(); } + + /** Regression test for b/280169520. */ + @Test + public void networkCallbackNullCapabilities_noCrash() { + Network primaryNetwork = Mockito.mock(Network.class); + + // WHEN the network capabilities are null + mNetworkCallbackCaptor.getValue().onCapabilitiesChanged( + primaryNetwork, /* networkCapabilities= */ null); + + // THEN there's no crash (no assert needed) + } } 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 29832a081612..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 @@ -78,16 +78,10 @@ android:id="@+id/mobile_roaming" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_gravity="top|start" android:src="@drawable/stat_sys_roaming" 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/drawable/action_chip_background.xml b/packages/SystemUI/res/drawable/action_chip_background.xml index 745470f4c61a..9492472a2be1 100644 --- a/packages/SystemUI/res/drawable/action_chip_background.xml +++ b/packages/SystemUI/res/drawable/action_chip_background.xml @@ -20,7 +20,7 @@ android:color="@color/overlay_button_ripple"> <item android:id="@android:id/background"> <shape android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorAccentSecondary"/> + <solid android:color="?androidprv:attr/materialColorSecondary"/> <corners android:radius="@dimen/overlay_button_corner_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/action_chip_container_background.xml b/packages/SystemUI/res/drawable/action_chip_container_background.xml index 36083f1f0408..2ee27107d900 100644 --- a/packages/SystemUI/res/drawable/action_chip_container_background.xml +++ b/packages/SystemUI/res/drawable/action_chip_container_background.xml @@ -18,6 +18,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorSurface"/> + <solid android:color="?androidprv:attr/materialColorSurfaceBright"/> <corners android:radius="@dimen/overlay_action_container_corner_radius"/> </shape> diff --git a/packages/SystemUI/res/drawable/overlay_cancel.xml b/packages/SystemUI/res/drawable/circular_background.xml index 3fa12ddca70a..4fef0d69c78c 100644 --- a/packages/SystemUI/res/drawable/overlay_cancel.xml +++ b/packages/SystemUI/res/drawable/circular_background.xml @@ -16,14 +16,11 @@ --> <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:width="48dp" - android:height="48dp" - android:viewportWidth="32.0" - android:viewportHeight="32.0"> + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> <path - android:fillColor="?androidprv:attr/colorAccentTertiary" - android:pathData="M16,16m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0"/> - <path - android:fillColor="?attr/overlayButtonTextColor" - android:pathData="M23,10.41L21.59,9 16,14.59 10.41,9 9,10.41 14.59,16 9,21.59 10.41,23 16,17.41 21.59,23 23,21.59 17.41,16z"/> -</vector> + android:fillColor="#ff000000" + android:pathData="M12,12m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"/> + </vector> diff --git a/packages/SystemUI/res/drawable/overlay_border.xml b/packages/SystemUI/res/drawable/overlay_border.xml index c1accdc7063a..a59f9239dfca 100644 --- a/packages/SystemUI/res/drawable/overlay_border.xml +++ b/packages/SystemUI/res/drawable/overlay_border.xml @@ -18,6 +18,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorSurface"/> - <corners android:radius="24dp"/> + <solid android:color="?androidprv:attr/materialColorSurfaceBright"/> + <corners android:radius="16dp"/> </shape> diff --git a/packages/SystemUI/res/drawable/overlay_button_background.xml b/packages/SystemUI/res/drawable/overlay_button_background.xml index c045048802f7..4e5b8fba136d 100644 --- a/packages/SystemUI/res/drawable/overlay_button_background.xml +++ b/packages/SystemUI/res/drawable/overlay_button_background.xml @@ -17,12 +17,11 @@ <!-- Button background for activities downstream of overlays (clipboard text editor, long screenshots) --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:color="@color/overlay_button_ripple"> <item android:id="@android:id/background"> <inset android:insetTop="4dp" android:insetBottom="4dp"> <shape android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorAccentPrimary"/> + <solid android:color="#fff"/> <corners android:radius="20dp"/> <size android:height="40dp"/> </shape> @@ -31,7 +30,7 @@ <item android:id="@android:id/mask"> <inset android:insetTop="4dp" android:insetBottom="4dp"> <shape android:shape="rectangle"> - <solid android:color="?android:textColorPrimary"/> + <solid android:color="#000"/> <corners android:radius="20dp"/> <size android:height="40dp"/> </shape> diff --git a/packages/SystemUI/res/drawable/overlay_button_outline.xml b/packages/SystemUI/res/drawable/overlay_button_outline.xml new file mode 100644 index 000000000000..4d91503f77b0 --- /dev/null +++ b/packages/SystemUI/res/drawable/overlay_button_outline.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<!-- Button background for activities downstream of overlays + (clipboard text editor, long screenshots) --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/overlay_button_ripple"> + <item android:id="@android:id/background"> + <inset android:insetTop="4dp" android:insetBottom="4dp"> + <shape android:shape="rectangle"> + <solid android:color="@android:color/transparent" /> + <stroke android:width="1dp" android:color="#fff"/> + <corners android:radius="20dp"/> + <size android:height="40dp"/> + </shape> + </inset> + </item> + <item android:id="@android:id/mask"> + <inset android:insetTop="4dp" android:insetBottom="4dp"> + <shape android:shape="rectangle"> + <solid android:color="#000"/> + <corners android:radius="20dp"/> + <size android:height="40dp"/> + </shape> + </inset> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/overlay_preview_background.xml b/packages/SystemUI/res/drawable/overlay_preview_background.xml index 5adfaa134c3c..d39d71ec7967 100644 --- a/packages/SystemUI/res/drawable/overlay_preview_background.xml +++ b/packages/SystemUI/res/drawable/overlay_preview_background.xml @@ -17,5 +17,6 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <corners android:radius="20dp"/> + <!-- preview radius should be equal to [overlay border radius - overlay border width] --> + <corners android:radius="12dp"/> </shape> diff --git a/packages/SystemUI/res/drawable/screenshot_edit_background.xml b/packages/SystemUI/res/drawable/screenshot_edit_background.xml index a1185a2d5479..07e5aff3954d 100644 --- a/packages/SystemUI/res/drawable/screenshot_edit_background.xml +++ b/packages/SystemUI/res/drawable/screenshot_edit_background.xml @@ -20,13 +20,7 @@ android:color="@color/overlay_button_ripple"> <item android:id="@android:id/background"> <shape android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorAccentPrimary"/> - <corners android:radius="16dp"/> - </shape> - </item> - <item android:id="@android:id/mask"> - <shape android:shape="rectangle"> - <solid android:color="?android:textColorPrimary"/> + <solid android:color="?androidprv:attr/materialColorSecondaryFixedDim"/> <corners android:radius="16dp"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/stat_sys_roaming.xml b/packages/SystemUI/res/drawable/stat_sys_roaming.xml index 0dd9f5a39f91..2dd12ca4e21a 100644 --- a/packages/SystemUI/res/drawable/stat_sys_roaming.xml +++ b/packages/SystemUI/res/drawable/stat_sys_roaming.xml @@ -14,10 +14,10 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/signal_icon_size" - android:height="@dimen/signal_icon_size" - android:viewportWidth="17" - android:viewportHeight="17"> + android:width="@dimen/signal_icon_size_roaming" + android:height="@dimen/signal_icon_size_roaming" + android:viewportWidth="8" + android:viewportHeight="8"> <path android:fillColor="#FFFFFFFF" diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml index 5155b77a6bee..2459eeaa9b19 100644 --- a/packages/SystemUI/res/layout/app_clips_screenshot.xml +++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml @@ -87,19 +87,4 @@ tools:background="?android:colorBackground" tools:minHeight="100dp" tools:minWidth="100dp" /> - - <com.android.systemui.screenshot.MagnifierView - android:id="@+id/magnifier" - android:visibility="invisible" - android:layout_width="200dp" - android:layout_height="200dp" - android:elevation="2dp" - app:layout_constraintTop_toTopOf="@id/preview" - app:layout_constraintLeft_toLeftOf="parent" - app:handleThickness="@dimen/screenshot_crop_handle_thickness" - app:handleColor="?android:attr/colorAccent" - app:scrimColor="?android:colorBackgroundFloating" - app:scrimAlpha="128" - app:borderThickness="4dp" - app:borderColor="#fff" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml index cb7f40f6d87d..ae243130e537 100644 --- a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml +++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -15,6 +16,8 @@ android:paddingHorizontal="16dp" android:background="@drawable/overlay_button_background" android:text="@string/clipboard_edit_text_done" + android:backgroundTint="?androidprv:attr/materialColorPrimary" + android:textColor="?androidprv:attr/materialColorOnPrimary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 297cf2b94a19..250076950907 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -179,6 +179,10 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/overlay_dismiss_button_margin" - android:src="@drawable/overlay_cancel"/> + android:background="@drawable/circular_background" + android:backgroundTint="?androidprv:attr/materialColorPrimaryFixedDim" + android:tint="?androidprv:attr/materialColorOnPrimaryFixed" + android:padding="4dp" + android:src="@drawable/ic_close"/> </FrameLayout> </com.android.systemui.clipboardoverlay.ClipboardOverlayView>
\ No newline at end of file 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/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml index 2927d6ba7f3c..8a19c2ebdcd6 100644 --- a/packages/SystemUI/res/layout/long_screenshot.xml +++ b/packages/SystemUI/res/layout/long_screenshot.xml @@ -16,9 +16,9 @@ --> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - android:background="?android:colorBackgroundFloating" android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -32,7 +32,8 @@ android:layout_marginStart="8dp" android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin" android:background="@drawable/overlay_button_background" - android:textColor="?android:textColorSecondary" + android:backgroundTint="?androidprv:attr/materialColorPrimary" + android:textColor="?androidprv:attr/materialColorOnPrimary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@id/preview" /> @@ -45,8 +46,9 @@ android:text="@android:string/cancel" android:layout_marginStart="6dp" android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin" - android:background="@drawable/overlay_button_background" - android:textColor="?android:textColorSecondary" + android:background="@drawable/overlay_button_outline" + android:backgroundTint="?androidprv:attr/materialColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSurface" app:layout_constraintStart_toEndOf="@id/save" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@id/preview" @@ -55,7 +57,7 @@ <ImageButton android:id="@+id/share" style="@android:style/Widget.Material.Button.Borderless" - android:tint="?android:textColorPrimary" + android:tint="?androidprv:attr/materialColorOnSurface" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginEnd="8dp" @@ -112,10 +114,10 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:handleThickness="@dimen/screenshot_crop_handle_thickness" - app:handleColor="?android:attr/colorAccent" - app:scrimColor="?android:colorBackgroundFloating" + app:handleColor="?androidprv:attr/materialColorSecondary" + app:scrimColor="?androidprv:attr/materialColorSurfaceContainer" app:scrimAlpha="128" - app:containerBackgroundColor="?android:colorBackgroundFloating" + app:containerBackgroundColor="?androidprv:attr/materialColorSurfaceContainer" tools:background="?android:colorBackground" tools:minHeight="100dp" tools:minWidth="100dp" /> @@ -129,12 +131,11 @@ app:layout_constraintTop_toTopOf="@id/preview" app:layout_constraintLeft_toLeftOf="parent" app:handleThickness="@dimen/screenshot_crop_handle_thickness" - app:handleColor="?android:attr/colorAccent" - app:scrimColor="?android:colorBackgroundFloating" + app:handleColor="?androidprv:attr/materialColorSecondary" + app:scrimColor="?androidprv:attr/materialColorSurfaceContainer" app:scrimAlpha="128" app:borderThickness="4dp" - app:borderColor="#fff" - /> + app:borderColor="?androidprv:attr/materialColorSurfaceBright" /> <ImageButton android:id="@+id/edit" @@ -146,12 +147,11 @@ android:background="@drawable/screenshot_edit_background" android:src="@drawable/ic_screenshot_edit" android:contentDescription="@string/screenshot_edit_label" - android:tint="?android:textColorSecondary" + android:tint="?androidprv:attr/materialColorOnSecondaryFixed" android:padding="16dp" android:scaleType="fitCenter" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - /> + app:layout_constraintEnd_toEndOf="parent"/> <ImageView android:id="@+id/transition" @@ -160,7 +160,6 @@ app:layout_constraintTop_toTopOf="@id/preview" app:layout_constraintLeft_toLeftOf="parent" android:scaleType="centerCrop" - android:visibility="invisible" - /> + android:visibility="invisible" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/media_session_end_dialog.xml b/packages/SystemUI/res/layout/media_session_end_dialog.xml new file mode 100644 index 000000000000..e1050f65d47a --- /dev/null +++ b/packages/SystemUI/res/layout/media_session_end_dialog.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:id="@+id/end_session_dialog" + android:layout_width="@dimen/large_dialog_width" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/Widget.SliceView.Panel" + android:gravity="center_vertical|center_horizontal" + android:layout_marginTop="@dimen/dialog_top_padding" + android:layout_marginBottom="@dimen/dialog_bottom_padding" + android:orientation="vertical"> + + <ImageView + android:id="@+id/end_icon" + android:gravity="center_vertical|center_horizontal" + android:layout_width="36dp" + android:layout_height="36dp" + android:importantForAccessibility="no"/> + + <TextView + android:id="@+id/end_session_dialog_title" + android:text="@string/media_output_end_session_dialog_summary" + android:layout_marginTop="16dp" + android:layout_marginBottom="@dimen/dialog_side_padding" + android:layout_marginStart="@dimen/dialog_side_padding" + android:layout_marginEnd="@dimen/dialog_bottom_padding" + android:ellipsize="end" + android:gravity="center_vertical|center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?android:attr/textColorPrimary" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:textSize="24sp"/> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="end|center_vertical" + android:layout_marginTop="8dp" + android:layout_marginStart="@dimen/dialog_side_padding" + android:layout_marginEnd="@dimen/dialog_side_padding" + android:layout_marginBottom="@dimen/dialog_bottom_padding" + android:orientation="horizontal"> + <Button + android:id="@+id/cancel_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:text="@string/cancel" + android:ellipsize="end" + android:layout_gravity="end|center_vertical" + android:singleLine="true" + style="@style/Widget.Dialog.Button.BorderButton" + android:clickable="true" + android:focusable="true"/> + <Button + android:id="@+id/stop_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end|center_vertical" + android:text="@string/media_output_end_session_dialog_stop" + style="@style/Widget.Dialog.Button" + android:singleLine="true" + android:ellipsize="end" + android:clickable="true" + android:focusable="true"/> + </LinearLayout> +</LinearLayout> 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/layout/overlay_action_chip.xml b/packages/SystemUI/res/layout/overlay_action_chip.xml index e0c20ff4269c..e7c382f9833b 100644 --- a/packages/SystemUI/res/layout/overlay_action_chip.xml +++ b/packages/SystemUI/res/layout/overlay_action_chip.xml @@ -16,12 +16,12 @@ --> <com.android.systemui.screenshot.OverlayActionChip xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/overlay_action_chip" android:theme="@style/FloatingOverlay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/overlay_action_chip_margin_start" - android:paddingVertical="@dimen/overlay_action_chip_margin_vertical" android:layout_gravity="center" android:gravity="center" android:alpha="0.0"> @@ -33,7 +33,7 @@ android:gravity="center"> <ImageView android:id="@+id/overlay_action_chip_icon" - android:tint="?attr/overlayButtonTextColor" + android:tint="?androidprv:attr/materialColorOnSecondary" android:layout_width="@dimen/overlay_action_chip_icon_size" android:layout_height="@dimen/overlay_action_chip_icon_size"/> <TextView @@ -42,6 +42,6 @@ android:layout_height="wrap_content" android:fontFamily="@*android:string/config_headlineFontFamilyMedium" android:textSize="@dimen/overlay_action_chip_text_size" - android:textColor="?attr/overlayButtonTextColor"/> + android:textColor="?androidprv:attr/materialColorOnSecondary"/> </LinearLayout> </com.android.systemui.screenshot.OverlayActionChip> diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml index 7e9202c9f89b..3b728a9ebc02 100644 --- a/packages/SystemUI/res/layout/screenshot_static.xml +++ b/packages/SystemUI/res/layout/screenshot_static.xml @@ -16,6 +16,7 @@ --> <com.android.systemui.screenshot.DraggableConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -68,7 +69,7 @@ android:layout_marginTop="@dimen/overlay_border_width_neg" android:layout_marginEnd="@dimen/overlay_border_width_neg" android:layout_marginBottom="@dimen/overlay_preview_container_margin" - android:elevation="7dp" + android:elevation="8dp" android:alpha="0" android:background="@drawable/overlay_border" app:layout_constraintStart_toStartOf="@id/actions_container_background" @@ -83,7 +84,7 @@ android:layout_marginStart="@dimen/overlay_border_width" android:layout_marginBottom="@dimen/overlay_border_width" android:layout_gravity="center" - android:elevation="7dp" + android:elevation="8dp" android:contentDescription="@string/screenshot_edit_description" android:scaleType="fitEnd" android:background="@drawable/overlay_preview_background" @@ -93,17 +94,17 @@ app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/> <ImageView android:id="@+id/screenshot_badge" - android:layout_width="48dp" - android:layout_height="48dp" + android:layout_width="56dp" + android:layout_height="56dp" android:visibility="gone" - android:elevation="8dp" + android:elevation="9dp" app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border" app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/> <FrameLayout android:id="@+id/screenshot_dismiss_button" android:layout_width="@dimen/overlay_dismiss_button_tappable_size" android:layout_height="@dimen/overlay_dismiss_button_tappable_size" - android:elevation="10dp" + android:elevation="11dp" android:visibility="gone" app:layout_constraintStart_toEndOf="@id/screenshot_preview" app:layout_constraintEnd_toEndOf="@id/screenshot_preview" @@ -115,7 +116,11 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/overlay_dismiss_button_margin" - android:src="@drawable/overlay_cancel"/> + android:background="@drawable/circular_background" + android:backgroundTint="?androidprv:attr/materialColorPrimary" + android:tint="?androidprv:attr/materialColorOnPrimary" + android:padding="4dp" + android:src="@drawable/ic_close"/> </FrameLayout> <ImageView android:id="@+id/screenshot_scrollable_preview" @@ -150,8 +155,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintWidth_max="450dp" - app:layout_constraintHorizontal_bias="0" - > + app:layout_constraintHorizontal_bias="0"> <include layout="@layout/screenshot_work_profile_first_run" /> <include layout="@layout/screenshot_detection_notice" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml index 392d84537e92..78cd7184b485 100644 --- a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml +++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/work_profile_first_run" android:layout_height="wrap_content" android:layout_width="match_parent" @@ -33,9 +34,13 @@ android:layout_height="@dimen/overlay_dismiss_button_tappable_size" android:contentDescription="@string/screenshot_dismiss_work_profile"> <ImageView - android:layout_width="24dp" - android:layout_height="24dp" + android:layout_width="16dp" + android:layout_height="16dp" android:layout_gravity="center" - android:src="@drawable/overlay_cancel"/> + android:background="@drawable/circular_background" + android:backgroundTint="?androidprv:attr/materialColorSurfaceContainerHigh" + android:tint="?androidprv:attr/materialColorOnSurface" + android:padding="2dp" + android:src="@drawable/ic_close"/> </FrameLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/udfps_keyguard_preview.xml b/packages/SystemUI/res/layout/udfps_keyguard_preview.xml new file mode 100644 index 000000000000..c068b7bc46a9 --- /dev/null +++ b/packages/SystemUI/res/layout/udfps_keyguard_preview.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:contentDescription="@string/accessibility_fingerprint_label" + android:background="@drawable/fingerprint_bg"> + + <!-- LockScreen fingerprint icon from 0 stroke width to full width --> + <com.airbnb.lottie.LottieAnimationView + android:layout_width="0dp" + android:layout_height="0dp" + android:scaleType="centerCrop" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHeight_percent="0.5" + app:layout_constraintWidth_percent="0.5" + app:lottie_autoPlay="false" + app:lottie_loop="false" + app:lottie_progress="1.0" + app:lottie_colorFilter="?android:attr/textColorPrimary" + app:lottie_rawRes="@raw/udfps_lockscreen_fp" /> +</androidx.constraintlayout.widget.ConstraintLayout >
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index aff0e8052739..7488e9fbbe3e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -342,23 +342,22 @@ <dimen name="screenshot_crop_handle_thickness">3dp</dimen> - <dimen name="long_screenshot_action_bar_top_margin">8dp</dimen> + <dimen name="long_screenshot_action_bar_top_margin">4dp</dimen> <!-- Dimensions shared between "overlays" (clipboard and screenshot preview UIs) --> <!-- Constrained size of the floating overlay preview --> <dimen name="overlay_x_scale">80dp</dimen> <!-- Radius of the chip background on floating overlay actions --> - <dimen name="overlay_button_corner_radius">8dp</dimen> + <dimen name="overlay_button_corner_radius">16dp</dimen> <!-- Margin between successive chips --> <dimen name="overlay_action_chip_margin_start">8dp</dimen> - <!-- Padding to make tappable chip height 48dp (18+11+11+4+4) --> - <dimen name="overlay_action_chip_margin_vertical">4dp</dimen> - <dimen name="overlay_action_chip_padding_vertical">11dp</dimen> - <dimen name="overlay_action_chip_icon_size">18sp</dimen> + <dimen name="overlay_action_chip_padding_vertical">12dp</dimen> + <dimen name="overlay_action_chip_icon_size">24sp</dimen> <!-- Padding on each side of the icon for icon-only chips --> - <dimen name="overlay_action_chip_icon_only_padding_horizontal">14dp</dimen> + <dimen name="overlay_action_chip_icon_only_padding_horizontal">12dp</dimen> <!-- Padding at the edges of the chip for icon-and-text chips --> - <dimen name="overlay_action_chip_padding_horizontal">12dp</dimen> + <dimen name="overlay_action_chip_padding_start">12dp</dimen> + <dimen name="overlay_action_chip_padding_end">16dp</dimen> <!-- Spacing between chip icon and chip text --> <dimen name="overlay_action_chip_spacing">8dp</dimen> <dimen name="overlay_action_chip_text_size">14sp</dimen> @@ -368,8 +367,8 @@ <dimen name="overlay_action_container_margin_horizontal">8dp</dimen> <dimen name="overlay_action_container_margin_bottom">6dp</dimen> <dimen name="overlay_bg_protection_height">242dp</dimen> - <dimen name="overlay_action_container_corner_radius">18dp</dimen> - <dimen name="overlay_action_container_padding_vertical">4dp</dimen> + <dimen name="overlay_action_container_corner_radius">20dp</dimen> + <dimen name="overlay_action_container_padding_vertical">8dp</dimen> <dimen name="overlay_action_container_padding_right">8dp</dimen> <dimen name="overlay_action_container_padding_end">8dp</dimen> <dimen name="overlay_dismiss_button_tappable_size">48dp</dimen> @@ -1643,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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c57fef1c52f7..70fdc2070b7a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2684,6 +2684,10 @@ <string name="media_output_group_title_speakers_and_displays">Speakers & Displays</string> <!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] --> <string name="media_output_group_title_suggested_device">Suggested Devices</string> + <!-- Summary for end session dialog. [CHAR LIMIT=NONE] --> + <string name="media_output_end_session_dialog_summary">Stop your shared session to move media to another device</string> + <!-- Button text for stopping session [CHAR LIMIT=60] --> + <string name="media_output_end_session_dialog_stop">Stop</string> <!-- Media Output Broadcast Dialog --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 9d0cc11c93c2..cee21353bd68 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -782,10 +782,12 @@ </style> <style name="LongScreenshotActivity" parent="@android:style/Theme.DeviceDefault.DayNight"> + <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainer</item> <item name="android:windowNoTitle">true</item> <item name="android:windowLightStatusBar">true</item> <item name="android:windowLightNavigationBar">true</item> - <item name="android:navigationBarColor">?android:attr/colorBackgroundFloating</item> + <item name="android:statusBarColor">?androidprv:attr/materialColorSurfaceContainer</item> + <item name="android:navigationBarColor">?androidprv:attr/materialColorSurfaceContainerHighest</item> <item name="android:windowActivityTransitions">true</item> </style> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index d8bf570954df..676f342775ef 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -179,6 +179,20 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } /** + * Set alpha directly to mView will clip clock, so we set alpha to clock face instead + */ + public void setAlpha(float alpha) { + ClockController clock = getClock(); + if (clock != null) { + clock.getLargeClock().getView().setAlpha(alpha); + clock.getSmallClock().getView().setAlpha(alpha); + } + if (mStatusArea != null) { + mStatusArea.setAlpha(alpha); + } + } + + /** * Attach the controller to the view it relates to. */ @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index 09820305f34e..58807e4bf3bd 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; @@ -184,6 +185,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { } mAppearAnimator.setDuration(ANIMATION_DURATION); mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction())); + mAppearAnimator.addListener(getAnimationListener(CUJ_LOCKSCREEN_PIN_APPEAR)); mAppearAnimator.start(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 693268d730a4..1cbcb9d56566 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -264,8 +264,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard */ @Override public void finish(boolean strongAuth, int targetUserId) { - if (mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD) - && !mKeyguardStateController.canDismissLockScreen() && !strongAuth) { + if (!mKeyguardStateController.canDismissLockScreen() && !strongAuth) { Log.e(TAG, "Tried to dismiss keyguard when lockscreen is not dismissible and user " + "was not authenticated with a primary security method " diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index d8e1eb0f0860..23136097f41f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -135,4 +135,31 @@ public class KeyguardStatusView extends GridLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Trace.endSection(); } + + /** + * Clock content will be clipped when goes beyond bounds, + * so we setAlpha for all views except clock + */ + public void setAlpha(float alpha, boolean excludeClock) { + if (!excludeClock) { + setAlpha(alpha); + return; + } + if (alpha == 1 || alpha == 0) { + setAlpha(alpha); + } + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child == mStatusViewContainer) { + for (int j = 0; j < mStatusViewContainer.getChildCount(); j++) { + View innerChild = mStatusViewContainer.getChildAt(j); + if (innerChild != mClockView) { + innerChild.setAlpha(alpha); + } + } + } else { + child.setAlpha(alpha); + } + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 794eeda86b0f..af474661a67d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -180,7 +180,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV */ public void setAlpha(float alpha) { if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { - mView.setAlpha(alpha); + mView.setAlpha(alpha, true); + mKeyguardClockSwitchController.setAlpha(alpha); } } 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/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt index 5499d2c07181..f04fdfff67f1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt @@ -23,6 +23,7 @@ import android.hardware.fingerprint.FingerprintManager import android.view.DisplayInfo import android.view.Surface import android.view.View +import androidx.annotation.VisibleForTesting import com.airbnb.lottie.LottieAnimationView import com.android.settingslib.widget.LottieColorUtils import com.android.systemui.R @@ -133,13 +134,19 @@ open class AuthBiometricFingerprintIconController( } } - private fun getIconContentDescription(@BiometricState newState: Int): CharSequence? { + @VisibleForTesting + fun getIconContentDescription(@BiometricState newState: Int): CharSequence? { val id = when (newState) { STATE_IDLE, STATE_AUTHENTICATING_ANIMATING_IN, STATE_AUTHENTICATING, STATE_PENDING_CONFIRMATION, - STATE_AUTHENTICATED -> R.string.security_settings_sfps_enroll_find_sensor_message + STATE_AUTHENTICATED -> + if (isSideFps) { + R.string.security_settings_sfps_enroll_find_sensor_message + } else { + R.string.fingerprint_dialog_touch_sensor + } STATE_ERROR, STATE_HELP -> R.string.biometric_dialog_try_again else -> null 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/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 92aff0624bdc..e60063248fb0 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -189,7 +189,7 @@ open class ControlsProviderSelectorActivity @Inject constructor( authorizedPanelsRepository.addAuthorizedPanels( setOf(serviceInfo.componentName.packageName) ) - val selected = SelectedItem.PanelItem(appName, componentName) + val selected = SelectedItem.PanelItem(appName, serviceInfo.componentName) controlsController.setPreferredSelection(selected) animateExitAndFinish() openControlsOrigin() diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index b446724e6da6..6967e6c23f35 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -206,11 +206,6 @@ object Flags { "wallpaper_picker_ui_for_aiwp" ) - /** Whether to inflate the bouncer view on a background thread. */ - // TODO(b/273341787): Tracking Bug - @JvmField - val PREVENT_BYPASS_KEYGUARD = releasedFlag(230, "prevent_bypass_keyguard") - /** Whether to use a new data source for intents to run on keyguard dismissal. */ @JvmField val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent") @@ -715,6 +710,5 @@ object Flags { // TODO(b/278761837): Tracking Bug @JvmField - val USE_NEW_ACTIVITY_STARTER = unreleasedFlag(2801, name = "use_new_activity_starter", - teamfood = true) + val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter") } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index b5ddc2ea91b7..a0db65ce5fdd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -16,6 +16,7 @@ package com.android.systemui.keyguard; +import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; @@ -101,6 +102,7 @@ import androidx.annotation.VisibleForTesting; import com.android.app.animation.Interpolators; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.Configuration; +import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardStateCallback; @@ -131,6 +133,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.dagger.KeyguardModule; +import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; @@ -1181,12 +1184,16 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private Lazy<ScrimController> mScrimControllerLazy; private FeatureFlags mFeatureFlags; + private final UiEventLogger mUiEventLogger; + private final SessionTracker mSessionTracker; /** * Injected constructor. See {@link KeyguardModule}. */ public KeyguardViewMediator( Context context, + UiEventLogger uiEventLogger, + SessionTracker sessionTracker, UserTracker userTracker, FalsingCollector falsingCollector, LockPatternUtils lockPatternUtils, @@ -1270,6 +1277,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS; mFeatureFlags = featureFlags; + mUiEventLogger = uiEventLogger; + mSessionTracker = sessionTracker; } public void userActivity() { @@ -1660,6 +1669,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence); notifyStartedWakingUp(); } + mUiEventLogger.logWithInstanceIdAndPosition( + BiometricUnlockController.BiometricUiEvent.STARTED_WAKING_UP, + 0, + null, + mSessionTracker.getSessionId(SESSION_KEYGUARD), + pmWakeReason + ); mUpdateMonitor.dispatchStartedWakingUp(pmWakeReason); maybeSendUserPresentBroadcast(); Trace.endSection(); @@ -2941,9 +2957,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, if (mSurfaceBehindRemoteAnimationFinishedCallback != null) { try { mSurfaceBehindRemoteAnimationFinishedCallback.onAnimationFinished(); + } catch (Throwable t) { + // The surface may no longer be available. Just capture the exception + Log.w(TAG, "Surface behind remote animation callback failed, and it's probably ok: " + + t.getMessage()); + } finally { mSurfaceBehindRemoteAnimationFinishedCallback = null; - } catch (RemoteException e) { - e.printStackTrace(); } } } @@ -3079,7 +3098,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking"); mWakeAndUnlocking = true; - keyguardDone(); + mKeyguardViewControllerLazy.get().notifyKeyguardAuthenticated(/* strongAuth */ false); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 5e71458c8bb4..deb8f5d96cbd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -21,6 +21,7 @@ import android.content.Context; import android.os.PowerManager; import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardUpdateMonitor; @@ -50,6 +51,7 @@ import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionMo import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl; +import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; @@ -63,12 +65,12 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.DeviceConfigProxy; -import java.util.concurrent.Executor; - import dagger.Lazy; import dagger.Module; import dagger.Provides; +import java.util.concurrent.Executor; + /** * Dagger Module providing keyguard. */ @@ -93,6 +95,8 @@ public class KeyguardModule { @SysUISingleton public static KeyguardViewMediator newKeyguardViewMediator( Context context, + UiEventLogger uiEventLogger, + SessionTracker sessionTracker, UserTracker userTracker, FalsingCollector falsingCollector, LockPatternUtils lockPatternUtils, @@ -124,6 +128,8 @@ public class KeyguardModule { FeatureFlags featureFlags) { return new KeyguardViewMediator( context, + uiEventLogger, + sessionTracker, userTracker, falsingCollector, lockPatternUtils, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt index 568db2f543b6..e7803c5e964c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt @@ -31,7 +31,9 @@ object KeyguardBottomAreaVibrations { VibrationEffect.startComposition() .apply { val vibrationDelayMs = - (ShakeAnimationDuration.inWholeMilliseconds / ShakeAnimationCycles * 2).toInt() + (ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2)) + .toInt() + val vibrationCount = ShakeAnimationCycles.toInt() * 2 repeat(vibrationCount) { addPrimitive( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index ad11360160c1..3aa57dde3178 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -273,7 +273,7 @@ constructor( val finger = LayoutInflater.from(context) .inflate( - R.layout.udfps_keyguard_view_internal, + R.layout.udfps_keyguard_preview, parentView, false, ) as View diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index f50a7a854169..d949cf56ff0e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -37,11 +37,13 @@ import androidx.annotation.RequiresApi; import androidx.core.widget.CompoundButtonCompat; import androidx.recyclerview.widget.RecyclerView; +import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.media.LocalMediaManager.MediaDeviceState; import com.android.settingslib.media.MediaDevice; import com.android.systemui.R; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; /** * Adapter for media output dialog. @@ -52,6 +54,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final float DEVICE_DISCONNECTED_ALPHA = 0.5f; private static final float DEVICE_CONNECTED_ALPHA = 1f; + protected List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>(); public MediaOutputAdapter(MediaOutputController controller) { super(controller); @@ -59,6 +62,13 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } @Override + public void updateItems() { + mMediaItemList.clear(); + mMediaItemList.addAll(mController.getMediaItemList()); + notifyDataSetChanged(); + } + + @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { super.onCreateViewHolder(viewGroup, viewType); @@ -79,14 +89,14 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { if (mController.isAdvancedLayoutSupported()) { - if (position >= mController.getMediaItemList().size()) { + if (position >= mMediaItemList.size()) { if (DEBUG) { Log.d(TAG, "Incorrect position: " + position + " list size: " - + mController.getMediaItemList().size()); + + mMediaItemList.size()); } return; } - MediaItem currentMediaItem = mController.getMediaItemList().get(position); + MediaItem currentMediaItem = mMediaItemList.get(position); switch (currentMediaItem.getMediaItemType()) { case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER: ((MediaGroupDividerViewHolder) viewHolder).onBind(currentMediaItem.getTitle()); @@ -119,11 +129,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @Override public long getItemId(int position) { if (mController.isAdvancedLayoutSupported()) { - if (position >= mController.getMediaItemList().size()) { + if (position >= mMediaItemList.size()) { Log.d(TAG, "Incorrect position for item id: " + position); return position; } - MediaItem currentMediaItem = mController.getMediaItemList().get(position); + MediaItem currentMediaItem = mMediaItemList.get(position); return currentMediaItem.getMediaDevice().isPresent() ? currentMediaItem.getMediaDevice().get().getId().hashCode() : position; @@ -143,12 +153,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @Override public int getItemViewType(int position) { if (mController.isAdvancedLayoutSupported() - && position >= mController.getMediaItemList().size()) { + && position >= mMediaItemList.size()) { Log.d(TAG, "Incorrect position for item type: " + position); return MediaItem.MediaItemType.TYPE_GROUP_DIVIDER; } return mController.isAdvancedLayoutSupported() - ? mController.getMediaItemList().get(position).getMediaItemType() + ? mMediaItemList.get(position).getMediaItemType() : super.getItemViewType(position); } @@ -156,7 +166,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { public int getItemCount() { // Add extra one for "pair new" return mController.isAdvancedLayoutSupported() - ? mController.getMediaItemList().size() + ? mMediaItemList.size() : mController.getMediaDevices().size() + 1; } @@ -482,6 +492,14 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } private void onItemClick(View view, MediaDevice device) { + if (mController.isCurrentOutputDeviceHasSessionOngoing()) { + showCustomEndSessionDialog(device); + } else { + transferOutput(device); + } + } + + private void transferOutput(MediaDevice device) { if (mController.isAnyDeviceTransferring()) { return; } @@ -496,6 +514,14 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { notifyDataSetChanged(); } + @VisibleForTesting + void showCustomEndSessionDialog(MediaDevice device) { + MediaSessionReleaseDialog mediaSessionReleaseDialog = new MediaSessionReleaseDialog( + mContext, () -> transferOutput(device), mController.getColorButtonBackground(), + mController.getColorItemContent()); + mediaSessionReleaseDialog.show(); + } + private void cancelMuteAwaitConnection() { mController.cancelMuteAwaitConnection(); notifyDataSetChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 73ab52722a79..151dbb2746aa 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -81,6 +81,11 @@ public abstract class MediaOutputBaseAdapter extends mIsInitVolumeFirstTime = true; } + /** + * Refresh current dataset + */ + public abstract void updateItems(); + @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 08e47a09bab9..770e4dfe0d7d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -382,7 +382,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements && currentActivePosition < mAdapter.getItemCount()) { mAdapter.notifyItemChanged(currentActivePosition); } else { - mAdapter.notifyDataSetChanged(); + mAdapter.updateItems(); } } else { mMediaOutputController.setRefreshing(false); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 8e014c61c641..822644b8e573 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -783,6 +783,12 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, currentConnectedMediaDevice); } + boolean isCurrentOutputDeviceHasSessionOngoing() { + MediaDevice currentConnectedMediaDevice = getCurrentConnectedMediaDevice(); + return currentConnectedMediaDevice != null + && (currentConnectedMediaDevice.isHostForOngoingSession()); + } + public boolean isAdvancedLayoutSupported() { return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSessionReleaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSessionReleaseDialog.java new file mode 100644 index 000000000000..2680a2ff4567 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSessionReleaseDialog.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dialog; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.ColorFilter; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.os.Bundle; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; + +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.SystemUIDialog; + +/** + * Confirmation dialog for releasing media session + */ + +public class MediaSessionReleaseDialog extends SystemUIDialog { + + private View mDialogView; + + private final Context mContext; + private final View.OnClickListener mPositiveButtonListener; + private final ColorFilter mButtonColorFilter; + private final int mIconColor; + + public MediaSessionReleaseDialog(Context context, Runnable runnable, int buttonColor, + int iconColor) { + super(context, R.style.Theme_SystemUI_Dialog_Media); + mContext = getContext(); + mPositiveButtonListener = (v) -> { + runnable.run(); + dismiss(); + }; + mButtonColorFilter = new PorterDuffColorFilter( + buttonColor, + PorterDuff.Mode.SRC_IN); + mIconColor = iconColor; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mDialogView = LayoutInflater.from(mContext).inflate(R.layout.media_session_end_dialog, + null); + final Window window = getWindow(); + window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); + window.setContentView(mDialogView); + + final WindowManager.LayoutParams lp = window.getAttributes(); + lp.gravity = Gravity.CENTER; + lp.width = (int) (mContext.getResources().getDisplayMetrics().widthPixels * 0.90); + + ImageView headerIcon = mDialogView.requireViewById(R.id.end_icon); + headerIcon.setImageDrawable(mContext.getDrawable(R.drawable.media_output_status_failed)); + headerIcon.setImageTintList( + ColorStateList.valueOf(mIconColor)); + + Button stopButton = mDialogView.requireViewById(R.id.stop_button); + stopButton.setOnClickListener(mPositiveButtonListener); + stopButton.getBackground().setColorFilter(mButtonColorFilter); + + Button cancelButton = mDialogView.requireViewById(R.id.cancel_button); + cancelButton.setOnClickListener((v) -> dismiss()); + cancelButton.getBackground().setColorFilter(mButtonColorFilter); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 385e72017bae..2469a98140e3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -42,7 +42,9 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.concurrency.DelayableExecutor; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; @@ -88,6 +90,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements private final Handler mHandler; private final Intent mIntent; private final UserHandle mUser; + private final DelayableExecutor mExecutor; private final IBinder mToken = new Binder(); private final PackageManagerAdapter mPackageManagerAdapter; private final BroadcastDispatcher mBroadcastDispatcher; @@ -100,25 +103,27 @@ public class TileLifecycleManager extends BroadcastReceiver implements private int mBindTryCount; private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; - private boolean mBound; + private AtomicBoolean mBound = new AtomicBoolean(false); private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false); private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false); - private boolean mUnbindImmediate; + private AtomicBoolean mUnbindImmediate = new AtomicBoolean(false); @Nullable private TileChangeListener mChangeListener; // Return value from bindServiceAsUser, determines whether safe to call unbind. - private boolean mIsBound; + private AtomicBoolean mIsBound = new AtomicBoolean(false); @AssistedInject TileLifecycleManager(@Main Handler handler, Context context, IQSService service, PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, - @Assisted Intent intent, @Assisted UserHandle user) { + @Assisted Intent intent, @Assisted UserHandle user, + @Background DelayableExecutor executor) { mContext = context; mHandler = handler; mIntent = intent; mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder()); mIntent.putExtra(TileService.EXTRA_TOKEN, mToken); mUser = user; + mExecutor = executor; mPackageManagerAdapter = packageManagerAdapter; mBroadcastDispatcher = broadcastDispatcher; if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser); @@ -184,22 +189,21 @@ public class TileLifecycleManager extends BroadcastReceiver implements * Binds just long enough to send any queued messages, then unbinds. */ public void flushMessagesAndUnbind() { - mUnbindImmediate = true; - setBindService(true); + mExecutor.execute(() -> { + mUnbindImmediate.set(true); + setBindService(true); + }); } - /** - * Binds or unbinds to IQSService - */ @WorkerThread - public void setBindService(boolean bind) { - if (mBound && mUnbindImmediate) { + private void setBindService(boolean bind) { + if (mBound.get() && mUnbindImmediate.get()) { // If we are already bound and expecting to unbind, this means we should stay bound // because something else wants to hold the connection open. - mUnbindImmediate = false; + mUnbindImmediate.set(false); return; } - mBound = bind; + mBound.set(bind); if (bind) { if (mBindTryCount == MAX_BIND_RETRIES) { // Too many failures, give up on this tile until an update. @@ -212,31 +216,38 @@ public class TileLifecycleManager extends BroadcastReceiver implements if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser); mBindTryCount++; try { - mIsBound = bindServices(); - if (!mIsBound) { + mIsBound.set(bindServices()); + if (!mIsBound.get()) { mContext.unbindService(this); } } catch (SecurityException e) { Log.e(TAG, "Failed to bind to service", e); - mIsBound = false; + mIsBound.set(false); } } else { if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser); // Give it another chance next time it needs to be bound, out of kindness. mBindTryCount = 0; freeWrapper(); - if (mIsBound) { + if (mIsBound.get()) { try { mContext.unbindService(this); } catch (Exception e) { Log.e(TAG, "Failed to unbind service " + mIntent.getComponent().flattenToShortString(), e); } - mIsBound = false; + mIsBound.set(false); } } } + /** + * Binds or unbinds to IQSService + */ + public void executeSetBindService(boolean bind) { + mExecutor.execute(() -> setBindService(bind)); + } + private boolean bindServices() { String packageName = mIntent.getComponent().getPackageName(); if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName, @@ -317,10 +328,12 @@ public class TileLifecycleManager extends BroadcastReceiver implements } onTileRemoved(); } - if (mUnbindImmediate) { - mUnbindImmediate = false; - setBindService(false); - } + mExecutor.execute(() -> { + if (mUnbindImmediate.get()) { + mUnbindImmediate.set(false); + setBindService(false); + } + }); } public void handleDestroy() { @@ -335,19 +348,11 @@ public class TileLifecycleManager extends BroadcastReceiver implements if (mWrapper == null) return; freeWrapper(); // Clearly not bound anymore - mIsBound = false; - if (!mBound) return; + mIsBound.set(false); + if (!mBound.get()) return; if (DEBUG) Log.d(TAG, "handleDeath"); if (checkComponentState()) { - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (mBound) { - // Retry binding. - setBindService(true); - } - } - }, mBindRetryDelay); + mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay); } } @@ -410,11 +415,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements mChangeListener.onTileChanged(mIntent.getComponent()); } stopPackageListening(); - if (mBound) { - // Trying to bind again will check the state of the package before bothering to bind. - if (DEBUG) Log.d(TAG, "Trying to rebind"); - setBindService(true); - } + mExecutor.execute(() -> { + if (mBound.get()) { + // Trying to bind again will check the state of the package before bothering to + // bind. + if (DEBUG) Log.d(TAG, "Trying to rebind"); + setBindService(true); + } + + }); } private boolean isComponentAvailable() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index 7a10a27f6aca..941a9d6dc952 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -35,6 +35,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener; import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.List; import java.util.Objects; @@ -75,12 +76,12 @@ public class TileServiceManager { TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, - CustomTileAddedRepository customTileAddedRepository) { + CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { this(tileServices, handler, userTracker, customTileAddedRepository, new TileLifecycleManager(handler, tileServices.getContext(), tileServices, - new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher, - new Intent(TileService.ACTION_QS_TILE).setComponent(component), - userTracker.getUserHandle())); + new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher, + new Intent(TileService.ACTION_QS_TILE).setComponent(component), + userTracker.getUserHandle(), executor)); } @VisibleForTesting @@ -203,7 +204,7 @@ public class TileServiceManager { mBound = true; mJustBound = true; mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); } private void unbindService() { @@ -213,7 +214,7 @@ public class TileServiceManager { } mBound = false; mJustBound = false; - mStateManager.setBindService(false); + mStateManager.executeSetBindService(false); } public void calculateBindPriority(long currentTime) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 121955cced1a..a07b955fdb73 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -39,6 +39,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; @@ -47,6 +48,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.ArrayList; import java.util.Collections; @@ -79,6 +81,7 @@ public class TileServices extends IQSService.Stub { private final StatusBarIconController mStatusBarIconController; private final PanelInteractor mPanelInteractor; private final CustomTileAddedRepository mCustomTileAddedRepository; + private final DelayableExecutor mBackgroundExecutor; private int mMaxBound = DEFAULT_MAX_BOUND; @@ -92,7 +95,8 @@ public class TileServices extends IQSService.Stub { CommandQueue commandQueue, StatusBarIconController statusBarIconController, PanelInteractor panelInteractor, - CustomTileAddedRepository customTileAddedRepository) { + CustomTileAddedRepository customTileAddedRepository, + @Background DelayableExecutor backgroundExecutor) { mHost = host; mKeyguardStateController = keyguardStateController; mContext = mHost.getContext(); @@ -105,6 +109,7 @@ public class TileServices extends IQSService.Stub { mCommandQueue.addCallback(mRequestListeningCallback); mPanelInteractor = panelInteractor; mCustomTileAddedRepository = customTileAddedRepository; + mBackgroundExecutor = backgroundExecutor; } public Context getContext() { @@ -132,7 +137,7 @@ public class TileServices extends IQSService.Stub { protected TileServiceManager onCreateTileService(ComponentName component, BroadcastDispatcher broadcastDispatcher) { return new TileServiceManager(this, mHandlerProvider.get(), component, - broadcastDispatcher, mUserTracker, mCustomTileAddedRepository); + broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor); } public void freeService(CustomTile tile, TileServiceManager service) { diff --git a/packages/SystemUI/src/com/android/systemui/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/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 2312c705cd6e..4bc7ec844794 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -343,22 +343,24 @@ public class LongScreenshotActivity extends Activity { } else { String editorPackage = getString(R.string.config_screenshotEditor); Intent intent = new Intent(Intent.ACTION_EDIT); - if (!TextUtils.isEmpty(editorPackage)) { - intent.setComponent(ComponentName.unflattenFromString(editorPackage)); - } intent.setDataAndType(uri, "image/png"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + Bundle options = null; - mTransitionView.setImageBitmap(mOutputBitmap); - mTransitionView.setVisibility(View.VISIBLE); - mTransitionView.setTransitionName( - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); - // TODO: listen for transition completing instead of finishing onStop - mTransitionStarted = true; - startActivity(intent, - ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView, - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle()); + // Skip shared element transition for implicit edit intents + if (!TextUtils.isEmpty(editorPackage)) { + intent.setComponent(ComponentName.unflattenFromString(editorPackage)); + mTransitionView.setImageBitmap(mOutputBitmap); + mTransitionView.setVisibility(View.VISIBLE); + mTransitionView.setTransitionName( + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); + options = ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView, + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle(); + // TODO: listen for transition completing instead of finishing onStop + mTransitionStarted = true; + } + startActivity(intent, options); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java index 860bfe37aee2..13678b0e7187 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java @@ -121,13 +121,15 @@ public class OverlayActionChip extends FrameLayout { LinearLayout.LayoutParams textParams = (LinearLayout.LayoutParams) mTextView.getLayoutParams(); if (hasText) { - int paddingHorizontal = mContext.getResources().getDimensionPixelSize( - R.dimen.overlay_action_chip_padding_horizontal); + int paddingStart = mContext.getResources().getDimensionPixelSize( + R.dimen.overlay_action_chip_padding_start); int spacing = mContext.getResources().getDimensionPixelSize( R.dimen.overlay_action_chip_spacing); - iconParams.setMarginStart(paddingHorizontal); + int paddingEnd = mContext.getResources().getDimensionPixelSize( + R.dimen.overlay_action_chip_padding_end); + iconParams.setMarginStart(paddingStart); iconParams.setMarginEnd(spacing); - textParams.setMarginEnd(paddingHorizontal); + textParams.setMarginEnd(paddingEnd); } else { int paddingHorizontal = mContext.getResources().getDimensionPixelSize( R.dimen.overlay_action_chip_icon_only_padding_horizontal); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index 20313c3df465..a048f543d476 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -54,12 +54,20 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { * Listener will also be immediately notified with the current values. */ fun addExpansionListener(listener: ShadeExpansionListener) { - expansionListeners.add(listener) + addShadeExpansionListener(listener) listener.onPanelExpansionChanged( ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount) ) } + /** + * Adds a listener that will be notified when the panel expansion fraction has changed. + * @see #addExpansionListener + */ + fun addShadeExpansionListener(listener: ShadeExpansionListener) { + expansionListeners.add(listener) + } + /** Removes an expansion listener. */ fun removeExpansionListener(listener: ShadeExpansionListener) { expansionListeners.remove(listener) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java index 12f2c22ab86a..f84b96c94a91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java @@ -15,13 +15,14 @@ */ package com.android.systemui.statusbar.connectivity; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT; import android.content.Context; import android.content.Intent; -import android.net.NetworkCapabilities; import android.net.wifi.WifiManager; import android.os.Handler; import android.text.Html; @@ -37,6 +38,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import java.io.PrintWriter; +import java.util.BitSet; /** */ public class WifiSignalController extends SignalController<WifiState, IconGroup> { @@ -56,8 +58,12 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> WifiManager wifiManager, WifiStatusTrackerFactory trackerFactory, @Background Handler bgHandler) { - super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI, - callbackHandler, networkController); + super( + "WifiSignalController", + context, + TRANSPORT_WIFI, + callbackHandler, + networkController); mBgHandler = bgHandler; mWifiManager = wifiManager; mWifiTracker = trackerFactory.createTracker(this::handleStatusUpdated, bgHandler); @@ -160,7 +166,10 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> // The WiFi signal level returned by WifiManager#calculateSignalLevel start from 0, so // WifiManager#getMaxSignalLevel + 1 represents the total level buckets count. int totalLevel = mWifiManager.getMaxSignalLevel() + 1; - boolean noInternet = mCurrentState.inetCondition == 0; + // A carrier merged connection could come from a WIFI *or* CELLULAR transport, so we can't + // use [mCurrentState.inetCondition], which only checks the WIFI status. Instead, check if + // the default connection is validated at all. + boolean noInternet = !mCurrentState.isDefaultConnectionValidated; if (mCurrentState.connected) { return SignalDrawable.getState(level, totalLevel, noInternet); } else if (mCurrentState.enabled) { @@ -236,6 +245,18 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> && mCurrentState.isCarrierMerged && (mCurrentState.subId == subId); } + @Override + void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) { + mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0; + // Because a carrier merged connection can come from either a CELLULAR *or* WIFI transport, + // we need to also store if either transport is validated to correctly display the carrier + // merged case. + mCurrentState.isDefaultConnectionValidated = + validatedTransports.get(TRANSPORT_CELLULAR) + || validatedTransports.get(TRANSPORT_WIFI); + notifyListenersIfNecessary(); + } + @VisibleForTesting void setActivity(int wifiActivity) { mCurrentState.activityIn = wifiActivity == DATA_ACTIVITY_INOUT diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt index d32e34915c61..63a63de83d71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt @@ -24,6 +24,14 @@ internal class WifiState( @JvmField var isDefault: Boolean = false, @JvmField var statusLabel: String? = null, @JvmField var isCarrierMerged: Boolean = false, + /** + * True if the current default connection is validated for *any* transport, not just wifi. + * (Specifically TRANSPORT_CELLULAR *or* TRANSPORT_WIFI.) + * + * This should *only* be used when calculating information for the carrier merged connection and + * *not* for typical wifi connections. See b/225902574. + */ + @JvmField var isDefaultConnectionValidated: Boolean = false, @JvmField var subId: Int = 0 ) : ConnectivityState() { @@ -35,6 +43,7 @@ internal class WifiState( isDefault = state.isDefault statusLabel = state.statusLabel isCarrierMerged = state.isCarrierMerged + isDefaultConnectionValidated = state.isDefaultConnectionValidated subId = state.subId } @@ -45,6 +54,7 @@ internal class WifiState( .append(",isDefault=").append(isDefault) .append(",statusLabel=").append(statusLabel) .append(",isCarrierMerged=").append(isCarrierMerged) + .append(",isDefaultConnectionValidated=").append(isDefaultConnectionValidated) .append(",subId=").append(subId) } @@ -54,6 +64,7 @@ internal class WifiState( "isDefault", "statusLabel", "isCarrierMerged", + "isDefaultConnectionValidated", "subId") return super.tableColumns() + columns @@ -65,6 +76,7 @@ internal class WifiState( isDefault, statusLabel, isCarrierMerged, + isDefaultConnectionValidated, subId).map { it.toString() } @@ -84,6 +96,7 @@ internal class WifiState( if (isDefault != other.isDefault) return false if (statusLabel != other.statusLabel) return false if (isCarrierMerged != other.isCarrierMerged) return false + if (isDefaultConnectionValidated != other.isDefaultConnectionValidated) return false if (subId != other.subId) return false return true @@ -96,6 +109,7 @@ internal class WifiState( result = 31 * result + isDefault.hashCode() result = 31 * result + (statusLabel?.hashCode() ?: 0) result = 31 * result + isCarrierMerged.hashCode() + result = 31 * result + isDefaultConnectionValidated.hashCode() result = 31 * result + subId return result } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt index 92a8356b7f07..1aeb6b304ea1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt @@ -22,7 +22,6 @@ import android.os.Looper import android.view.Choreographer import android.view.InputEvent import android.view.MotionEvent -import com.android.systemui.settings.DisplayTracker import com.android.systemui.shared.system.InputChannelCompat import com.android.systemui.shared.system.InputMonitorCompat @@ -39,7 +38,7 @@ import com.android.systemui.shared.system.InputMonitorCompat */ abstract class GenericGestureDetector( private val tag: String, - private val displayTracker: DisplayTracker + private val displayId: Int, ) { /** * Active callbacks, each associated with a tag. Gestures will only be monitored if @@ -87,7 +86,7 @@ abstract class GenericGestureDetector( internal open fun startGestureListening() { stopGestureListening() - inputMonitor = InputMonitorCompat(tag, displayTracker.defaultDisplayId).also { + inputMonitor = InputMonitorCompat(tag, displayId).also { inputReceiver = it.getInputReceiver( Looper.getMainLooper(), Choreographer.getInstance(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt index 6d60f4a9affa..2fd0a5324d15 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt @@ -36,7 +36,10 @@ abstract class SwipeUpGestureHandler( displayTracker: DisplayTracker, private val logger: SwipeUpGestureLogger, private val loggerTag: String, -) : GenericGestureDetector(SwipeUpGestureHandler::class.simpleName!!, displayTracker) { +) : GenericGestureDetector( + SwipeUpGestureHandler::class.simpleName!!, + displayTracker.defaultDisplayId +) { private var startY: Float = 0f private var startTime: Long = 0L diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt index a901d5979576..ed30f2fc2e10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt @@ -32,7 +32,10 @@ import javax.inject.Inject class TapGestureDetector @Inject constructor( private val context: Context, displayTracker: DisplayTracker -) : GenericGestureDetector(TapGestureDetector::class.simpleName!!, displayTracker) { +) : GenericGestureDetector( + TapGestureDetector::class.simpleName!!, + displayTracker.defaultDisplayId +) { private val gestureListener = object : GestureDetector.SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index d9dc8878fa61..bbb4f2449330 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -236,7 +236,11 @@ constructor( override fun postStartActivityDismissingKeyguard(intent: Intent, delay: Int) { postOnUiThread(delay) { - activityStarterInternal.startActivityDismissingKeyguard(intent = intent) + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + onlyProvisioned = true, + dismissShade = true, + ) } } @@ -248,6 +252,8 @@ constructor( postOnUiThread(delay) { activityStarterInternal.startActivityDismissingKeyguard( intent = intent, + onlyProvisioned = true, + dismissShade = true, animationController = animationController, ) } @@ -262,6 +268,8 @@ constructor( postOnUiThread(delay) { activityStarterInternal.startActivityDismissingKeyguard( intent = intent, + onlyProvisioned = true, + dismissShade = true, animationController = animationController, customMessage = customMessage, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 6742e4f3041e..bd7840d447cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -190,7 +190,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } } - @VisibleForTesting public enum BiometricUiEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "A biometric event of type fingerprint succeeded.") @@ -221,7 +220,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp BIOMETRIC_IRIS_ERROR(404), @UiEvent(doc = "Bouncer was shown as a result of consecutive failed UDFPS attempts.") - BIOMETRIC_BOUNCER_SHOWN(916); + BIOMETRIC_BOUNCER_SHOWN(916), + + @UiEvent(doc = "Screen started waking up with the given PowerManager wake reason.") + STARTED_WAKING_UP(1378); private final int mId; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 560ea8aae594..313410ac45df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -604,6 +604,7 @@ public class NotificationIconAreaController implements } updateAodIconsVisibility(animate, false /* force */); updateAodNotificationIcons(); + updateAodIconColors(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index bd5815aa240f..7bbb03b4bc94 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -304,10 +304,11 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte ActivityManager.getService().resumeAppSwitches(); } catch (RemoteException e) { } - // If we are launching a work activity and require to launch - // separate work challenge, we defer the activity action and cancel - // notification until work challenge is unlocked. - if (isActivityIntent) { + // If the notification should be cancelled on click and we are launching a work activity in + // a locked profile with separate challenge, we defer the activity action and cancelling of + // the notification until work challenge is unlocked. If the notification shouldn't be + // cancelled, the work challenge will be shown by ActivityManager if necessary anyway. + if (isActivityIntent && shouldAutoCancel(entry.getSbn())) { final int userId = intent.getCreatorUserHandle().getIdentifier(); if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) && mKeyguardManager.isDeviceLocked(userId)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 7aa90336e2bf..49de5a232f30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -160,7 +160,7 @@ abstract class StatusBarPipelineModule { @SysUISingleton @SharedConnectivityInputLog fun provideSharedConnectivityTableLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("SharedConnectivityInputLog", 30) + return factory.create("SharedConnectivityInputLog", 60) } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 0e9b6c56437e..81a068d10a14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -38,6 +38,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel @@ -88,6 +89,7 @@ constructor( private val context: Context, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, + airplaneModeRepository: AirplaneModeRepository, // Some "wifi networks" should be rendered as a mobile connection, which is why the wifi // repository is an input to the mobile repository. // See [CarrierMergedConnectionRepository] for details. @@ -106,10 +108,20 @@ constructor( context.getString(R.string.status_bar_network_name_separator) private val carrierMergedSubId: StateFlow<Int?> = - wifiRepository.wifiNetwork - .mapLatest { - if (it is WifiNetworkModel.CarrierMerged) { - it.subscriptionId + combine( + wifiRepository.wifiNetwork, + connectivityRepository.defaultConnections, + airplaneModeRepository.isAirplaneMode, + ) { wifiNetwork, defaultConnections, isAirplaneMode -> + // The carrier merged connection should only be used if it's also the default + // connection or mobile connections aren't available because of airplane mode. + val defaultConnectionIsNonMobile = + defaultConnections.carrierMerged.isDefault || + defaultConnections.wifi.isDefault || + isAirplaneMode + + if (wifiNetwork is WifiNetworkModel.CarrierMerged && defaultConnectionIsNonMobile) { + wifiNetwork.subscriptionId } else { null } @@ -269,12 +281,8 @@ constructor( .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val hasCarrierMergedConnection: StateFlow<Boolean> = - combine( - connectivityRepository.defaultConnections, - carrierMergedSubId, - ) { defaultConnections, carrierMergedSubId -> - defaultConnections.carrierMerged.isDefault || carrierMergedSubId != null - } + carrierMergedSubId + .map { it != null } .distinctUntilChanged() .logDiffsForTable( tableLogger, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index eec91a0bca82..e90f40c74cc5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -155,7 +155,8 @@ constructor( combine( unfilteredSubscriptions, mobileConnectionsRepo.activeMobileDataSubscriptionId, - ) { unfilteredSubs, activeId -> + connectivityRepository.vcnSubId, + ) { unfilteredSubs, activeId, vcnSubId -> // Based on the old logic, if (unfilteredSubs.size != 2) { return@combine unfilteredSubs @@ -182,7 +183,13 @@ constructor( // return the non-opportunistic info return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1) } else { - return@combine if (info1.subscriptionId == activeId) { + // It's possible for the subId of the VCN to disagree with the active subId in + // cases where the system has tried to switch but found no connection. In these + // scenarios, VCN will always have the subId that we want to use, so use that + // value instead of the activeId reported by telephony + val subIdToKeep = vcnSubId ?: activeId + + return@combine if (info1.subscriptionId == subIdToKeep) { listOf(info1) } else { listOf(info2) @@ -259,7 +266,7 @@ constructor( */ override val isDefaultConnectionFailed: StateFlow<Boolean> = combine( - mobileConnectionsRepo.mobileIsDefault, + mobileIsDefault, mobileConnectionsRepo.defaultConnectionIsValidated, forcingCellularValidation, ) { mobileIsDefault, defaultConnectionIsValidated, forcingCellularValidation -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt index 051f43f1059c..cac0ae3dbab4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt @@ -61,6 +61,10 @@ constructor( model::messagePrinter, ) } + + fun logVcnSubscriptionId(subId: Int) { + buffer.log(TAG, LogLevel.DEBUG, { int1 = subId }, { "vcnSubId changed: $int1" }) + } } private const val TAG = "ConnectivityInputLogger" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt index 731f1e028470..7076f345df97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt @@ -27,6 +27,7 @@ import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.annotation.ArrayRes import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable @@ -50,10 +51,13 @@ import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn /** @@ -66,6 +70,16 @@ interface ConnectivityRepository { /** Observable for which connection(s) are currently default. */ val defaultConnections: StateFlow<DefaultConnectionModel> + + /** + * Subscription ID of the [VcnTransportInfo] for the default connection. + * + * If the default network has a [VcnTransportInfo], then that transport info contains a subId of + * the VCN. When VCN is connected and default, this subId is what SystemUI will care about. In + * cases where telephony's activeDataSubscriptionId differs from this value, it is expected to + * eventually catch up and reflect what is represented here in the VcnTransportInfo. + */ + val vcnSubId: StateFlow<Int?> } @SuppressLint("MissingPermission") @@ -118,24 +132,13 @@ constructor( initialValue = defaultHiddenIcons ) - @SuppressLint("MissingPermission") - override val defaultConnections: StateFlow<DefaultConnectionModel> = + private val defaultNetworkCapabilities: SharedFlow<NetworkCapabilities?> = conflatedCallbackFlow { val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { override fun onLost(network: Network) { logger.logOnDefaultLost(network) - // The system no longer has a default network, so everything is - // non-default. - trySend( - DefaultConnectionModel( - Wifi(isDefault = false), - Mobile(isDefault = false), - CarrierMerged(isDefault = false), - Ethernet(isDefault = false), - isValidated = false, - ) - ) + trySend(null) } override fun onCapabilitiesChanged( @@ -143,30 +146,7 @@ constructor( networkCapabilities: NetworkCapabilities, ) { logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities) - - val wifiInfo = - networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) - - val isWifiDefault = - networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null - val isMobileDefault = - networkCapabilities.hasTransport(TRANSPORT_CELLULAR) - val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true - val isEthernetDefault = - networkCapabilities.hasTransport(TRANSPORT_ETHERNET) - - val isValidated = - networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) - - trySend( - DefaultConnectionModel( - Wifi(isWifiDefault), - Mobile(isMobileDefault), - CarrierMerged(isCarrierMergedDefault), - Ethernet(isEthernetDefault), - isValidated, - ) - ) + trySend(networkCapabilities) } } @@ -174,6 +154,61 @@ constructor( awaitClose { connectivityManager.unregisterNetworkCallback(callback) } } + .shareIn(scope, SharingStarted.WhileSubscribed()) + + override val vcnSubId: StateFlow<Int?> = + defaultNetworkCapabilities + .map { networkCapabilities -> + networkCapabilities?.run { + val subId = (transportInfo as? VcnTransportInfo)?.subId + // Never return an INVALID_SUBSCRIPTION_ID (-1) + if (subId != INVALID_SUBSCRIPTION_ID) { + subId + } else { + null + } + } + } + .distinctUntilChanged() + /* A note for logging: we use -2 here since -1 == INVALID_SUBSCRIPTION_ID */ + .onEach { logger.logVcnSubscriptionId(it ?: -2) } + .stateIn(scope, SharingStarted.Eagerly, null) + + @SuppressLint("MissingPermission") + override val defaultConnections: StateFlow<DefaultConnectionModel> = + defaultNetworkCapabilities + .map { networkCapabilities -> + if (networkCapabilities == null) { + // The system no longer has a default network, so everything is + // non-default. + DefaultConnectionModel( + Wifi(isDefault = false), + Mobile(isDefault = false), + CarrierMerged(isDefault = false), + Ethernet(isDefault = false), + isValidated = false, + ) + } else { + val wifiInfo = + networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + val isWifiDefault = + networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null + val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR) + val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true + val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET) + + val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) + + DefaultConnectionModel( + Wifi(isWifiDefault), + Mobile(isMobileDefault), + CarrierMerged(isCarrierMergedDefault), + Ethernet(isEthernetDefault), + isValidated, + ) + } + } .distinctUntilChanged() .onEach { logger.logDefaultConnectionsChanged(it) } .stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 5208064d2c0b..5cc3d52f4494 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -38,7 +38,11 @@ import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository; +import com.android.systemui.statusbar.policy.bluetooth.ConnectionStatusModel; import java.io.PrintWriter; import java.util.ArrayList; @@ -50,14 +54,20 @@ import java.util.concurrent.Executor; import javax.inject.Inject; /** + * Controller for information about bluetooth connections. + * + * Note: Right now, this class and {@link BluetoothRepository} co-exist. Any new code should go in + * {@link BluetoothRepository}, but external clients should query this file for now. */ @SysUISingleton public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback, CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener { private static final String TAG = "BluetoothController"; + private final FeatureFlags mFeatureFlags; private final DumpManager mDumpManager; private final BluetoothLogger mLogger; + private final BluetoothRepository mBluetoothRepository; private final LocalBluetoothManager mLocalBluetoothManager; private final UserManager mUserManager; private final int mCurrentUser; @@ -79,14 +89,18 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @Inject public BluetoothControllerImpl( Context context, + FeatureFlags featureFlags, UserTracker userTracker, DumpManager dumpManager, BluetoothLogger logger, + BluetoothRepository bluetoothRepository, @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager, @Nullable BluetoothAdapter bluetoothAdapter) { + mFeatureFlags = featureFlags; mDumpManager = dumpManager; mLogger = logger; + mBluetoothRepository = bluetoothRepository; mLocalBluetoothManager = localBluetoothManager; mHandler = new H(mainLooper); if (mLocalBluetoothManager != null) { @@ -229,6 +243,16 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } private void updateConnected() { + if (mFeatureFlags.isEnabled(Flags.NEW_BLUETOOTH_REPOSITORY)) { + mBluetoothRepository.fetchConnectionStatusInBackground( + getDevices(), this::onConnectionStatusFetched); + } else { + updateConnectedOld(); + } + } + + /** Used only if {@link Flags.NEW_BLUETOOTH_REPOSITORY} is *not* enabled. */ + private void updateConnectedOld() { // Make sure our connection state is up to date. int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState(); List<CachedBluetoothDevice> newList = new ArrayList<>(); @@ -249,6 +273,12 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa // connected. state = BluetoothAdapter.STATE_DISCONNECTED; } + onConnectionStatusFetched(new ConnectionStatusModel(state, newList)); + } + + private void onConnectionStatusFetched(ConnectionStatusModel status) { + List<CachedBluetoothDevice> newList = status.getConnectedDevices(); + int state = status.getMaxConnectionState(); synchronized (mConnectedDevices) { mConnectedDevices.clear(); mConnectedDevices.addAll(newList); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt new file mode 100644 index 000000000000..80f3d76f0897 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.policy.bluetooth + +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothProfile +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LocalBluetoothManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Repository for information about bluetooth connections. + * + * Note: Right now, this class and [BluetoothController] co-exist. Any new code should go in this + * implementation, but external clients should query [BluetoothController] instead of this class for + * now. + */ +interface BluetoothRepository { + /** + * Fetches the connection statuses for the given [currentDevices] and invokes [callback] once + * those statuses have been fetched. The fetching occurs on a background thread because IPCs may + * be required to fetch the statuses (see b/271058380). + */ + fun fetchConnectionStatusInBackground( + currentDevices: Collection<CachedBluetoothDevice>, + callback: ConnectionStatusFetchedCallback, + ) +} + +/** Implementation of [BluetoothRepository]. */ +@SysUISingleton +class BluetoothRepositoryImpl +@Inject +constructor( + @Application private val scope: CoroutineScope, + @Background private val bgDispatcher: CoroutineDispatcher, + private val localBluetoothManager: LocalBluetoothManager?, +) : BluetoothRepository { + override fun fetchConnectionStatusInBackground( + currentDevices: Collection<CachedBluetoothDevice>, + callback: ConnectionStatusFetchedCallback, + ) { + scope.launch { + val result = fetchConnectionStatus(currentDevices) + callback.onConnectionStatusFetched(result) + } + } + + private suspend fun fetchConnectionStatus( + currentDevices: Collection<CachedBluetoothDevice>, + ): ConnectionStatusModel { + return withContext(bgDispatcher) { + val minimumMaxConnectionState = + localBluetoothManager?.bluetoothAdapter?.connectionState + ?: BluetoothProfile.STATE_DISCONNECTED + var maxConnectionState = + if (currentDevices.isEmpty()) { + minimumMaxConnectionState + } else { + currentDevices + .maxOf { it.maxConnectionState } + .coerceAtLeast(minimumMaxConnectionState) + } + + val connectedDevices = currentDevices.filter { it.isConnected } + + if ( + connectedDevices.isEmpty() && maxConnectionState == BluetoothAdapter.STATE_CONNECTED + ) { + // If somehow we think we are connected, but have no connected devices, we aren't + // connected. + maxConnectionState = BluetoothAdapter.STATE_DISCONNECTED + } + + ConnectionStatusModel(maxConnectionState, connectedDevices) + } + } +} + +data class ConnectionStatusModel( + /** The maximum connection state out of all current devices. */ + val maxConnectionState: Int, + /** A list of devices that are currently connected. */ + val connectedDevices: List<CachedBluetoothDevice>, +) + +/** Callback notified when the new status has been fetched. */ +fun interface ConnectionStatusFetchedCallback { + fun onConnectionStatusFetched(status: ConnectionStatusModel) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 1b7353923ada..e1a7b6d59f41 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -62,15 +62,16 @@ import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.statusbar.policy.WalletControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; - -import java.util.concurrent.Executor; - -import javax.inject.Named; +import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository; +import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl; import dagger.Binds; import dagger.Module; import dagger.Provides; +import java.util.concurrent.Executor; + +import javax.inject.Named; /** Dagger Module for code in the statusbar.policy package. */ @Module @@ -84,6 +85,10 @@ public interface StatusBarPolicyModule { /** */ @Binds + BluetoothRepository provideBluetoothRepository(BluetoothRepositoryImpl impl); + + /** */ + @Binds CastController provideCastController(CastControllerImpl controllerImpl); /** */ diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt index 58f22466d44d..57b9f914d4d6 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt +++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt @@ -56,18 +56,6 @@ class DynamicColors { Pair.create("on_error", MDC.onError()), Pair.create("error_container", MDC.errorContainer()), Pair.create("on_error_container", MDC.onErrorContainer()), - Pair.create("primary_fixed", MDC.primaryFixed()), - Pair.create("primary_fixed_dim", MDC.primaryFixedDim()), - Pair.create("on_primary_fixed", MDC.onPrimaryFixed()), - Pair.create("on_primary_fixed_variant", MDC.onPrimaryFixedVariant()), - Pair.create("secondary_fixed", MDC.secondaryFixed()), - Pair.create("secondary_fixed_dim", MDC.secondaryFixedDim()), - Pair.create("on_secondary_fixed", MDC.onSecondaryFixed()), - Pair.create("on_secondary_fixed_variant", MDC.onSecondaryFixedVariant()), - Pair.create("tertiary_fixed", MDC.tertiaryFixed()), - Pair.create("tertiary_fixed_dim", MDC.tertiaryFixedDim()), - Pair.create("on_tertiary_fixed", MDC.onTertiaryFixed()), - Pair.create("on_tertiary_fixed_variant", MDC.onTertiaryFixedVariant()), Pair.create("control_activated", MDC.controlActivated()), Pair.create("control_normal", MDC.controlNormal()), Pair.create("control_highlight", MDC.controlHighlight()), @@ -92,7 +80,24 @@ class DynamicColors { Pair.create( "palette_key_color_neutral_variant", MDC.neutralVariantPaletteKeyColor() - ) + ), + ) + + @JvmField + val FIXED_COLORS_MAPPED: List<Pair<String, DynamicColor>> = + arrayListOf( + Pair.create("primary_fixed", MDC.primaryFixed()), + Pair.create("primary_fixed_dim", MDC.primaryFixedDim()), + Pair.create("on_primary_fixed", MDC.onPrimaryFixed()), + Pair.create("on_primary_fixed_variant", MDC.onPrimaryFixedVariant()), + Pair.create("secondary_fixed", MDC.secondaryFixed()), + Pair.create("secondary_fixed_dim", MDC.secondaryFixedDim()), + Pair.create("on_secondary_fixed", MDC.onSecondaryFixed()), + Pair.create("on_secondary_fixed_variant", MDC.onSecondaryFixedVariant()), + Pair.create("tertiary_fixed", MDC.tertiaryFixed()), + Pair.create("tertiary_fixed_dim", MDC.tertiaryFixedDim()), + Pair.create("on_tertiary_fixed", MDC.onTertiaryFixed()), + Pair.create("on_tertiary_fixed_variant", MDC.onTertiaryFixedVariant()), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 4b73d6190d99..c1999b284553 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -624,6 +624,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { FabricatedOverlay overlay = newFabricatedOverlay("dynamic"); assignDynamicPaletteToOverlay(overlay, true /* isDark */); assignDynamicPaletteToOverlay(overlay, false /* isDark */); + assignFixedColorsToOverlay(overlay); return overlay; } @@ -638,6 +639,15 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { }); } + private void assignFixedColorsToOverlay(FabricatedOverlay overlay) { + DynamicColors.FIXED_COLORS_MAPPED.forEach(p -> { + String resourceName = "android:color/system_" + p.first; + int colorValue = p.second.getArgb(mDynamicSchemeLight); + overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue, + null /* configuration */); + }); + } + /** * Checks if the color scheme in mColorScheme matches the current system palettes. * @param managedProfiles List of managed profiles for this user. @@ -666,7 +676,9 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { && res.getColor(android.R.color.system_primary_container_dark, theme) == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeDark) && res.getColor(android.R.color.system_primary_container_light, theme) - == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeLight))) { + == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeLight) + && res.getColor(android.R.color.system_primary_fixed, theme) + == MaterialDynamicColors.primaryFixed().getArgb(mDynamicSchemeLight))) { return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index d74906af38da..eed7950abacb 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -154,10 +154,6 @@ constructor( pendingScrimReadyCallback = onReady } } else if (isFolded && !isFoldHandled && alwaysOnEnabled && isDozing) { - // Screen turning on for the first time after folding and we are already dozing - // We should play the folding to AOD animation - isFoldHandled = true - setAnimationState(playing = true) getShadeFoldAnimator().prepareFoldToAodAnimation() @@ -173,6 +169,13 @@ constructor( // No animation, call ready callback immediately onReady.run() } + + if (isFolded) { + // Any time the screen turns on, this state needs to be reset if the device has been + // folded. Reaching this line implies AOD has been shown in one way or another, + // if enabled + isFoldHandled = true + } } /** Called when keyguard scrim opaque changed */ diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java index fc0033d71844..2d1e622fbdce 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/Events.java +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -98,6 +98,8 @@ public class Events { public static final int DISMISS_REASON_OUTPUT_CHOOSER = 8; public static final int DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED = 9; public static final int DISMISS_REASON_CSD_WARNING_TIMEOUT = 10; + public static final int DISMISS_REASON_POSTURE_CHANGED = 11; + public static final String[] DISMISS_REASONS = { "unknown", "touch_outside", @@ -109,7 +111,8 @@ public class Events { "a11y_stream_changed", "output_chooser", "usb_temperature_below_threshold", - "csd_warning_timeout" + "csd_warning_timeout", + "posture_changed" }; public static final int SHOW_REASON_UNKNOWN = 0; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 91078dc65477..f893cf4694f9 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -34,6 +34,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL; import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder; +import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; import android.animation.Animator; @@ -129,6 +130,7 @@ import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.AlphaTintDrawableWrapper; import com.android.systemui.util.DeviceConfigProxy; @@ -184,7 +186,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final boolean mChangeVolumeRowTintWhenInactive; private final Context mContext; - private final H mHandler = new H(); + private final H mHandler; private final VolumeDialogController mController; private final DeviceProvisionedController mDeviceProvisionedController; private final Region mTouchableRegion = new Region(); @@ -259,16 +261,13 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final AccessibilityManagerWrapper mAccessibilityMgr; private final Object mSafetyWarningLock = new Object(); private final Accessibility mAccessibility = new Accessibility(); - private final ConfigurationController mConfigurationController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final VolumePanelFactory mVolumePanelFactory; private final CsdWarningDialog.Factory mCsdWarningDialogFactory; private final ActivityStarter mActivityStarter; - private boolean mShowing; private boolean mShowA11yStream; - private int mActiveStream; private int mPrevActiveStream; private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE; @@ -300,6 +299,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, @VisibleForTesting int mVolumeRingerMuteIconDrawableId; + private int mOriginalGravity; + private final DevicePostureController.Callback mDevicePostureControllerCallback; + private final DevicePostureController mDevicePostureController; + private @DevicePostureController.DevicePostureInt int mDevicePosture; + private int mOrientation; + public VolumeDialogImpl( Context context, VolumeDialogController volumeDialogController, @@ -313,9 +318,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, DeviceConfigProxy deviceConfigProxy, Executor executor, CsdWarningDialog.Factory csdWarningDialogFactory, + DevicePostureController devicePostureController, + Looper looper, DumpManager dumpManager) { mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); + mHandler = new H(looper); mController = volumeDialogController; mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); @@ -357,6 +365,16 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, initDimens(); + mOrientation = mContext.getResources().getConfiguration().orientation; + mDevicePostureController = devicePostureController; + if (mDevicePostureController != null) { + int initialPosture = mDevicePostureController.getDevicePosture(); + mDevicePosture = initialPosture; + mDevicePostureControllerCallback = this::onPostureChanged; + } else { + mDevicePostureControllerCallback = null; + } + mDeviceConfigProxy = deviceConfigProxy; mExecutor = executor; mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, @@ -365,6 +383,25 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } /** + * Adjust the dialog location on the screen in order to avoid drawing on the hinge. + */ + private void adjustPositionOnScreen() { + final boolean isPortrait = mOrientation == Configuration.ORIENTATION_PORTRAIT; + final boolean isHalfOpen = + mDevicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED; + final boolean isTabletop = isPortrait && isHalfOpen; + WindowManager.LayoutParams lp = mWindow.getAttributes(); + int gravity = isTabletop ? (mOriginalGravity | Gravity.TOP) : mOriginalGravity; + mWindowGravity = Gravity.getAbsoluteGravity(gravity, + mContext.getResources().getConfiguration().getLayoutDirection()); + lp.gravity = mWindowGravity; + } + + @VisibleForTesting int getWindowGravity() { + return mWindowGravity; + } + + /** * If ringer and notification are the same stream (T and earlier), use notification-like bell * icon set. * If ringer and notification are separated, then use generic speaker icons. @@ -419,6 +456,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mExecutor, this::onDeviceConfigChange); + + if (mDevicePostureController != null) { + mDevicePostureController.addCallback(mDevicePostureControllerCallback); + } } @Override @@ -427,6 +468,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mHandler.removeCallbacksAndMessages(null); mConfigurationController.removeCallback(this); mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange); + if (mDevicePostureController != null) { + mDevicePostureController.removeCallback(mDevicePostureControllerCallback); + } } /** @@ -441,7 +485,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mSeparateNotification = newVal; updateRingerModeIconSet(); updateRingRowIcon(); - } } } @@ -500,7 +543,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private void initDialog(int lockTaskModeState) { mDialog = new CustomDialog(mContext); - initDimens(); mConfigurableTexts = new ConfigurableTexts(mContext); @@ -524,14 +566,13 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, lp.setTitle(VolumeDialogImpl.class.getSimpleName()); lp.windowAnimations = -1; - mWindowGravity = Gravity.getAbsoluteGravity( - mContext.getResources().getInteger(R.integer.volume_dialog_gravity), + mOriginalGravity = mContext.getResources().getInteger(R.integer.volume_dialog_gravity); + mWindowGravity = Gravity.getAbsoluteGravity(mOriginalGravity, mContext.getResources().getConfiguration().getLayoutDirection()); lp.gravity = mWindowGravity; mWindow.setAttributes(lp); mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT); - mDialog.setContentView(R.layout.volume_dialog); mDialogView = mDialog.findViewById(R.id.volume_dialog); mDialogView.setAlpha(0); @@ -1539,8 +1580,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, animator.translationX( (isWindowGravityLeft() ? -1 : 1) * mDialogView.getWidth() / 2.0f); } + animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS, mDialogHideAnimationDurationMs)).start(); + checkODICaptionsTooltip(true); synchronized (mSafetyWarningLock) { if (mSafetyWarning != null) { @@ -2237,6 +2280,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mTopContainer.setBackground(background); } + @Override + public void onConfigChanged(Configuration config) { + mOrientation = config.orientation; + } + private final VolumeDialogController.Callbacks mControllerCallbackH = new VolumeDialogController.Callbacks() { @Override @@ -2313,6 +2361,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } }; + @VisibleForTesting void onPostureChanged(int posture) { + dismiss(DISMISS_REASON_POSTURE_CHANGED); + mDevicePosture = posture; + } + private final class H extends Handler { private static final int SHOW = 1; private static final int DISMISS = 2; @@ -2323,8 +2376,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private static final int STATE_CHANGED = 7; private static final int CSD_TIMEOUT = 8; - public H() { - super(Looper.getMainLooper()); + H(Looper looper) { + super(looper); } @Override @@ -2370,6 +2423,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, protected void onStart() { super.setCanceledOnTouchOutside(true); super.onStart(); + adjustPositionOnScreen(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index 14d3ca334073..bb04f82fcffa 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -18,6 +18,7 @@ package com.android.systemui.volume.dagger; import android.content.Context; import android.media.AudioManager; +import android.os.Looper; import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.dagger.qualifiers.Main; @@ -28,6 +29,7 @@ import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.volume.CsdWarningDialog; @@ -42,7 +44,6 @@ import dagger.Provides; import java.util.concurrent.Executor; - /** Dagger Module for code in the volume package. */ @Module public interface VolumeModule { @@ -65,6 +66,7 @@ public interface VolumeModule { DeviceConfigProxy deviceConfigProxy, @Main Executor executor, CsdWarningDialog.Factory csdFactory, + DevicePostureController devicePostureController, DumpManager dumpManager) { VolumeDialogImpl impl = new VolumeDialogImpl( context, @@ -79,6 +81,8 @@ public interface VolumeModule { deviceConfigProxy, executor, csdFactory, + devicePostureController, + Looper.getMainLooper(), dumpManager); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); diff --git a/packages/SystemUI/src/com/android/systemui/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/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index fb738454fc71..d8e2a3842e85 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -134,6 +134,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { private KeyguardClockSwitchController mController; private View mSliceView; + private LinearLayout mStatusArea; private FakeExecutor mExecutor; @Before @@ -195,8 +196,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { mSliceView = new View(getContext()); when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView); - when(mView.findViewById(R.id.keyguard_status_area)).thenReturn( - new LinearLayout(getContext())); + mStatusArea = new LinearLayout(getContext()); + when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea); } @Test @@ -401,6 +402,15 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { assertNull(mController.getClock()); } + @Test + public void testSetAlpha_setClockAlphaForCLockFace() { + mController.onViewAttached(); + mController.setAlpha(0.5f); + verify(mLargeClockView).setAlpha(0.5f); + verify(mSmallClockView).setAlpha(0.5f); + assertEquals(0.5f, mStatusArea.getAlpha(), 0.0f); + } + private void verifyAttachment(VerificationMode times) { verify(mClockRegistry, times).registerClockChangeListener( any(ClockRegistry.ClockChangeListener.class)); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 8a05a37ad0dd..65ddb53f748b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -64,7 +64,6 @@ import com.android.systemui.biometrics.SideFpsUiRequestSource; import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; @@ -398,26 +397,6 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test - public void showNextSecurityScreenOrFinish_DeviceNotSecure_prevent_bypass_on() { - when(mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)).thenReturn(true); - // GIVEN the current security method is SimPin - when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false); - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false); - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin); - - // WHEN a request is made from the SimPin screens to show the next security method - when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None); - mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( - /* authenticated= */true, - TARGET_USER_ID, - /* bypassSecondaryLockScreen= */true, - SecurityMode.SimPin); - - // THEN the next security method of None will dismiss keyguard. - verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt()); - } - - @Test public void showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() { //GIVEN current security mode has been set to PIN mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.PIN); @@ -608,7 +587,6 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Test public void testSecurityCallbackFinish_cannotDismissLockScreenAndNotStrongAuth() { - when(mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)).thenReturn(true); when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false); mKeyguardSecurityContainerController.finish(false, 0); verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt index 508aea51b666..a8c281c24700 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt @@ -24,6 +24,8 @@ class KeyguardStatusViewTest : SysuiTestCase() { get() = keyguardStatusView.findViewById(R.id.status_view_media_container) private val statusViewContainer: ViewGroup get() = keyguardStatusView.findViewById(R.id.status_view_container) + private val clockView: ViewGroup + get() = keyguardStatusView.findViewById(R.id.keyguard_clock_container) private val childrenExcludingMedia get() = statusViewContainer.children.filter { it != mediaView } @@ -56,4 +58,12 @@ class KeyguardStatusViewTest : SysuiTestCase() { assertThat(it.translationY).isEqualTo(translationY) } } + + @Test + fun setAlphaExcludeClock() { + keyguardStatusView.setAlpha(0.5f, /* excludeClock= */true) + assertThat(statusViewContainer.alpha).isNotEqualTo(0.5f) + assertThat(mediaView.alpha).isEqualTo(0.5f) + assertThat(clockView.alpha).isNotEqualTo(0.5f) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt new file mode 100644 index 000000000000..cac618b21dc7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.content.Context +import android.hardware.biometrics.SensorProperties +import android.hardware.fingerprint.FingerprintManager +import android.hardware.fingerprint.FingerprintSensorProperties +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.view.ViewGroup.LayoutParams +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.airbnb.lottie.LottieAnimationView +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.`when` as whenEver +import org.mockito.junit.MockitoJUnit + +private const val SENSOR_ID = 1 + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AuthBiometricFingerprintIconControllerTest : SysuiTestCase() { + + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var iconView: LottieAnimationView + @Mock private lateinit var iconViewOverlay: LottieAnimationView + @Mock private lateinit var layoutParam: LayoutParams + @Mock private lateinit var fingerprintManager: FingerprintManager + + private lateinit var controller: AuthBiometricFingerprintIconController + + @Before + fun setUp() { + context.addMockSystemService(Context.FINGERPRINT_SERVICE, fingerprintManager) + whenEver(iconView.layoutParams).thenReturn(layoutParam) + whenEver(iconViewOverlay.layoutParams).thenReturn(layoutParam) + } + + @Test + fun testIconContentDescription_SfpsDevice() { + setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_POWER_BUTTON) + controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) + + assertThat(controller.getIconContentDescription(AuthBiometricView.STATE_AUTHENTICATING)) + .isEqualTo( + context.resources.getString( + R.string.security_settings_sfps_enroll_find_sensor_message + ) + ) + } + + @Test + fun testIconContentDescription_NonSfpsDevice() { + setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) + controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) + + assertThat(controller.getIconContentDescription(AuthBiometricView.STATE_AUTHENTICATING)) + .isEqualTo(context.resources.getString(R.string.fingerprint_dialog_touch_sensor)) + } + + private fun setupFingerprintSensorProperties(sensorType: Int) { + whenEver(fingerprintManager.sensorPropertiesInternal) + .thenReturn( + listOf( + FingerprintSensorPropertiesInternal( + SENSOR_ID, + SensorProperties.STRENGTH_STRONG, + 5 /* maxEnrollmentsPerUser */, + listOf() /* componentInfo */, + sensorType, + true /* halControlsIllumination */, + true /* resetLockoutRequiresHardwareAuthToken */, + listOf() /* sensorLocations */ + ) + ) + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt index 8dfd22378a14..b2e37ccb045b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.panels.AuthorizedPanelsRepository +import com.android.systemui.controls.ui.SelectedItem import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.settings.UserTracker @@ -190,6 +191,9 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { val setCaptor: ArgumentCaptor<Set<String>> = argumentCaptor() verify(authorizedPanelsRepository).addAuthorizedPanels(capture(setCaptor)) assertThat(setCaptor.value).containsExactly(info.componentName.packageName) + val selectedComponentCaptor: ArgumentCaptor<SelectedItem> = argumentCaptor() + verify(controlsController).setPreferredSelection(capture(selectedComponentCaptor)) + assertThat(selectedComponentCaptor.value.componentName).isEqualTo(info.componentName) assertThat(activityRule.activity.triedToFinish).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 8f58140bce43..1d8b5ca1f54a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -52,6 +52,8 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.logging.InstanceId; +import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardSecurityView; @@ -68,6 +70,7 @@ import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -77,6 +80,7 @@ import com.android.systemui.shade.ShadeWindowLogger; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -143,6 +147,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private FalsingCollectorFake mFalsingCollector; private @Mock CentralSurfaces mCentralSurfaces; + private @Mock UiEventLogger mUiEventLogger; + private @Mock SessionTracker mSessionTracker; private FakeFeatureFlags mFeatureFlags; @@ -543,9 +549,32 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { assertTrue(mViewMediator.isShowingAndNotOccluded()); } + @Test + public void testWakeAndUnlocking() { + mViewMediator.onWakeAndUnlocking(); + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); + } + + @Test + public void testOnStartedWakingUp_logsUiEvent() { + final InstanceId instanceId = InstanceId.fakeInstanceId(8); + when(mSessionTracker.getSessionId((anyInt()))).thenReturn(instanceId); + mViewMediator.onStartedWakingUp(PowerManager.WAKE_REASON_LIFT, false); + + verify(mUiEventLogger).logWithInstanceIdAndPosition( + eq(BiometricUnlockController.BiometricUiEvent.STARTED_WAKING_UP), + anyInt(), + any(), + eq(instanceId), + eq(PowerManager.WAKE_REASON_LIFT) + ); + } + private void createAndStartViewMediator() { mViewMediator = new KeyguardViewMediator( mContext, + mUiEventLogger, + mSessionTracker, mUserTracker, mFalsingCollector, mLockPatternUtils, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 7f7952feb10b..faca8a91d6b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -26,6 +26,7 @@ import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECT import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -34,6 +35,7 @@ import android.app.WallpaperColors; import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.view.View; import android.widget.LinearLayout; import android.widget.SeekBar; @@ -60,6 +62,7 @@ import java.util.stream.Collectors; @SmallTest @RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class MediaOutputAdapterTest extends SysuiTestCase { private static final String TEST_DEVICE_NAME_1 = "test_device_name_1"; @@ -116,6 +119,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaItems.add(new MediaItem(mMediaDevice2)); mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar); @@ -202,9 +206,11 @@ public class MediaOutputAdapterTest extends SysuiTestCase { public void advanced_onBindViewHolder_bindPairNew_verifyView() { when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true); mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaItems.add(new MediaItem()); + mMediaOutputAdapter.updateItems(); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); @@ -223,6 +229,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { Collectors.toList())); when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME); mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.getItemCount(); @@ -243,6 +250,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { Collectors.toList())); when(mMediaOutputController.getSessionName()).thenReturn(null); mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.getItemCount(); @@ -602,9 +610,11 @@ public class MediaOutputAdapterTest extends SysuiTestCase { public void advanced_onItemClick_clickPairNew_verifyLaunchBluetoothPairing() { when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true); mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaItems.add(new MediaItem()); + mMediaOutputAdapter.updateItems(); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2); mViewHolder.mContainerLayout.performClick(); @@ -613,6 +623,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onItemClick_clickDevice_verifyConnectDevice() { + when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false); assertThat(mMediaDevice2.getState()).isEqualTo( LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -623,6 +634,21 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @Test + public void onItemClick_clickDeviceWithSessionOngoing_verifyShowsDialog() { + when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(true); + assertThat(mMediaDevice2.getState()).isEqualTo( + LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); + MediaOutputAdapter.MediaDeviceViewHolder spyMediaDeviceViewHolder = spy(mViewHolder); + + mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 0); + mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 1); + spyMediaDeviceViewHolder.mContainerLayout.performClick(); + + verify(mMediaOutputController, never()).connectDevice(mMediaDevice2); + verify(spyMediaDeviceViewHolder).showCustomEndSessionDialog(mMediaDevice2); + } + + @Test public void onItemClick_clicksWithMutingExpectedDeviceExist_cancelsMuteAwaitConnection() { when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false); when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true); @@ -700,6 +726,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect( Collectors.toList())); mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); List<MediaDevice> selectableDevices = new ArrayList<>(); @@ -734,4 +761,18 @@ public class MediaOutputAdapterTest extends SysuiTestCase { verify(mMediaOutputController).setCurrentColorScheme(wallpaperColors, true); } + + @Test + public void updateItems_controllerItemsUpdated_notUpdatesInAdapterUntilUpdateItems() { + when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true); + mMediaOutputAdapter.updateItems(); + List<MediaItem> updatedList = new ArrayList<>(); + updatedList.add(new MediaItem()); + when(mMediaOutputController.getMediaItemList()).thenReturn(updatedList); + assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaItems.size()); + + mMediaOutputAdapter.updateItems(); + + assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(updatedList.size()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index f206409a071e..480d59c5e8bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -239,7 +239,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { when(mMediaOutputBaseAdapter.isDragging()).thenReturn(false); mMediaOutputBaseDialogImpl.refresh(); - verify(mMediaOutputBaseAdapter).notifyDataSetChanged(); + verify(mMediaOutputBaseAdapter).updateItems(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index 21a7a340aa80..425d0bc47a4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -39,6 +39,7 @@ import android.testing.TestableLooper; import android.util.FeatureFlagUtils; import android.view.View; +import androidx.annotation.NonNull; import androidx.test.filters.MediumTest; import com.android.internal.logging.UiEventLogger; @@ -64,6 +65,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.function.Consumer; @MediumTest @RunWith(AndroidTestingRunner.class) @@ -89,7 +91,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); private final MediaMetadata mMediaMetadata = mock(MediaMetadata.class); - private final MediaDescription mMediaDescription = mock(MediaDescription.class); + private final MediaDescription mMediaDescription = mock(MediaDescription.class); private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( NearbyMediaDevicesManager.class); private final AudioManager mAudioManager = mock(AudioManager.class); @@ -102,6 +104,11 @@ public class MediaOutputDialogTest extends SysuiTestCase { private MediaOutputController mMediaOutputController; private final List<String> mFeatures = new ArrayList<>(); + @Override + protected boolean shouldFailOnLeakedReceiver() { + return true; + } + @Before public void setUp() { when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); @@ -120,8 +127,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, mKeyguardManager, mFlags); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; - mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mMediaOutputController, mUiEventLogger); + mMediaOutputDialog = makeTestDialog(mMediaOutputController); mMediaOutputDialog.show(); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice); @@ -130,7 +136,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { @After public void tearDown() { - mMediaOutputDialog.dismissDialog(); + mMediaOutputDialog.dismiss(); } @Test @@ -311,11 +317,9 @@ public class MediaOutputDialogTest extends SysuiTestCase { MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mockMediaOutputController, mUiEventLogger); - testDialog.show(); - - assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + withTestDialog(mockMediaOutputController, testDialog -> { + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + }); } @Test @@ -328,11 +332,9 @@ public class MediaOutputDialogTest extends SysuiTestCase { when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true); when(mockMediaOutputController.isPlaying()).thenReturn(true); when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false); - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mockMediaOutputController, mUiEventLogger); - testDialog.show(); - - assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + withTestDialog(mockMediaOutputController, testDialog -> { + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + }); } @Test @@ -341,11 +343,9 @@ public class MediaOutputDialogTest extends SysuiTestCase { when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null); when(mockMediaOutputController.isPlaying()).thenReturn(false); - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mockMediaOutputController, mUiEventLogger); - testDialog.show(); - - testDialog.onStopButtonClick(); + withTestDialog(mockMediaOutputController, testDialog -> { + testDialog.onStopButtonClick(); + }); verify(mockMediaOutputController).releaseSession(); } @@ -354,13 +354,22 @@ public class MediaOutputDialogTest extends SysuiTestCase { // Check the visibility metric logging by creating a new MediaOutput dialog, // and verify if the calling times increases. public void onCreate_ShouldLogVisibility() { - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mMediaOutputController, mUiEventLogger); - testDialog.show(); - - testDialog.dismissDialog(); + withTestDialog(mMediaOutputController, testDialog -> {}); verify(mUiEventLogger, times(2)) .log(MediaOutputDialog.MediaOutputEvent.MEDIA_OUTPUT_DIALOG_SHOW); } + + @NonNull + private MediaOutputDialog makeTestDialog(MediaOutputController controller) { + return new MediaOutputDialog(mContext, false, mBroadcastSender, + controller, mUiEventLogger); + } + + private void withTestDialog(MediaOutputController controller, Consumer<MediaOutputDialog> c) { + MediaOutputDialog testDialog = makeTestDialog(controller); + testDialog.show(); + c.accept(testDialog); + testDialog.dismiss(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index e4d8b2598fe3..810ab344e7d8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -145,7 +145,8 @@ public class QSTileHostTest extends SysuiTestCase { mMainExecutor = new FakeExecutor(new FakeSystemClock()); mSharedPreferencesByUser = new SparseArray<>(); - when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class))) + when(mTileLifecycleManagerFactory + .create(any(Intent.class), any(UserHandle.class))) .thenReturn(mTileLifecycleManager); when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())) .thenAnswer((Answer<SharedPreferences>) invocation -> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 2e6b0cf7b0a8..67587e3a8914 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -60,6 +60,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Before; @@ -81,6 +83,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { private ComponentName mTileServiceComponentName; private Intent mTileServiceIntent; private UserHandle mUser; + private FakeExecutor mExecutor; private HandlerThread mThread; private Handler mHandler; private TileLifecycleManager mStateManager; @@ -109,12 +112,14 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mThread = new HandlerThread("TestThread"); mThread.start(); mHandler = Handler.createAsync(mThread.getLooper()); + mExecutor = new FakeExecutor(new FakeSystemClock()); mStateManager = new TileLifecycleManager(mHandler, mWrappedContext, mock(IQSService.class), mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); } @After @@ -152,7 +157,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { @Test public void testBind() { - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); } @@ -160,7 +166,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { public void testPackageReceiverExported() throws Exception { // Make sure that we register a receiver setPackageEnabled(false); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); IntentFilter filter = mWrappedContext.mLastIntentFilter; assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_ADDED)); assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)); @@ -170,14 +177,17 @@ public class TileLifecycleManagerTest extends SysuiTestCase { @Test public void testUnbind() { - mStateManager.setBindService(true); - mStateManager.setBindService(false); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); assertFalse(mContext.isBound(mTileServiceComponentName)); } @Test public void testTileServiceCallbacks() throws Exception { - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); mStateManager.onTileAdded(); verify(mMockTileService).onTileAdded(); mStateManager.onStartListening(); @@ -193,7 +203,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { @Test public void testAddedBeforeBind() throws Exception { mStateManager.onTileAdded(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); verify(mMockTileService).onTileAdded(); @@ -203,7 +214,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { public void testListeningBeforeBind() throws Exception { mStateManager.onTileAdded(); mStateManager.onStartListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); verify(mMockTileService).onTileAdded(); @@ -215,7 +227,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onTileAdded(); mStateManager.onStartListening(); mStateManager.onClick(null); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); verify(mMockTileService).onTileAdded(); @@ -228,10 +241,12 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onTileAdded(); mStateManager.onStartListening(); mStateManager.onStopListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); - mStateManager.setBindService(false); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); assertFalse(mContext.isBound(mTileServiceComponentName)); verify(mMockTileService, never()).onStartListening(); } @@ -242,10 +257,12 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onStartListening(); mStateManager.onClick(null); mStateManager.onStopListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); - mStateManager.setBindService(false); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); assertFalse(mContext.isBound(mTileServiceComponentName)); verify(mMockTileService, never()).onClick(null); } @@ -255,7 +272,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onTileAdded(); mStateManager.onStartListening(); setPackageEnabled(false); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); // Package not available, not yet created. verifyBind(0); @@ -267,18 +285,19 @@ public class TileLifecycleManagerTest extends SysuiTestCase { Intent.ACTION_PACKAGE_CHANGED, Uri.fromParts( "package", mTileServiceComponentName.getPackageName(), null))); + mExecutor.runAllReady(); verifyBind(1); } @Test public void testKillProcess() throws Exception { mStateManager.onStartListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); mStateManager.setBindRetryDelay(0); + mExecutor.runAllReady(); mStateManager.onServiceDisconnected(mTileServiceComponentName); - - // Guarantees mHandler has processed all messages. - assertTrue(mHandler.runWithScissors(()->{}, TEST_FAIL_TIMEOUT)); + mExecutor.runAllReady(); // Two calls: one for the first bind, one for the restart. verifyBind(2); @@ -299,9 +318,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); - manager.setBindService(true); + manager.executeSetBindService(true); + mExecutor.runAllReady(); ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class); verify(falseContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any()); @@ -318,9 +339,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); - manager.setBindService(true); + manager.executeSetBindService(true); + mExecutor.runAllReady(); int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE | Context.BIND_WAIVE_PRIORITY; @@ -337,9 +360,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); - manager.setBindService(true); + manager.executeSetBindService(true); + mExecutor.runAllReady(); int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java index 9ca7a8521e95..28331bbfafb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java @@ -187,7 +187,7 @@ public class TileServiceManagerTest extends SysuiTestCase { mTileServiceManager.setBindAllowed(true); ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class); - verify(mTileLifecycle, times(1)).setBindService(captor.capture()); + verify(mTileLifecycle, times(1)).executeSetBindService(captor.capture()); assertTrue((boolean) captor.getValue()); mTileServiceManager.setBindRequested(false); @@ -198,7 +198,7 @@ public class TileServiceManagerTest extends SysuiTestCase { mTileServiceManager.setBindAllowed(false); captor = ArgumentCaptor.forClass(Boolean.class); - verify(mTileLifecycle, times(2)).setBindService(captor.capture()); + verify(mTileLifecycle, times(2)).executeSetBindService(captor.capture()); assertFalse((boolean) captor.getValue()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 12b5656725eb..4bc16a52f8dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -48,6 +48,9 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Assert; @@ -118,7 +121,8 @@ public class TileServicesTest extends SysuiTestCase { mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher, mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController, - mPanelInteractor, mCustomTileAddedRepository); + mPanelInteractor, mCustomTileAddedRepository, + new FakeExecutor(new FakeSystemClock())); } @After @@ -297,10 +301,10 @@ public class TileServicesTest extends SysuiTestCase { BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, KeyguardStateController keyguardStateController, CommandQueue commandQueue, StatusBarIconController statusBarIconController, PanelInteractor panelInteractor, - CustomTileAddedRepository customTileAddedRepository) { + CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController, commandQueue, statusBarIconController, panelInteractor, - customTileAddedRepository); + customTileAddedRepository, executor); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java index 0e2a3acd3df4..190ee81cc55a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java @@ -24,13 +24,13 @@ import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; -import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -76,7 +76,7 @@ public class RemoteTransitionTest extends SysuiTestCase { .addChange(TRANSIT_CLOSE, 0 /* flags */, createTaskInfo(2 /* taskId */, ACTIVITY_TYPE_STANDARD)) .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */) - .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */) + .addChange(TRANSIT_CHANGE, FLAG_IS_DIVIDER_BAR, null /* taskInfo */) .build(); // Check apps extraction RemoteAnimationTarget[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined, @@ -107,7 +107,7 @@ public class RemoteTransitionTest extends SysuiTestCase { RemoteAnimationTarget[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(combined, false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */); assertEquals(1, nonApps.length); - assertTrue(nonApps[0].prefixOrderIndex < closeLayer); + assertTrue(nonApps[0].prefixOrderIndex == Integer.MAX_VALUE); assertEquals(MODE_CHANGING, nonApps[0].mode); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index c7ea09cb519d..2e5afa4a8b50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -340,6 +340,11 @@ public class NetworkControllerBaseTest extends SysuiTestCase { } public void setConnectivityViaCallbackInNetworkController( + Network network, NetworkCapabilities networkCapabilities) { + mDefaultCallbackInNetworkController.onCapabilitiesChanged(network, networkCapabilities); + } + + public void setConnectivityViaCallbackInNetworkController( int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(mNetCapabilities); @@ -351,6 +356,13 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mock(Network.class), builder.build()); } + public void setConnectivityViaDefaultAndNormalCallbackInWifiTracker( + Network network, NetworkCapabilities networkCapabilities) { + mNetworkCallback.onAvailable(network); + mNetworkCallback.onCapabilitiesChanged(network, networkCapabilities); + mDefaultCallbackInWifiTracker.onCapabilitiesChanged(network, networkCapabilities); + } + public void setConnectivityViaCallbackInWifiTracker( int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) { final NetworkCapabilities.Builder builder = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java index 68170ea4b518..44a1c50e5a58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.connectivity; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; + import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -25,6 +28,7 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.net.ConnectivityManager; +import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.vcn.VcnTransportInfo; @@ -43,6 +47,8 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import java.util.Collections; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -269,6 +275,83 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } } + /** Test for b/225902574. */ + @Test + public void vcnOnlyOnUnderlyingNetwork() { + setWifiEnabled(true); + + // Set up a carrier merged network... + WifiInfo underlyingCarrierMergedInfo = Mockito.mock(WifiInfo.class); + when(underlyingCarrierMergedInfo.isCarrierMerged()).thenReturn(true); + when(underlyingCarrierMergedInfo.isPrimary()).thenReturn(true); + int zeroLevel = 0; + when(underlyingCarrierMergedInfo.getRssi()).thenReturn(calculateRssiForLevel(zeroLevel)); + + NetworkCapabilities underlyingNetworkCapabilities = Mockito.mock(NetworkCapabilities.class); + when(underlyingNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) + .thenReturn(true); + when(underlyingNetworkCapabilities.getTransportInfo()) + .thenReturn(underlyingCarrierMergedInfo); + + Network underlyingNetwork = Mockito.mock(Network.class); + when(mMockCm.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingNetworkCapabilities); + + NetworkCapabilities.Builder mainCapabilitiesBuilder = new NetworkCapabilities.Builder(); + mainCapabilitiesBuilder.addTransportType(TRANSPORT_CELLULAR); + mainCapabilitiesBuilder.setTransportInfo(null); + // And make the carrier merged network the underlying network, *not* the main network. + mainCapabilitiesBuilder.setUnderlyingNetworks(Collections.singletonList(underlyingNetwork)); + + Network primaryNetwork = Mockito.mock(Network.class); + int primaryNetworkId = 1; + when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId); + + // WHEN this primary network with underlying carrier merged information is sent + setConnectivityViaDefaultAndNormalCallbackInWifiTracker( + primaryNetwork, mainCapabilitiesBuilder.build()); + + // THEN we see the mobile data indicators for carrier merged + verifyLastMobileDataIndicatorsForVcn( + /* visible= */ true, + /* level= */ zeroLevel, + TelephonyIcons.ICON_CWF, + /* inet= */ false); + + // For each level... + for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) { + int rssi = calculateRssiForLevel(testLevel); + when(underlyingCarrierMergedInfo.getRssi()).thenReturn(rssi); + // WHEN the new level is sent to the callbacks + setConnectivityViaDefaultAndNormalCallbackInWifiTracker( + primaryNetwork, mainCapabilitiesBuilder.build()); + + // WHEN the network is validated + mainCapabilitiesBuilder.addCapability(NET_CAPABILITY_VALIDATED); + setConnectivityViaCallbackInNetworkController( + primaryNetwork, mainCapabilitiesBuilder.build()); + + // THEN we see the mobile data indicators with inet=true (no exclamation mark) + verifyLastMobileDataIndicatorsForVcn( + /* visible= */ true, + testLevel, + TelephonyIcons.ICON_CWF, + /* inet= */ true); + + // WHEN the network is not validated + mainCapabilitiesBuilder.removeCapability(NET_CAPABILITY_VALIDATED); + setConnectivityViaCallbackInNetworkController( + primaryNetwork, mainCapabilitiesBuilder.build()); + + // THEN we see the mobile data indicators with inet=false (exclamation mark) + verifyLastMobileDataIndicatorsForVcn( + /* visible= */ true, + testLevel, + TelephonyIcons.ICON_CWF, + /* inet= */ false); + } + } + @Test public void testDisableWiFiWithVcnWithUnderlyingWifi() { String testSsid = "Test VCN SSID"; @@ -290,11 +373,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } protected void setWifiLevel(int level) { - float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1); - int rssi = (int) (MIN_RSSI + level * amountPerLevel); - // Put RSSI in the middle of the range. - rssi += amountPerLevel / 2; - when(mWifiInfo.getRssi()).thenReturn(rssi); + when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level)); setConnectivityViaCallbackInWifiTracker( NetworkCapabilities.TRANSPORT_WIFI, false, true, mWifiInfo); } @@ -312,19 +391,23 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } protected void setWifiLevelForVcn(int level) { - float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1); - int rssi = (int) (MIN_RSSI + level * amountPerLevel); - // Put RSSI in the middle of the range. - rssi += amountPerLevel / 2; when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo); when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo); - when(mWifiInfo.getRssi()).thenReturn(rssi); + when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level)); when(mWifiInfo.isCarrierMerged()).thenReturn(true); when(mWifiInfo.getSubscriptionId()).thenReturn(1); setConnectivityViaCallbackInWifiTrackerForVcn( NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo); } + private int calculateRssiForLevel(int level) { + float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1); + int rssi = (int) (MIN_RSSI + level * amountPerLevel); + // Put RSSI in the middle of the range. + rssi += amountPerLevel / 2; + return rssi; + } + protected void setWifiStateForVcn(boolean connected, String ssid) { when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo); when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt index 746544a3563f..d3f5adeb05bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt @@ -118,7 +118,10 @@ class GenericGestureDetectorTest : SysuiTestCase() { assertThat(oldCallbackNotified).isFalse() } - inner class TestGestureDetector : GenericGestureDetector("fakeTag", displayTracker) { + inner class TestGestureDetector : GenericGestureDetector( + "fakeTag", + displayTracker.defaultDisplayId + ) { var isGestureListening = false override fun onInputEvent(ev: InputEvent) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt index b6b28c9e4527..4a3080050a37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone import android.app.PendingIntent import android.content.Intent import android.os.RemoteException +import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor @@ -102,6 +103,7 @@ class ActivityStarterImplTest : SysuiTestCase() { activityIntentHelper, mainExecutor, ) + whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER) } @Test @@ -150,11 +152,28 @@ class ActivityStarterImplTest : SysuiTestCase() { @Test fun postStartActivityDismissingKeyguard_intent_postsOnMain() { + whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) val intent = mock(Intent::class.java) underTest.postStartActivityDismissingKeyguard(intent, 0) assertThat(mainExecutor.numPending()).isEqualTo(1) + mainExecutor.runAllReady() + + verify(deviceProvisionedController).isDeviceProvisioned + verify(shadeController).runPostCollapseRunnables() + } + + @Test + fun postStartActivityDismissingKeyguard_intent_notDeviceProvisioned_doesNotProceed() { + whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false) + val intent = mock(Intent::class.java) + + underTest.postStartActivityDismissingKeyguard(intent, 0) + mainExecutor.runAllReady() + + verify(deviceProvisionedController).isDeviceProvisioned + verify(shadeController, never()).runPostCollapseRunnables() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index bde05b9f499e..5a887ebcee80 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository @@ -131,6 +132,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { context, IMMEDIATE, scope, + FakeAirplaneModeRepository(), wifiRepository, mock(), ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 7cc59b67414b..38c7432eb288 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -34,13 +34,16 @@ import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener import android.telephony.TelephonyManager +import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.telephony.PhoneConstants import com.android.settingslib.R import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository @@ -51,25 +54,25 @@ import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManag import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl -import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository -import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.UUID -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield -import org.junit.After +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Before @@ -83,6 +86,9 @@ import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +// This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper +// to run the callback and this makes the looper place nicely with TestScope etc. +@TestableLooper.RunWithLooper class MobileConnectionsRepositoryTest : SysuiTestCase() { private lateinit var underTest: MobileConnectionsRepositoryImpl @@ -90,7 +96,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory private lateinit var connectivityRepository: ConnectivityRepository - private lateinit var wifiRepository: FakeWifiRepository + private lateinit var airplaneModeRepository: FakeAirplaneModeRepository + private lateinit var wifiRepository: WifiRepository private lateinit var carrierConfigRepository: CarrierConfigRepository @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var subscriptionManager: SubscriptionManager @@ -102,7 +109,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private val mobileMappings = FakeMobileMappingsProxy() private val subscriptionManagerProxy = FakeSubscriptionManagerProxy() - private val scope = CoroutineScope(IMMEDIATE) + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) @Before fun setUp() { @@ -138,11 +146,23 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { context, mock(), mock(), - scope, + testScope.backgroundScope, mock(), ) - wifiRepository = FakeWifiRepository() + airplaneModeRepository = FakeAirplaneModeRepository() + + wifiRepository = + WifiRepositoryImpl( + fakeBroadcastDispatcher, + connectivityManager, + connectivityRepository, + mock(), + mock(), + FakeExecutor(FakeSystemClock()), + testScope.backgroundScope, + mock(), + ) carrierConfigRepository = CarrierConfigRepository( @@ -150,28 +170,28 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mock(), mock(), logger, - scope, + testScope.backgroundScope, ) connectionFactory = MobileConnectionRepositoryImpl.Factory( fakeBroadcastDispatcher, telephonyManager = telephonyManager, - bgDispatcher = IMMEDIATE, + bgDispatcher = dispatcher, logger = logger, mobileMappingsProxy = mobileMappings, - scope = scope, + scope = testScope.backgroundScope, carrierConfigRepository = carrierConfigRepository, ) carrierMergedFactory = CarrierMergedConnectionRepository.Factory( telephonyManager, - scope, + testScope.backgroundScope, wifiRepository, ) fullConnectionFactory = FullMobileConnectionRepository.Factory( - scope = scope, + scope = testScope.backgroundScope, logFactory = logBufferFactory, mobileRepoFactory = connectionFactory, carrierMergedRepoFactory = carrierMergedFactory, @@ -188,46 +208,38 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mobileMappings, fakeBroadcastDispatcher, context, - IMMEDIATE, - scope, + dispatcher, + testScope.backgroundScope, + airplaneModeRepository, wifiRepository, fullConnectionFactory, ) - } - @After - fun tearDown() { - scope.cancel() + testScope.runCurrent() } @Test fun testSubscriptions_initiallyEmpty() = - runBlocking(IMMEDIATE) { + testScope.runTest { assertThat(underTest.subscriptions.value).isEqualTo(listOf<SubscriptionModel>()) } @Test fun testSubscriptions_listUpdates() = - runBlocking(IMMEDIATE) { - var latest: List<SubscriptionModel>? = null - - val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) getSubscriptionCallback().onSubscriptionsChanged() assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2)) - - job.cancel() } @Test fun testSubscriptions_removingSub_updatesList() = - runBlocking(IMMEDIATE) { - var latest: List<SubscriptionModel>? = null - - val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) // WHEN 2 networks show up whenever(subscriptionManager.completeActiveSubscriptionInfoList) @@ -241,71 +253,55 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // THEN the subscriptions list represents the newest change assertThat(latest).isEqualTo(listOf(MODEL_2)) - - job.cancel() } @Test fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() = - runBlocking(IMMEDIATE) { - var latest: List<SubscriptionModel>? = null + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) - val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) - - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() assertThat(latest).isEqualTo(listOf(MODEL_CM)) - - job.cancel() } @Test fun testSubscriptions_carrierMergedAndOther_listHasBothWithCarrierMergedLast() = - runBlocking(IMMEDIATE) { - var latest: List<SubscriptionModel>? = null + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) - val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) - - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2, SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2, MODEL_CM)) - - job.cancel() } @Test fun testActiveDataSubscriptionId_initialValueIsNull() = - runBlocking(IMMEDIATE) { + testScope.runTest { assertThat(underTest.activeMobileDataSubscriptionId.value).isEqualTo(null) } @Test fun testActiveDataSubscriptionId_updates() = - runBlocking(IMMEDIATE) { - var active: Int? = null - - val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this) + testScope.runTest { + val active by collectLastValue(underTest.activeMobileDataSubscriptionId) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) assertThat(active).isEqualTo(SUB_2_ID) - - job.cancel() } @Test fun activeSubId_nullIfInvalidSubIdIsReceived() = - runBlocking(IMMEDIATE) { - var latest: Int? = null - - val job = underTest.activeMobileDataSubscriptionId.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.activeMobileDataSubscriptionId) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) @@ -316,8 +312,6 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID) assertThat(latest).isNull() - - job.cancel() } @Test @@ -327,23 +321,19 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun activeRepo_updatesWithActiveDataId() = - runBlocking(IMMEDIATE) { - var latest: MobileConnectionRepository? = null - val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.activeMobileDataRepository) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) assertThat(latest?.subId).isEqualTo(SUB_2_ID) - - job.cancel() } @Test fun activeRepo_nullIfActiveDataSubIdBecomesInvalid() = - runBlocking(IMMEDIATE) { - var latest: MobileConnectionRepository? = null - val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.activeMobileDataRepository) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) @@ -354,64 +344,49 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID) assertThat(latest).isNull() - - job.cancel() } @Test /** Regression test for b/268146648. */ fun activeSubIdIsSetBeforeSubscriptionsAreUpdated_doesNotThrow() = - runBlocking(IMMEDIATE) { - var activeRepo: MobileConnectionRepository? = null - var subscriptions: List<SubscriptionModel>? = null - - val activeRepoJob = - underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this) - val subscriptionsJob = - underTest.subscriptions.onEach { subscriptions = it }.launchIn(this) + testScope.runTest { + val activeRepo by collectLastValue(underTest.activeMobileDataRepository) + val subscriptions by collectLastValue(underTest.subscriptions) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) assertThat(subscriptions).isEmpty() assertThat(activeRepo).isNotNull() - - activeRepoJob.cancel() - subscriptionsJob.cancel() } @Test fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() = - runBlocking(IMMEDIATE) { - var latest: MobileConnectionRepository? = null - var subscriptions: List<SubscriptionModel>? = null - val activeSubIdJob = - underTest.activeMobileDataSubscriptionId - .filterNotNull() - .onEach { latest = underTest.getRepoForSubId(it) } - .launchIn(this) - val subscriptionsJob = - underTest.subscriptions.onEach { subscriptions = it }.launchIn(this) + testScope.runTest { + var latestActiveRepo: MobileConnectionRepository? = null + collectLastValue( + underTest.activeMobileDataSubscriptionId.filterNotNull().onEach { + latestActiveRepo = underTest.getRepoForSubId(it) + } + ) + + val latestSubscriptions by collectLastValue(underTest.subscriptions) // Active data subscription id is sent, but no subscription change has been posted yet getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_2_ID) // Subscriptions list is empty - assertThat(subscriptions).isEmpty() + assertThat(latestSubscriptions).isEmpty() // getRepoForSubId does not throw - assertThat(latest).isNotNull() - - activeSubIdJob.cancel() - subscriptionsJob.cancel() + assertThat(latestActiveRepo).isNotNull() } @Test fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() = - runBlocking(IMMEDIATE) { - var activeRepo: MobileConnectionRepository? = null - val job = underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this) - val subscriptionsJob = underTest.subscriptions.launchIn(this) + testScope.runTest { + val activeRepo by collectLastValue(underTest.activeMobileDataRepository) + collectLastValue(underTest.subscriptions) // GIVEN active repo is updated before the subscription list updates getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() @@ -429,15 +404,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // THEN the newly request repo has been cached and reused assertThat(activeRepo).isSameInstanceAs(newRepo) - - job.cancel() - subscriptionsJob.cancel() } @Test fun testConnectionRepository_validSubId_isCached() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1)) @@ -447,16 +419,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { val repo2 = underTest.getRepoForSubId(SUB_1_ID) assertThat(repo1).isSameInstanceAs(repo2) - - job.cancel() } @Test fun testConnectionRepository_carrierMergedSubId_isCached() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() @@ -465,16 +436,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { val repo2 = underTest.getRepoForSubId(SUB_CM_ID) assertThat(repo1).isSameInstanceAs(repo2) - - job.cancel() } @Test fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() @@ -483,16 +453,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { val mobileRepo = underTest.getRepoForSubId(SUB_1_ID) assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue() assertThat(mobileRepo.getIsCarrierMerged()).isFalse() - - job.cancel() } @Test fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() @@ -503,26 +472,28 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(mobileRepo.getIsCarrierMerged()).isFalse() // WHEN the wifi network updates to be not carrier merged - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 4, level = 1)) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE) + runCurrent() // THEN the repos update val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID) mobileRepo = underTest.getRepoForSubId(SUB_1_ID) assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse() assertThat(mobileRepo.getIsCarrierMerged()).isFalse() - - job.cancel() } @Test fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) - wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() + runCurrent() val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID) var mobileRepo = underTest.getRepoForSubId(SUB_1_ID) @@ -530,21 +501,21 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(mobileRepo.getIsCarrierMerged()).isFalse() // WHEN the wifi network updates to be carrier merged - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + runCurrent() // THEN the repos update val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID) mobileRepo = underTest.getRepoForSubId(SUB_1_ID) assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue() assertThat(mobileRepo.getIsCarrierMerged()).isFalse() - - job.cancel() } @Test fun testConnectionCache_clearsInvalidSubscriptions() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) @@ -563,16 +534,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { getSubscriptionCallback().onSubscriptionsChanged() assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1) - - job.cancel() } @Test fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2, SUB_CM)) getSubscriptionCallback().onSubscriptionsChanged() @@ -591,15 +561,13 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { getSubscriptionCallback().onSubscriptionsChanged() assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1) - - job.cancel() } /** Regression test for b/261706421 */ @Test fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) @@ -617,26 +585,20 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { getSubscriptionCallback().onSubscriptionsChanged() assertThat(underTest.getSubIdRepoCache()).isEmpty() - - job.cancel() } @Test fun testConnectionRepository_invalidSubId_throws() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) - + testScope.runTest { assertThrows(IllegalArgumentException::class.java) { underTest.getRepoForSubId(SUB_1_ID) } - - job.cancel() } @Test fun connectionRepository_logBufferContainsSubIdInItsName() = - runBlocking(IMMEDIATE) { - val job = underTest.subscriptions.launchIn(this) + testScope.runTest { + collectLastValue(underTest.subscriptions) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) @@ -655,15 +617,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { eq(tableBufferLogName(SUB_2_ID)), anyInt(), ) - - job.cancel() } @Test fun testDefaultDataSubId_updatesOnBroadcast() = - runBlocking(IMMEDIATE) { - var latest: Int? = null - val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.defaultDataSubId) assertThat(latest).isEqualTo(INVALID_SUBSCRIPTION_ID) @@ -686,28 +645,24 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } assertThat(latest).isEqualTo(SUB_1_ID) - - job.cancel() } @Test fun defaultDataSubId_fetchesInitialValueOnStart() = - runBlocking(IMMEDIATE) { + testScope.runTest { subscriptionManagerProxy.defaultDataSubId = 2 - var latest: Int? = null - val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.defaultDataSubId) assertThat(latest).isEqualTo(2) - - job.cancel() } @Test fun defaultDataSubId_fetchesCurrentOnRestart() = - runBlocking(IMMEDIATE) { + testScope.runTest { subscriptionManagerProxy.defaultDataSubId = 2 var latest: Int? = null var job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this) + runCurrent() assertThat(latest).isEqualTo(2) @@ -720,6 +675,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { subscriptionManagerProxy.defaultDataSubId = 1 job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this) + runCurrent() assertThat(latest).isEqualTo(1) @@ -733,43 +689,37 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun mobileIsDefault_capsHaveCellular_isDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) } - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun mobileIsDefault_capsDoNotHaveCellular_isNotDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) } - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isFalse() - - job.cancel() } @Test fun mobileIsDefault_carrierMergedViaMobile_isDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } val caps = @@ -778,151 +728,144 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { whenever(it.transportInfo).thenReturn(carrierMergedInfo) } - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun mobileIsDefault_wifiDefault_mobileNotDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) } - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isFalse() - - job.cancel() } @Test fun mobileIsDefault_ethernetDefault_mobileNotDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true) } - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isFalse() - - job.cancel() } /** Regression test for b/272586234. */ @Test fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) whenever(it.transportInfo).thenReturn(carrierMergedInfo) } - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.hasCarrierMergedConnection) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(carrierMergedInfo) } - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.hasCarrierMergedConnection) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } /** Regression test for b/272586234. */ @Test fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) } - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.hasCarrierMergedConnection) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) } - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.hasCarrierMergedConnection) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() = - runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.hasCarrierMergedConnection) val underlyingNetwork = mock<Network>() val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val underlyingWifiCapabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) @@ -941,23 +884,23 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) // THEN there's a carrier merged connection assertThat(latest).isTrue() - - job.cancel() } @Test fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() = - runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.hasCarrierMergedConnection) val underlyingCarrierMergedNetwork = mock<Network>() val carrierMergedInfo = - mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } val underlyingCapabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) @@ -977,22 +920,19 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) // THEN there's a carrier merged connection assertThat(latest).isTrue() - - job.cancel() } /** Regression test for b/272586234. */ @Test - fun hasCarrierMergedConnection_defaultNotCarrierMerged_butWifiRepoHasCarrierMerged_isTrue() = - runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + fun hasCarrierMergedConnection_defaultIsWifiNotCarrierMerged_wifiRepoIsCarrierMerged_isTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.hasCarrierMergedConnection) - // WHEN the default callback isn't carrier merged + // WHEN the default callback is TRANSPORT_WIFI but not carrier merged val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) } val caps = @@ -1001,16 +941,57 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { whenever(it.transportInfo).thenReturn(carrierMergedInfo) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - yield() // BUT the wifi repo has gotten updates that it *is* carrier merged - wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) - yield() + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) // THEN hasCarrierMergedConnection is true assertThat(latest).isTrue() + } - job.cancel() + /** Regression test for b/278618530. */ + @Test + fun hasCarrierMergedConnection_defaultIsCellular_wifiRepoIsCarrierMerged_isFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.hasCarrierMergedConnection) + + // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged + val caps = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + } + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + // BUT the wifi repo has gotten updates that it *is* carrier merged + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + + // THEN hasCarrierMergedConnection is **false** (The default network being CELLULAR + // takes precedence over the wifi network being carrier merged.) + assertThat(latest).isFalse() + } + + /** Regression test for b/278618530. */ + @Test + fun hasCarrierMergedConnection_defaultCellular_wifiIsCarrierMerged_airplaneMode_isTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.hasCarrierMergedConnection) + + // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged + val caps = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + } + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + // BUT the wifi repo has gotten updates that it *is* carrier merged + getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM) + // AND we're in airplane mode + airplaneModeRepository.setIsAirplaneMode(true) + + // THEN hasCarrierMergedConnection is true. + assertThat(latest).isTrue() } @Test @@ -1020,43 +1001,37 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun defaultConnectionIsValidated_capsHaveValidated_isValidated() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true) } - var latest: Boolean? = null - val job = underTest.defaultConnectionIsValidated.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.defaultConnectionIsValidated) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isTrue() - - job.cancel() } @Test fun defaultConnectionIsValidated_capsHaveNotValidated_isNotValidated() = - runBlocking(IMMEDIATE) { + testScope.runTest { val caps = mock<NetworkCapabilities>().also { whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(false) } - var latest: Boolean? = null - val job = underTest.defaultConnectionIsValidated.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.defaultConnectionIsValidated) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) assertThat(latest).isFalse() - - job.cancel() } @Test fun config_initiallyFromContext() = - runBlocking(IMMEDIATE) { + testScope.runTest { overrideResource(R.bool.config_showMin3G, true) val configFromContext = MobileMappings.Config.readConfig(context) assertThat(configFromContext.showAtLeast3G).isTrue() @@ -1074,26 +1049,24 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mobileMappings, fakeBroadcastDispatcher, context, - IMMEDIATE, - scope, + dispatcher, + testScope.backgroundScope, + airplaneModeRepository, wifiRepository, fullConnectionFactory, ) - var latest: MobileMappings.Config? = null - val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.defaultDataSubRatConfig) assertTrue(latest!!.areEqual(configFromContext)) assertTrue(latest!!.showAtLeast3G) - - job.cancel() } @Test fun config_subIdChangeEvent_updated() = - runBlocking(IMMEDIATE) { - var latest: MobileMappings.Config? = null - val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.defaultDataSubRatConfig) + assertThat(latest!!.showAtLeast3G).isFalse() overrideResource(R.bool.config_showMin3G, true) @@ -1112,15 +1085,13 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // THEN the config is updated assertTrue(latest!!.areEqual(configFromContext)) assertTrue(latest!!.showAtLeast3G) - - job.cancel() } @Test fun config_carrierConfigChangeEvent_updated() = - runBlocking(IMMEDIATE) { - var latest: MobileMappings.Config? = null - val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.defaultDataSubRatConfig) + assertThat(latest!!.showAtLeast3G).isFalse() overrideResource(R.bool.config_showMin3G, true) @@ -1138,15 +1109,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // THEN the config is updated assertThat(latest!!.areEqual(configFromContext)).isTrue() assertThat(latest!!.showAtLeast3G).isTrue() - - job.cancel() } @Test fun activeDataChange_inSameGroup_emitsUnit() = - runBlocking(IMMEDIATE) { - var latest: Unit? = null - val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.activeSubChangedInGroupEvent) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED) @@ -1154,15 +1122,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { .onActiveDataSubscriptionIdChanged(SUB_4_ID_GROUPED) assertThat(latest).isEqualTo(Unit) - - job.cancel() } @Test fun activeDataChange_notInSameGroup_doesNotEmit() = - runBlocking(IMMEDIATE) { - var latest: Unit? = null - val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this) + testScope.runTest { + val latest by collectLastValue(underTest.activeSubChangedInGroupEvent) getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED) @@ -1170,38 +1135,46 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { .onActiveDataSubscriptionIdChanged(SUB_1_ID) assertThat(latest).isEqualTo(null) - - job.cancel() } - private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback { + private fun TestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback { + runCurrent() val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture()) return callbackCaptor.value!! } - private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener { + // Note: This is used to update the [WifiRepository]. + private fun TestScope.getNormalNetworkCallback(): ConnectivityManager.NetworkCallback { + runCurrent() + val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() + verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + + private fun TestScope.getSubscriptionCallback(): + SubscriptionManager.OnSubscriptionsChangedListener { + runCurrent() val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>() verify(subscriptionManager) .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture()) return callbackCaptor.value!! } - private fun getTelephonyCallbacks(): List<TelephonyCallback> { + private fun TestScope.getTelephonyCallbacks(): List<TelephonyCallback> { + runCurrent() val callbackCaptor = argumentCaptor<TelephonyCallback>() verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture()) return callbackCaptor.allValues } - private inline fun <reified T> getTelephonyCallbackForType(): T { - val cbs = getTelephonyCallbacks().filterIsInstance<T>() + private inline fun <reified T> TestScope.getTelephonyCallbackForType(): T { + val cbs = this.getTelephonyCallbacks().filterIsInstance<T>() assertThat(cbs.size).isEqualTo(1) return cbs[0] } companion object { - private val IMMEDIATE = Dispatchers.Main.immediate - // Subscription 1 private const val SUB_1_ID = 1 private val GROUP_1 = ParcelUuid(UUID.randomUUID()) @@ -1259,11 +1232,30 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private val SUB_CM = mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) } private val MODEL_CM = SubscriptionModel(subscriptionId = SUB_CM_ID) - private val WIFI_NETWORK_CM = - WifiNetworkModel.CarrierMerged( - networkId = 3, - subscriptionId = SUB_CM_ID, - level = 1, - ) + + private val WIFI_INFO_CM = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.subscriptionId).thenReturn(SUB_CM_ID) + } + private val WIFI_NETWORK_CAPS_CM = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(WIFI_INFO_CM) + whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true) + } + + private val WIFI_INFO_ACTIVE = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(false) + } + private val WIFI_NETWORK_CAPS_ACTIVE = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(WIFI_INFO_ACTIVE) + whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true) + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 6e1ab58db56d..1fb76b048d47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -272,6 +272,52 @@ class MobileIconsInteractorTest : SysuiTestCase() { } @Test + fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() = + testScope.runTest { + val (sub1, sub3) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + connectionsRepository.setSubscriptions(listOf(sub1, sub3)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + connectivityRepository.vcnSubId.value = SUB_3_ID + whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) + .thenReturn(false) + + var latest: List<SubscriptionModel>? = null + val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(listOf(sub3)) + + job.cancel() + } + + @Test + fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() = + testScope.runTest { + val (sub1, sub3) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + connectionsRepository.setSubscriptions(listOf(sub1, sub3)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + connectivityRepository.vcnSubId.value = SUB_1_ID + whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) + .thenReturn(false) + + var latest: List<SubscriptionModel>? = null + val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(listOf(sub1)) + + job.cancel() + } + + @Test fun activeDataConnection_turnedOn() = testScope.runTest { CONNECTION_1.setDataEnabled(true) @@ -361,6 +407,21 @@ class MobileIconsInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun failedConnection_carrierMergedDefault_notValidated_failed() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + + connectionsRepository.hasCarrierMergedConnection.value = true + connectionsRepository.defaultConnectionIsValidated.value = false + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + /** Regression test for b/275076959. */ @Test fun failedConnection_dataSwitchInSameGroup_notFailed() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt index 661002d275b0..fa4e91b68a5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt @@ -24,6 +24,7 @@ import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -37,6 +38,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi import com.android.systemui.tuner.TunerService import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -655,6 +657,139 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { } @Test + fun vcnSubId_initiallyNull() { + assertThat(underTest.vcnSubId.value).isNull() + } + + @Test + fun vcnSubId_tracksVcnTransportInfo() = + testScope.runTest { + val vcnInfo = VcnTransportInfo(SUB_1_ID) + + var latest: Int? = null + val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) + + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(vcnInfo) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isEqualTo(SUB_1_ID) + job.cancel() + } + + @Test + fun vcnSubId_filersOutInvalid() = + testScope.runTest { + val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID) + + var latest: Int? = null + val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) + + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(vcnInfo) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isNull() + job.cancel() + } + + @Test + fun vcnSubId_nullIfNoTransportInfo() = + testScope.runTest { + var latest: Int? = null + val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) + + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isNull() + job.cancel() + } + + @Test + fun vcnSubId_nullIfVcnInfoIsNotCellular() = + testScope.runTest { + // If the underlying network of the VCN is a WiFi network, then there is no subId that + // could disagree with telephony's active data subscription id. + + var latest: Int? = null + val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) + + val wifiInfo = mock<WifiInfo>() + val vcnInfo = VcnTransportInfo(wifiInfo) + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(vcnInfo) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isNull() + job.cancel() + } + + @Test + fun vcnSubId_changingVcnInfoIsTracked() = + testScope.runTest { + var latest: Int? = null + val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) + + val wifiInfo = mock<WifiInfo>() + val wifiVcnInfo = VcnTransportInfo(wifiInfo) + val sub1VcnInfo = VcnTransportInfo(SUB_1_ID) + val sub2VcnInfo = VcnTransportInfo(SUB_2_ID) + + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(wifiVcnInfo) + } + + // WIFI VCN info + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isNull() + + // Cellular VCN info with subId 1 + whenever(capabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(true) + whenever(capabilities.transportInfo).thenReturn(sub1VcnInfo) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isEqualTo(SUB_1_ID) + + // Cellular VCN info with subId 2 + whenever(capabilities.transportInfo).thenReturn(sub2VcnInfo) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isEqualTo(SUB_2_ID) + + // No VCN anymore + whenever(capabilities.transportInfo).thenReturn(null) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest).isNull() + + job.cancel() + } + + @Test fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() { val wifiInfo = mock<WifiInfo>() val capabilities = @@ -964,6 +1099,9 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { private const val SLOT_WIFI = "wifi" private const val SLOT_MOBILE = "mobile" + private const val SUB_1_ID = 1 + private const val SUB_2_ID = 2 + const val NETWORK_ID = 45 val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt index 9e825b704851..8f28cc003d67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt @@ -30,6 +30,8 @@ class FakeConnectivityRepository : ConnectivityRepository { override val defaultConnections: StateFlow<DefaultConnectionModel> = MutableStateFlow(DefaultConnectionModel()) + override val vcnSubId: MutableStateFlow<Int?> = MutableStateFlow(null) + fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) { _forceHiddenIcons.value = hiddenIcons } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index 2b1370544bfd..7402b4d64b16 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -14,6 +14,8 @@ package com.android.systemui.statusbar.policy; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -44,7 +46,11 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository; +import com.android.systemui.statusbar.policy.bluetooth.FakeBluetoothRepository; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -69,6 +75,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { private DumpManager mMockDumpManager; private BluetoothControllerImpl mBluetoothControllerImpl; private BluetoothAdapter mMockAdapter; + private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags(); private List<CachedBluetoothDevice> mDevices; @@ -89,17 +96,26 @@ public class BluetoothControllerImplTest extends SysuiTestCase { .thenReturn(mock(LocalBluetoothProfileManager.class)); mMockDumpManager = mock(DumpManager.class); - mBluetoothControllerImpl = new BluetoothControllerImpl(mContext, + BluetoothRepository bluetoothRepository = + new FakeBluetoothRepository(mMockBluetoothManager); + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true); + + mBluetoothControllerImpl = new BluetoothControllerImpl( + mContext, + mFakeFeatureFlags, mUserTracker, mMockDumpManager, mock(BluetoothLogger.class), + bluetoothRepository, mTestableLooper.getLooper(), mMockBluetoothManager, mMockAdapter); } @Test - public void testNoConnectionWithDevices() { + public void testNoConnectionWithDevices_repoFlagOff() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false); + CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); when(device.isConnected()).thenReturn(true); when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED); @@ -113,7 +129,39 @@ public class BluetoothControllerImplTest extends SysuiTestCase { } @Test - public void testOnServiceConnected_updatesConnectionState() { + public void testNoConnectionWithDevices_repoFlagOn() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true); + + CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); + when(device.isConnected()).thenReturn(true); + when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED); + + mDevices.add(device); + when(mMockLocalAdapter.getConnectionState()) + .thenReturn(BluetoothAdapter.STATE_DISCONNECTED); + + mBluetoothControllerImpl.onConnectionStateChanged(null, + BluetoothAdapter.STATE_DISCONNECTED); + + assertTrue(mBluetoothControllerImpl.isBluetoothConnected()); + } + + @Test + public void testOnServiceConnected_updatesConnectionState_repoFlagOff() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false); + + when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING); + + mBluetoothControllerImpl.onServiceConnected(); + + assertTrue(mBluetoothControllerImpl.isBluetoothConnecting()); + assertFalse(mBluetoothControllerImpl.isBluetoothConnected()); + } + + @Test + public void testOnServiceConnected_updatesConnectionState_repoFlagOn() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true); + when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING); mBluetoothControllerImpl.onServiceConnected(); @@ -123,6 +171,46 @@ public class BluetoothControllerImplTest extends SysuiTestCase { } @Test + public void getConnectedDevices_onlyReturnsConnected_repoFlagOff() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false); + + CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class); + when(device1Disconnected.isConnected()).thenReturn(false); + mDevices.add(device1Disconnected); + + CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class); + when(device2Connected.isConnected()).thenReturn(true); + mDevices.add(device2Connected); + + mBluetoothControllerImpl.onDeviceAdded(device1Disconnected); + mBluetoothControllerImpl.onDeviceAdded(device2Connected); + + assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1); + assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0)) + .isEqualTo(device2Connected); + } + + @Test + public void getConnectedDevices_onlyReturnsConnected_repoFlagOn() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true); + + CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class); + when(device1Disconnected.isConnected()).thenReturn(false); + mDevices.add(device1Disconnected); + + CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class); + when(device2Connected.isConnected()).thenReturn(true); + mDevices.add(device2Connected); + + mBluetoothControllerImpl.onDeviceAdded(device1Disconnected); + mBluetoothControllerImpl.onDeviceAdded(device2Connected); + + assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1); + assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0)) + .isEqualTo(device2Connected); + } + + @Test public void testOnBluetoothStateChange_updatesBluetoothState() { mBluetoothControllerImpl.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF); @@ -147,8 +235,9 @@ public class BluetoothControllerImplTest extends SysuiTestCase { } @Test - public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection() - throws Exception { + public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOff() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false); + BluetoothController.Callback callback = mock(BluetoothController.Callback.class); mBluetoothControllerImpl.addCallback(callback); @@ -168,6 +257,29 @@ public class BluetoothControllerImplTest extends SysuiTestCase { } @Test + public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOn() { + mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true); + + BluetoothController.Callback callback = mock(BluetoothController.Callback.class); + mBluetoothControllerImpl.addCallback(callback); + + assertFalse(mBluetoothControllerImpl.isBluetoothConnected()); + CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); + mDevices.add(device); + when(device.isConnected()).thenReturn(true); + when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED); + reset(callback); + mBluetoothControllerImpl.onAclConnectionStateChanged(device, + BluetoothProfile.STATE_CONNECTED); + + mTestableLooper.processAllMessages(); + + assertTrue(mBluetoothControllerImpl.isBluetoothConnected()); + verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean()); + } + + + @Test public void testOnActiveDeviceChanged_updatesAudioActive() { assertFalse(mBluetoothControllerImpl.isBluetoothAudioActive()); assertFalse(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt new file mode 100644 index 000000000000..6f40f15ca00a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.policy.bluetooth + +import android.bluetooth.BluetoothProfile +import androidx.test.filters.SmallTest +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LocalBluetoothAdapter +import com.android.settingslib.bluetooth.LocalBluetoothManager +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class BluetoothRepositoryImplTest : SysuiTestCase() { + + private lateinit var underTest: BluetoothRepositoryImpl + + private lateinit var scheduler: TestCoroutineScheduler + private lateinit var dispatcher: TestDispatcher + private lateinit var testScope: TestScope + + @Mock private lateinit var localBluetoothManager: LocalBluetoothManager + @Mock private lateinit var bluetoothAdapter: LocalBluetoothAdapter + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter) + + scheduler = TestCoroutineScheduler() + dispatcher = StandardTestDispatcher(scheduler) + testScope = TestScope(dispatcher) + + underTest = + BluetoothRepositoryImpl(testScope.backgroundScope, dispatcher, localBluetoothManager) + } + + @Test + fun fetchConnectionStatusInBackground_currentDevicesEmpty_maxStateIsManagerState() { + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + + val status = fetchConnectionStatus(currentDevices = emptyList()) + + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING) + } + + @Test + fun fetchConnectionStatusInBackground_currentDevicesEmpty_nullManager_maxStateIsDisconnected() { + // This CONNECTING state should be unused because localBluetoothManager is null + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + underTest = + BluetoothRepositoryImpl( + testScope.backgroundScope, + dispatcher, + localBluetoothManager = null, + ) + + val status = fetchConnectionStatus(currentDevices = emptyList()) + + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED) + } + + @Test + fun fetchConnectionStatusInBackground_managerStateLargerThanDeviceStates_maxStateIsManager() { + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + val device1 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) + } + val device2 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) + } + + val status = fetchConnectionStatus(currentDevices = listOf(device1, device2)) + + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING) + } + + @Test + fun fetchConnectionStatusInBackground_oneCurrentDevice_maxStateIsDeviceState() { + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) + val device = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + } + + val status = fetchConnectionStatus(currentDevices = listOf(device)) + + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING) + } + + @Test + fun fetchConnectionStatusInBackground_multipleDevices_maxStateIsHighestState() { + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) + + val device1 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + whenever(it.isConnected).thenReturn(false) + } + val device2 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED) + whenever(it.isConnected).thenReturn(true) + } + + val status = fetchConnectionStatus(currentDevices = listOf(device1, device2)) + + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTED) + } + + @Test + fun fetchConnectionStatusInBackground_devicesNotConnected_maxStateIsDisconnected() { + whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) + + // WHEN the devices say their state is CONNECTED but [isConnected] is false + val device1 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED) + whenever(it.isConnected).thenReturn(false) + } + val device2 = + mock<CachedBluetoothDevice>().also { + whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED) + whenever(it.isConnected).thenReturn(false) + } + + val status = fetchConnectionStatus(currentDevices = listOf(device1, device2)) + + // THEN the max state is DISCONNECTED + assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED) + } + + @Test + fun fetchConnectionStatusInBackground_currentDevicesEmpty_connectedDevicesEmpty() { + val status = fetchConnectionStatus(currentDevices = emptyList()) + + assertThat(status.connectedDevices).isEmpty() + } + + @Test + fun fetchConnectionStatusInBackground_oneCurrentDeviceDisconnected_connectedDevicesEmpty() { + val device = + mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) } + + val status = fetchConnectionStatus(currentDevices = listOf(device)) + + assertThat(status.connectedDevices).isEmpty() + } + + @Test + fun fetchConnectionStatusInBackground_oneCurrentDeviceConnected_connectedDevicesHasDevice() { + val device = + mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) } + + val status = fetchConnectionStatus(currentDevices = listOf(device)) + + assertThat(status.connectedDevices).isEqualTo(listOf(device)) + } + + @Test + fun fetchConnectionStatusInBackground_multipleDevices_connectedDevicesHasOnlyConnected() { + val device1Connected = + mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) } + val device2Disconnected = + mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) } + val device3Connected = + mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) } + + val status = + fetchConnectionStatus( + currentDevices = listOf(device1Connected, device2Disconnected, device3Connected) + ) + + assertThat(status.connectedDevices).isEqualTo(listOf(device1Connected, device3Connected)) + } + + private fun fetchConnectionStatus( + currentDevices: Collection<CachedBluetoothDevice> + ): ConnectionStatusModel { + var receivedStatus: ConnectionStatusModel? = null + underTest.fetchConnectionStatusInBackground(currentDevices) { status -> + receivedStatus = status + } + scheduler.runCurrent() + return receivedStatus!! + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt new file mode 100644 index 000000000000..d8c0f777d4cc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.policy.bluetooth + +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LocalBluetoothManager +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestScope + +/** + * Fake [BluetoothRepository] that delegates to the real [BluetoothRepositoryImpl]. + * + * We only need this because [BluetoothRepository] is called from Java, which can't use [TestScope], + * [StandardTestDispatcher], etc. to create a test version of the repo. This class uses those test + * items under-the-hood so Java classes can indirectly access them. + */ +@OptIn(ExperimentalCoroutinesApi::class) +class FakeBluetoothRepository(localBluetoothManager: LocalBluetoothManager) : BluetoothRepository { + + private val scheduler = TestCoroutineScheduler() + private val dispatcher = StandardTestDispatcher(scheduler) + private val testScope = TestScope(dispatcher) + + private val impl = + BluetoothRepositoryImpl(testScope.backgroundScope, dispatcher, localBluetoothManager) + + override fun fetchConnectionStatusInBackground( + currentDevices: Collection<CachedBluetoothDevice>, + callback: ConnectionStatusFetchedCallback + ) { + impl.fetchConnectionStatusInBackground(currentDevices, callback) + scheduler.runCurrent() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 17b5e0546eca..09ac0e312e21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.theme; +import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; + import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; @@ -29,6 +31,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -926,4 +929,38 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(), any()); } + + @Test + public void createDynamicOverlay_addsAllDynamicColors() { + // Trigger new wallpaper colors to generate an overlay + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, + USER_SYSTEM); + ArgumentCaptor<FabricatedOverlay[]> themeOverlays = + ArgumentCaptor.forClass(FabricatedOverlay[].class); + + verify(mThemeOverlayApplier) + .applyCurrentUserOverlays(any(), themeOverlays.capture(), anyInt(), any()); + + FabricatedOverlay[] overlays = themeOverlays.getValue(); + FabricatedOverlay accents = overlays[0]; + FabricatedOverlay neutrals = overlays[1]; + FabricatedOverlay dynamic = overlays[2]; + + final int colorsPerPalette = 12; + + // Color resources were added for all 3 accent palettes + verify(accents, times(colorsPerPalette * 3)) + .setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null)); + // Color resources were added for all 2 neutral palettes + verify(neutrals, times(colorsPerPalette * 2)) + .setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null)); + // All dynamic colors were added twice: light and dark them + // All fixed colors were added once + verify(dynamic, times( + DynamicColors.ALL_DYNAMIC_COLORS_MAPPED.size() * 2 + + DynamicColors.FIXED_COLORS_MAPPED.size()) + ).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt index 8fc0a1acf76b..62985060e7d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt @@ -200,6 +200,31 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { } @Test + fun onFolded_onScreenTurningOnWithoutDozingThenWithDozing_doesNotLogLatency() = + runBlocking(IMMEDIATE) { + val job = underTest.listenForDozing(this) + keyguardRepository.setDozing(false) + setAodEnabled(enabled = true) + + yield() + + fold() + simulateScreenTurningOn() + reset(latencyTracker) + + // Now enable dozing and trigger a second run through the aod animation code. It should + // not rerun the animation + keyguardRepository.setDozing(true) + yield() + simulateScreenTurningOn() + + verify(latencyTracker, never()).onActionStart(any()) + verify(latencyTracker, never()).onActionEnd(any()) + + job.cancel() + } + + @Test fun onFolded_animationCancelled_doesNotLogLatency() = runBlocking(IMMEDIATE) { val job = underTest.listenForDozing(this) diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index e06b43ac5aea..45a37cffa588 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -17,22 +17,28 @@ package com.android.systemui.volume; import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; +import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.KeyguardManager; +import android.content.res.Configuration; import android.media.AudioManager; import android.os.SystemClock; import android.provider.DeviceConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.Gravity; import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; @@ -53,7 +59,9 @@ import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.FakeConfigurationController; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -82,6 +90,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { View mDrawerNormal; private DeviceConfigProxyFake mDeviceConfigProxy; private FakeExecutor mExecutor; + private TestableLooper mTestableLooper; + private ConfigurationController mConfigurationController; + private int mOriginalOrientation; @Mock VolumeDialogController mVolumeDialogController; @@ -92,8 +103,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Mock DeviceProvisionedController mDeviceProvisionedController; @Mock - ConfigurationController mConfigurationController; - @Mock MediaOutputDialogFactory mMediaOutputDialogFactory; @Mock VolumePanelFactory mVolumePanelFactory; @@ -104,6 +113,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock CsdWarningDialog mCsdWarningDialog; + @Mock + DevicePostureController mPostureController; private final CsdWarningDialog.Factory mCsdWarningDialogFactory = new CsdWarningDialog.Factory() { @@ -119,9 +130,17 @@ public class VolumeDialogImplTest extends SysuiTestCase { getContext().addMockSystemService(KeyguardManager.class, mKeyguard); + mTestableLooper = TestableLooper.get(this); mDeviceConfigProxy = new DeviceConfigProxyFake(); mExecutor = new FakeExecutor(new FakeSystemClock()); + when(mPostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + mOriginalOrientation = mContext.getResources().getConfiguration().orientation; + + mConfigurationController = new FakeConfigurationController(); + mDialog = new VolumeDialogImpl( getContext(), mVolumeDialogController, @@ -135,8 +154,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDeviceConfigProxy, mExecutor, mCsdWarningDialogFactory, - mDumpManager - ); + mPostureController, + mTestableLooper.getLooper(), + mDumpManager); mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); @@ -227,6 +247,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class); verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any()); VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue(); + callbacks.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI); verify(mAccessibilityMgr).getRecommendedTimeoutMillis( VolumeDialogImpl.DIALOG_SAFETYWARNING_TIMEOUT_MILLIS, @@ -371,11 +392,171 @@ public class VolumeDialogImplTest extends SysuiTestCase { verify(mCsdWarningDialog).show(); } + @Test + public void ifPortraitHalfOpen_drawVerticallyTop() { + DevicePostureController devicePostureController = mock(DevicePostureController.class); + when(devicePostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + devicePostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0 , null); + + verify(devicePostureController).addCallback(any()); + dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED); + mTestableLooper.processAllMessages(); // let dismiss() finish + + setOrientation(Configuration.ORIENTATION_PORTRAIT); + + // Call show() to trigger layout updates before verifying position + dialog.show(SHOW_REASON_UNKNOWN); + mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect + + int gravity = dialog.getWindowGravity(); + assertEquals(Gravity.TOP, gravity & Gravity.VERTICAL_GRAVITY_MASK); + } + + @Test + public void ifPortraitAndOpen_drawCenterVertically() { + DevicePostureController devicePostureController = mock(DevicePostureController.class); + when(devicePostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + devicePostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0, null); + + verify(devicePostureController).addCallback(any()); + dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED); + mTestableLooper.processAllMessages(); // let dismiss() finish + + setOrientation(Configuration.ORIENTATION_PORTRAIT); + + dialog.show(SHOW_REASON_UNKNOWN); + mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect + + int gravity = dialog.getWindowGravity(); + assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK); + } + + @Test + public void ifLandscapeAndHalfOpen_drawCenterVertically() { + DevicePostureController devicePostureController = mock(DevicePostureController.class); + when(devicePostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + devicePostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0, null); + + verify(devicePostureController).addCallback(any()); + dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED); + mTestableLooper.processAllMessages(); // let dismiss() finish + + setOrientation(Configuration.ORIENTATION_LANDSCAPE); + + dialog.show(SHOW_REASON_UNKNOWN); + mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect + + int gravity = dialog.getWindowGravity(); + assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK); + } + + @Test + public void dialogInit_addsPostureControllerCallback() { + // init is already called in setup + verify(mPostureController).addCallback(any()); + } + + @Test + public void dialogDestroy_removesPostureControllerCallback() { + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + mPostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0, null); + + verify(mPostureController, never()).removeCallback(any()); + + dialog.destroy(); + + verify(mPostureController).removeCallback(any()); + } + + private void setOrientation(int orientation) { + Configuration config = new Configuration(); + config.orientation = orientation; + if (mConfigurationController != null) { + mConfigurationController.onConfigurationChanged(config); + } + } + @After public void teardown() { if (mDialog != null) { mDialog.clearInternalHandlerAfterTest(); } + setOrientation(mOriginalOrientation); + mTestableLooper.processAllMessages(); + reset(mPostureController); } /* diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 1ec4e8c48707..8bbd58dc8fe1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -102,7 +102,8 @@ public abstract class SysuiTestCase { mock(Executor.class), mock(DumpManager.class), mock(BroadcastDispatcherLogger.class), - mock(UserTracker.class)); + mock(UserTracker.class), + shouldFailOnLeakedReceiver()); mRealInstrumentation = InstrumentationRegistry.getInstrumentation(); Instrumentation inst = spy(mRealInstrumentation); @@ -141,6 +142,10 @@ public abstract class SysuiTestCase { mDependency.injectTestDependency(DialogLaunchAnimator.class, fakeDialogLaunchAnimator()); } + protected boolean shouldFailOnLeakedReceiver() { + return false; + } + @After public void SysuiTeardown() { if (mRealInstrumentation != null) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt index ad086ff9c664..af940e4fa687 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt @@ -27,6 +27,7 @@ import com.android.systemui.SysuiTestableContext import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.dump.DumpManager import com.android.systemui.settings.UserTracker +import java.lang.IllegalStateException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executor @@ -37,7 +38,8 @@ class FakeBroadcastDispatcher( broadcastRunningExecutor: Executor, dumpManager: DumpManager, logger: BroadcastDispatcherLogger, - userTracker: UserTracker + userTracker: UserTracker, + private val shouldFailOnLeakedReceiver: Boolean ) : BroadcastDispatcher( context, @@ -85,6 +87,9 @@ class FakeBroadcastDispatcher( fun cleanUpReceivers(testName: String) { registeredReceivers.forEach { Log.i(testName, "Receiver not unregistered from dispatcher: $it") + if (shouldFailOnLeakedReceiver) { + throw IllegalStateException("Receiver not unregistered from dispatcher: $it") + } } registeredReceivers.clear() } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 7463061256b7..a324b2ff88e8 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -2551,6 +2551,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public void attachAccessibilityOverlayToWindow(int accessibilityWindowId, SurfaceControl sc) throws RemoteException { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.setTrustedOverlay(sc, true).apply(); + t.close(); synchronized (mLock) { RemoteAccessibilityConnection connection = mA11yWindowManager.getConnectionLocked( diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index e894f1c2879e..2d1290caf32d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -5329,9 +5329,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yOverlayLayers.remove(displayId); return; } - SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); - transaction.reparent(sc, parent); - transaction.apply(); - transaction.close(); + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.reparent(sc, parent).setTrustedOverlay(sc, true).apply(); + t.close(); } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index d290c3611f78..fb94af65513f 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1581,6 +1581,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // TODO(b/266379948): Ideally wait for PCC request to finish for a while more // (say 100ms) before proceeding further on. + processResponseLockedForPcc(response, response.getClientState(), requestFlags); + } + + + @GuardedBy("mLock") + private void processResponseLockedForPcc(@NonNull FillResponse response, + @Nullable Bundle newClientState, int flags) { if (DBG) { Slog.d(TAG, "DBG: Initial response: " + response); } @@ -1588,12 +1595,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState response = getEffectiveFillResponse(response); if (isEmptyResponse(response)) { // Treat it as a null response. - processNullResponseLocked(requestId, requestFlags); + processNullResponseLocked( + response != null ? response.getRequestId() : 0, + flags); + return; } if (DBG) { Slog.d(TAG, "DBG: Processed response: " + response); } - processResponseLocked(response, null, requestFlags); + processResponseLocked(response, newClientState, flags); } } @@ -2490,7 +2500,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sDebug) Slog.d(TAG, "Updating client state from auth dataset"); mClientState = newClientState; } - final Dataset dataset = (Dataset) result; + Dataset dataset = (Dataset) result; + FillResponse temp = new FillResponse.Builder().addDataset(dataset).build(); + temp = getEffectiveFillResponse(temp); + dataset = temp.getDatasets().get(0); final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx); if (!isAuthResultDatasetEphemeral(oldDataset, data)) { authenticatedResponse.getDatasets().set(datasetIdx, dataset); @@ -4665,10 +4678,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true); // Move over the id newResponse.setRequestId(oldResponse.getRequestId()); - // Replace the old response - mResponses.put(newResponse.getRequestId(), newResponse); // Now process the new response - processResponseLocked(newResponse, newClientState, 0); + processResponseLockedForPcc(newResponse, newClientState, 0); } @GuardedBy("mLock") diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java index 05f2eea621cf..8570515f241d 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java @@ -31,10 +31,10 @@ public class CompanionDeviceConfig { public static final String ENABLE_CONTEXT_SYNC_TELECOM = "enable_context_sync_telecom"; /** - * Returns whether the given flag is currently enabled, with a default value of {@code true}. + * Returns whether the given flag is currently enabled, with a default value of {@code false}. */ public static boolean isEnabled(String flag) { - return DeviceConfig.getBoolean(NAMESPACE_COMPANION, flag, /* defaultValue= */ true); + return DeviceConfig.getBoolean(NAMESPACE_COMPANION, flag, /* defaultValue= */ false); } /** diff --git a/services/core/java/com/android/server/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java index f184574c9a36..f8830ea705c6 100644 --- a/services/core/java/com/android/server/SoundTriggerInternal.java +++ b/services/core/java/com/android/server/SoundTriggerInternal.java @@ -47,7 +47,14 @@ public interface SoundTriggerInternal { int STATUS_OK = SoundTrigger.STATUS_OK; // Attach to a specific underlying STModule - Session attach(@NonNull IBinder client, ModuleProperties underlyingModule); + /** + * Attach to a specific underlying STModule. + * @param client - Binder token representing the app client for death notifications + * @param underlyingModule - Properties of the underlying STModule to attach to + * @param isTrusted - {@code true} if callbacks will be appropriately AppOps attributed by + * a trusted component prior to delivery to the ultimate client. + */ + Session attach(@NonNull IBinder client, ModuleProperties underlyingModule, boolean isTrusted); // Enumerate possible STModules to attach to List<ModuleProperties> listModuleProperties(Identity originatorIdentity); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a888a0b5d7d9..a4cd2780bec6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2497,13 +2497,10 @@ public class ActivityManagerService extends IActivityManager.Stub final File systemDir = SystemServiceManager.ensureSystemDir(); // TODO: Move creation of battery stats service outside of activity manager service. - mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, - BackgroundThread.get().getHandler()); - mBatteryStatsService.getActiveStatistics().readLocked(); - mBatteryStatsService.scheduleWriteToDisk(); + mBatteryStatsService = BatteryStatsService.create(systemContext, systemDir, + BackgroundThread.getHandler(), this); mOnBattery = DEBUG_POWER ? true : mBatteryStatsService.getActiveStatistics().getIsOnBattery(); - mBatteryStatsService.getActiveStatistics().setCallback(this); mOomAdjProfiler.batteryPowerChanged(mOnBattery); mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats")); @@ -3764,6 +3761,15 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void forceStopPackage(final String packageName, int userId) { + forceStopPackage(packageName, userId, /*flags=*/ 0); + } + + @Override + public void forceStopPackageEvenWhenStopping(final String packageName, int userId) { + forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED); + } + + private void forceStopPackage(final String packageName, int userId, int userRunningFlags) { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) != PackageManager.PERMISSION_GRANTED) { String msg = "Permission Denial: forceStopPackage() from pid=" @@ -3779,7 +3785,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); - synchronized(this) { + synchronized (this) { int[] users = userId == UserHandle.USER_ALL ? mUserController.getUsers() : new int[] { userId }; for (int user : users) { @@ -3807,7 +3813,7 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.w(TAG, "Failed trying to unstop package " + packageName + ": " + e); } - if (mUserController.isUserRunning(user, 0)) { + if (mUserController.isUserRunning(user, userRunningFlags)) { forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid); finishForceStopPackageLocked(packageName, pkgUid); } @@ -7514,7 +7520,31 @@ public class ActivityManagerService extends IActivityManager.Stub "registerUidObserver"); } mUidObserverController.register(observer, which, cutpoint, callingPackage, - Binder.getCallingUid()); + Binder.getCallingUid(), /*uids*/null); + } + + /** + * Registers a UidObserver with a uid filter. + * + * @param observer The UidObserver implementation to register. + * @param which A bitmask of events to observe. See ActivityManager.UID_OBSERVER_*. + * @param cutpoint The cutpoint for onUidStateChanged events. When the state crosses this + * threshold in either direction, onUidStateChanged will be called. + * @param callingPackage The name of the calling package. + * @param uids A list of uids to watch. If all uids are to be watched, use + * registerUidObserver instead. + * @throws RemoteException + * @return Returns A binder token identifying the UidObserver registration. + */ + @Override + public IBinder registerUidObserverForUids(IUidObserver observer, int which, int cutpoint, + String callingPackage, int[] uids) { + if (!hasUsageStatsPermission(callingPackage)) { + enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, + "registerUidObserver"); + } + return mUidObserverController.register(observer, which, cutpoint, callingPackage, + Binder.getCallingUid(), uids); } @Override @@ -7522,6 +7552,40 @@ public class ActivityManagerService extends IActivityManager.Stub mUidObserverController.unregister(observer); } + /** + * Adds a uid to the list of uids that a UidObserver will receive updates about. + * + * @param observerToken The binder token identifying the UidObserver registration. + * @param callingPackage The name of the calling package. + * @param uid The uid to watch. + * @throws RemoteException + */ + @Override + public void addUidToObserver(IBinder observerToken, String callingPackage, int uid) { + if (!hasUsageStatsPermission(callingPackage)) { + enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, + "registerUidObserver"); + } + mUidObserverController.addUidToObserver(observerToken, uid); + } + + /** + * Removes a uid from the list of uids that a UidObserver will receive updates about. + * + * @param observerToken The binder token identifying the UidObserver registration. + * @param callingPackage The name of the calling package. + * @param uid The uid to stop watching. + * @throws RemoteException + */ + @Override + public void removeUidFromObserver(IBinder observerToken, String callingPackage, int uid) { + if (!hasUsageStatsPermission(callingPackage)) { + enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, + "registerUidObserver"); + } + mUidObserverController.removeUidFromObserver(observerToken, uid); + } + @Override public boolean isUidActive(int uid, String callingPackage) { if (!hasUsageStatsPermission(callingPackage)) { @@ -18616,7 +18680,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 @@ -18909,6 +18973,13 @@ public class ActivityManagerService extends IActivityManager.Stub pw.flush(); } + void waitForBroadcastDispatch(@NonNull PrintWriter pw, @NonNull Intent intent) { + enforceCallingPermission(permission.DUMP, "waitForBroadcastDispatch"); + for (BroadcastQueue queue : mBroadcastQueues) { + queue.waitForDispatched(intent, pw); + } + } + void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) { Objects.requireNonNull(broadcastAction); enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()"); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 17a0d62c27b3..8759e3f207c4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -368,6 +368,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runWaitForBroadcastBarrier(pw); case "wait-for-application-barrier": return runWaitForApplicationBarrier(pw); + case "wait-for-broadcast-dispatch": + return runWaitForBroadcastDispatch(pw); case "set-ignore-delivery-group-policy": return runSetIgnoreDeliveryGroupPolicy(pw); case "clear-ignore-delivery-group-policy": @@ -3472,6 +3474,18 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runWaitForBroadcastDispatch(PrintWriter pw) throws RemoteException { + pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw)); + final Intent intent; + try { + intent = makeIntent(UserHandle.USER_CURRENT); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + mInternal.waitForBroadcastDispatch(pw, intent); + return 0; + } + int runSetIgnoreDeliveryGroupPolicy(PrintWriter pw) throws RemoteException { final String broadcastAction = getNextArgRequired(); mInternal.setIgnoreDeliveryGroupPolicy(broadcastAction); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 6360e2a0d089..36da888dbc2a 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -399,6 +399,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler); } + /** + * Creates an instance of BatteryStatsService and restores data from stored state. + */ + public static BatteryStatsService create(Context context, File systemDir, Handler handler, + BatteryStatsImpl.BatteryCallback callback) { + BatteryStatsService service = new BatteryStatsService(context, systemDir, handler); + service.mStats.setCallback(callback); + synchronized (service.mStats) { + service.mStats.readLocked(); + } + service.scheduleWriteToDisk(); + return service; + } + public void publish() { LocalServices.addService(BatteryStatsInternal.class, new LocalService()); ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder()); diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java index 2adcf2f48343..8aa3921d3f2f 100644 --- a/services/core/java/com/android/server/am/BroadcastDispatcher.java +++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java @@ -582,6 +582,38 @@ public class BroadcastDispatcher { } } + private static boolean isDispatchedInDeferrals(@NonNull ArrayList<Deferrals> list, + @NonNull Intent intent) { + for (int i = 0; i < list.size(); i++) { + if (!isDispatched(list.get(i).broadcasts, intent)) { + return false; + } + } + return true; + } + + private static boolean isDispatched(@NonNull ArrayList<BroadcastRecord> list, + @NonNull Intent intent) { + for (int i = 0; i < list.size(); i++) { + if (intent.filterEquals(list.get(i).intent)) { + return false; + } + } + return true; + } + + public boolean isDispatched(@NonNull Intent intent) { + synchronized (mLock) { + if ((mCurrentBroadcast != null) && intent.filterEquals(mCurrentBroadcast.intent)) { + return false; + } + return isDispatched(mOrderedBroadcasts, intent) + && isDispatched(mAlarmQueue, intent) + && isDispatchedInDeferrals(mDeferredBroadcasts, intent) + && isDispatchedInDeferrals(mAlarmDeferrals, intent); + } + } + private static int pendingInDeferralsList(ArrayList<Deferrals> list) { int pending = 0; final int numEntries = list.size(); diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 5c68e6759083..c7ef8fab2da6 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -182,7 +182,7 @@ class BroadcastProcessQueue { private int mCountInstrumented; private int mCountManifest; - private boolean mPrioritizeEarliest; + private int mCountPrioritizeEarliestRequests; private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE; private @Reason int mRunnableAtReason = REASON_EMPTY; @@ -748,7 +748,7 @@ class BroadcastProcessQueue { final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1; final int nextLPRecordIndex = nextLPArgs.argi1; final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1; - final boolean shouldConsiderLPQueue = (mPrioritizeEarliest + final boolean shouldConsiderLPQueue = (mCountPrioritizeEarliestRequests > 0 || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit); final boolean isLPQueueEligible = shouldConsiderLPQueue && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime @@ -761,10 +761,9 @@ class BroadcastProcessQueue { } /** - * When {@code prioritizeEarliest} is set to {@code true}, then earliest enqueued - * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts - * waiting. This is typically used in case there are callers waiting for "barrier" to be - * reached. + * Add a request to prioritize dispatching of broadcasts that have been enqueued the earliest, + * even if there are urgent broadcasts waiting to be dispatched. This is typically used in + * case there are callers waiting for "barrier" to be reached. * * @return if this operation may have changed internal state, indicating * that the caller is responsible for invoking @@ -772,12 +771,38 @@ class BroadcastProcessQueue { */ @CheckResult @VisibleForTesting - boolean setPrioritizeEarliest(boolean prioritizeEarliest) { - if (mPrioritizeEarliest != prioritizeEarliest) { - mPrioritizeEarliest = prioritizeEarliest; + boolean addPrioritizeEarliestRequest() { + if (mCountPrioritizeEarliestRequests == 0) { + mCountPrioritizeEarliestRequests++; invalidateRunnableAt(); return true; } else { + mCountPrioritizeEarliestRequests++; + return false; + } + } + + /** + * Remove a request to prioritize dispatching of broadcasts that have been enqueued the + * earliest, even if there are urgent broadcasts waiting to be dispatched. This is typically + * used in case there are callers waiting for "barrier" to be reached. + * + * <p> Once there are no more remaining requests, the dispatching order reverts back to normal. + * + * @return if this operation may have changed internal state, indicating + * that the caller is responsible for invoking + * {@link BroadcastQueueModernImpl#updateRunnableList} + */ + @CheckResult + boolean removePrioritizeEarliestRequest() { + mCountPrioritizeEarliestRequests--; + if (mCountPrioritizeEarliestRequests == 0) { + invalidateRunnableAt(); + return true; + } else if (mCountPrioritizeEarliestRequests < 0) { + mCountPrioritizeEarliestRequests = 0; + return false; + } else { return false; } } @@ -837,7 +862,7 @@ class BroadcastProcessQueue { } /** - * Quickly determine if this queue has broadcasts enqueued before the given + * Quickly determine if this queue has non-deferred broadcasts enqueued before the given * barrier timestamp that are still waiting to be delivered. */ public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) { @@ -859,6 +884,41 @@ class BroadcastProcessQueue { || isDeferredUntilActive(); } + /** + * Quickly determine if this queue has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + */ + public boolean isDispatched(@NonNull Intent intent) { + final boolean activeDispatched = (mActive == null) + || (!intent.filterEquals(mActive.intent)); + final boolean dispatched = isDispatchedInQueue(mPending, intent); + final boolean urgentDispatched = isDispatchedInQueue(mPendingUrgent, intent); + final boolean offloadDispatched = isDispatchedInQueue(mPendingOffload, intent); + + return (activeDispatched && dispatched && urgentDispatched && offloadDispatched) + || isDeferredUntilActive(); + } + + /** + * Quickly determine if the {@code queue} has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + */ + private boolean isDispatchedInQueue(@NonNull ArrayDeque<SomeArgs> queue, + @NonNull Intent intent) { + final Iterator<SomeArgs> it = queue.iterator(); + while (it.hasNext()) { + final SomeArgs args = it.next(); + if (args == null) { + return true; + } + final BroadcastRecord record = (BroadcastRecord) args.arg1; + if (intent.filterEquals(record.intent)) { + return false; + } + } + return true; + } + public boolean isRunnable() { if (mRunnableAtInvalidated) updateRunnableAt(); return mRunnableAt != Long.MAX_VALUE; diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 6d1344d79b6c..8e76e5b5cf48 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -192,7 +192,7 @@ public abstract class BroadcastQueue { public abstract boolean isIdleLocked(); /** - * Quickly determine if this queue has broadcasts enqueued before the given + * Quickly determine if this queue has non-deferred broadcasts enqueued before the given * barrier timestamp that are still waiting to be delivered. * * @see #waitForIdle @@ -202,6 +202,15 @@ public abstract class BroadcastQueue { public abstract boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime); /** + * Quickly determine if this queue has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + * + * @see #waitForDispatched(Intent, PrintWriter) + */ + @GuardedBy("mService") + public abstract boolean isDispatchedLocked(@NonNull Intent intent); + + /** * Wait until this queue becomes completely idle. * <p> * Any broadcasts waiting to be delivered at some point in the future will @@ -214,7 +223,7 @@ public abstract class BroadcastQueue { public abstract void waitForIdle(@NonNull PrintWriter pw); /** - * Wait until any currently waiting broadcasts have been dispatched. + * Wait until any currently waiting non-deferred broadcasts have been dispatched. * <p> * Any broadcasts waiting to be delivered at some point in the future will * be dispatched as quickly as possible. @@ -225,6 +234,15 @@ public abstract class BroadcastQueue { public abstract void waitForBarrier(@NonNull PrintWriter pw); /** + * Wait until all non-deferred broadcasts matching {@code intent}, as defined by + * {@link Intent#filterEquals(Intent)}, have been dispatched. + * <p> + * Any broadcasts waiting to be delivered at some point in the future will + * be dispatched as quickly as possible. + */ + public abstract void waitForDispatched(@NonNull Intent intent, @NonNull PrintWriter pw); + + /** * Delays delivering broadcasts to the specified package. * * <p> Note that this is only valid for modern queue. diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 4a69f90d9fc0..7f3ceb578891 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -1793,6 +1793,23 @@ public class BroadcastQueueImpl extends BroadcastQueue { return mDispatcher.isBeyondBarrier(barrierTime); } + public boolean isDispatchedLocked(Intent intent) { + if (isIdleLocked()) return true; + + for (int i = 0; i < mParallelBroadcasts.size(); i++) { + if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) { + return false; + } + } + + final BroadcastRecord pending = getPendingBroadcastLocked(); + if ((pending != null) && intent.filterEquals(pending.intent)) { + return false; + } + + return mDispatcher.isDispatched(intent); + } + public void waitForIdle(PrintWriter pw) { waitFor(() -> isIdleLocked(), pw, "idle"); } @@ -1802,6 +1819,10 @@ public class BroadcastQueueImpl extends BroadcastQueue { waitFor(() -> isBeyondBarrierLocked(barrierTime), pw, "barrier"); } + public void waitForDispatched(Intent intent, PrintWriter pw) { + waitFor(() -> isDispatchedLocked(intent), pw, "dispatch"); + } + private void waitFor(BooleanSupplier condition, PrintWriter pw, String conditionName) { long lastPrint = 0; while (true) { diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 96e152320282..539f4e844413 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -1376,6 +1376,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } @Override + public boolean isDispatchedLocked(@NonNull Intent intent) { + return isDispatchedLocked(intent, LOG_WRITER_INFO); + } + + public boolean isDispatchedLocked(@NonNull Intent intent, @NonNull PrintWriter pw) { + return testAllProcessQueues(q -> q.isDispatched(intent), + "dispatch of " + intent, pw); + } + + @Override public void waitForIdle(@NonNull PrintWriter pw) { waitFor(() -> isIdleLocked(pw)); } @@ -1383,28 +1393,35 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @Override public void waitForBarrier(@NonNull PrintWriter pw) { final long now = SystemClock.uptimeMillis(); - waitFor(() -> isBeyondBarrierLocked(now, pw)); + synchronized (mService) { + forEachMatchingQueue(QUEUE_PREDICATE_ANY, + q -> q.addPrioritizeEarliestRequest()); + } + try { + waitFor(() -> isBeyondBarrierLocked(now, pw)); + } finally { + synchronized (mService) { + forEachMatchingQueue(QUEUE_PREDICATE_ANY, + q -> q.removePrioritizeEarliestRequest()); + } + } + } + + @Override + public void waitForDispatched(@NonNull Intent intent, @NonNull PrintWriter pw) { + waitFor(() -> isDispatchedLocked(intent, pw)); } private void waitFor(@NonNull BooleanSupplier condition) { final CountDownLatch latch = new CountDownLatch(1); synchronized (mService) { mWaitingFor.add(Pair.create(condition, latch)); - forEachMatchingQueue(QUEUE_PREDICATE_ANY, - (q) -> q.setPrioritizeEarliest(true)); } enqueueUpdateRunningList(); try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); - } finally { - synchronized (mService) { - if (mWaitingFor.isEmpty()) { - forEachMatchingQueue(QUEUE_PREDICATE_ANY, - (q) -> q.setPrioritizeEarliest(false)); - } - } } } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index d7b22a8a2da0..c393213c5e85 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2300,6 +2300,8 @@ public final class ProcessList { final Process.ProcessStartResult startResult; boolean regularZygote = false; + app.mProcessGroupCreated = false; + app.mSkipProcessGroupCreation = false; if (hostingRecord.usesWebviewZygote()) { startResult = startWebView(entryPoint, app.processName, uid, uid, gids, runtimeFlags, mountExternal, @@ -2328,18 +2330,28 @@ public final class ProcessList { isTopApp, app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs, new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()}); + // By now the process group should have been created by zygote. + app.mProcessGroupCreated = true; } if (!regularZygote) { // webview and app zygote don't have the permission to create the nodes - final int res = Process.createProcessGroup(uid, startResult.pid); - if (res < 0) { - if (res == -OsConstants.ESRCH) { - Slog.e(ActivityManagerService.TAG, "Unable to create process group for " - + app.processName + " (" + startResult.pid + ")"); - } else { - throw new AssertionError("Unable to create process group for " - + app.processName + " (" + startResult.pid + ")"); + synchronized (app) { + if (!app.mSkipProcessGroupCreation) { + // If we're not told to skip the process group creation, go create it. + final int res = Process.createProcessGroup(uid, startResult.pid); + if (res < 0) { + if (res == -OsConstants.ESRCH) { + Slog.e(ActivityManagerService.TAG, + "Unable to create process group for " + + app.processName + " (" + startResult.pid + ")"); + } else { + throw new AssertionError("Unable to create process group for " + + app.processName + " (" + startResult.pid + ")"); + } + } else { + app.mProcessGroupCreated = true; + } } } } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 0417b8cfa2e2..4ec813ecd81c 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -424,6 +424,16 @@ class ProcessRecord implements WindowProcessListener { */ Runnable mSuccessorStartRunnable; + /** + * Whether or not the process group of this process has been created. + */ + volatile boolean mProcessGroupCreated; + + /** + * Whether or not we should skip the process group creation. + */ + volatile boolean mSkipProcessGroupCreation; + void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo, long startUptime, long startElapsedTime) { this.mStartUid = startUid; @@ -1192,8 +1202,26 @@ class ProcessRecord implements WindowProcessListener { EventLog.writeEvent(EventLogTags.AM_KILL, userId, mPid, processName, mState.getSetAdj(), reason); Process.killProcessQuiet(mPid); - if (!asyncKPG) Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL); - ProcessList.killProcessGroup(uid, mPid); + final boolean killProcessGroup; + if (mHostingRecord != null + && (mHostingRecord.usesWebviewZygote() || mHostingRecord.usesAppZygote())) { + synchronized (ProcessRecord.this) { + killProcessGroup = mProcessGroupCreated; + if (!killProcessGroup) { + // The process group hasn't been created, request to skip it. + mSkipProcessGroupCreation = true; + } + } + } else { + killProcessGroup = true; + } + if (killProcessGroup) { + if (asyncKPG) { + ProcessList.killProcessGroup(uid, mPid); + } else { + Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL); + } + } } else { mPendingStart = false; } 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/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index a110169ac8c2..1f3795a12b76 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -44,6 +44,7 @@ import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED; +import static android.app.AppOpsManager.OP_RUN_ANY_IN_BACKGROUND; import static android.app.AppOpsManager.OP_VIBRATE; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED; @@ -1769,6 +1770,11 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void setUidMode(int code, int uid, int mode) { setUidMode(code, uid, mode, null); + if (code == OP_RUN_ANY_IN_BACKGROUND) { + // TODO (b/280869337): Remove this once we have the required data. + Slog.wtfStack(TAG, "setUidMode called for RUN_ANY_IN_BACKGROUND by uid: " + + UserHandle.formatUid(Binder.getCallingUid())); + } } private void setUidMode(int code, int uid, int mode, @@ -1944,6 +1950,17 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void setMode(int code, int uid, @NonNull String packageName, int mode) { setMode(code, uid, packageName, mode, null); + final int callingUid = Binder.getCallingUid(); + if (code == OP_RUN_ANY_IN_BACKGROUND && mode != MODE_ALLOWED) { + // TODO (b/280869337): Remove this once we have the required data. + final String callingPackage = ArrayUtils.firstOrNull(getPackagesForUid(callingUid)); + Slog.wtfStack(TAG, + "RUN_ANY_IN_BACKGROUND for package " + packageName + " changed to mode: " + + modeToName(mode) + " via setMode. Calling package: " + callingPackage + + ", calling uid: " + UserHandle.formatUid(callingUid) + + ", calling pid: " + Binder.getCallingPid() + + ", system pid: " + Process.myPid()); + } } void setMode(int code, int uid, @NonNull String packageName, int mode, diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 773df3720ed3..1d8bef1c6732 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -1031,7 +1031,8 @@ public class AudioDeviceInventory { synchronized (rolesMap) { Pair<Integer, Integer> key = new Pair<>(useCase, role); if (!rolesMap.containsKey(key)) { - return AudioSystem.SUCCESS; + // trying to remove a role for a device that wasn't set + return AudioSystem.BAD_VALUE; } List<AudioDeviceAttributes> roleDevices = rolesMap.get(key); List<AudioDeviceAttributes> appliedDevices = new ArrayList<>(); diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index aece17e7a6e6..4aa256d3ce74 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -78,6 +78,23 @@ public class SoundDoseHelper { /*package*/ static final String ACTION_CHECK_MUSIC_ACTIVE = "com.android.server.audio.action.CHECK_MUSIC_ACTIVE"; + /** + * Property to force the index based safe volume warnings. Note that usually when the + * CSD warnings are active the safe volume warnings are deactivated. In combination with + * {@link SoundDoseHelper#SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE} both approaches can be active + * at the same time. + */ + private static final String SYSTEM_PROPERTY_SAFEMEDIA_FORCE = "audio.safemedia.force"; + /** Property for bypassing the index based safe volume approach. */ + private static final String SYSTEM_PROPERTY_SAFEMEDIA_BYPASS = "audio.safemedia.bypass"; + /** + * Property to force the CSD warnings. Note that usually when the CSD warnings are active the + * safe volume warnings are deactivated. In combination with + * {@link SoundDoseHelper#SYSTEM_PROPERTY_SAFEMEDIA_FORCE} both approaches can be active + * at the same time. + */ + private static final String SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE = "audio.safemedia.csd.force"; + // mSafeMediaVolumeState indicates whether the media volume is limited over headphones. // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or @@ -830,56 +847,64 @@ public class SoundDoseHelper { } private void onConfigureSafeMedia(boolean force, String caller) { + updateCsdEnabled(caller); + synchronized (mSafeMediaVolumeStateLock) { int mcc = mContext.getResources().getConfiguration().mcc; if ((mMcc != mcc) || ((mMcc == 0) && force)) { mSafeMediaVolumeIndex = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_index) * 10; - initSafeMediaVolumeIndex(); - boolean safeMediaVolumeEnabled = - SystemProperties.getBoolean("audio.safemedia.force", false) - || mContext.getResources().getBoolean( - com.android.internal.R.bool.config_safe_media_volume_enabled); - boolean safeMediaVolumeBypass = - SystemProperties.getBoolean("audio.safemedia.bypass", false); - - // The persisted state is either "disabled" or "active": this is the state applied - // next time we boot and cannot be "inactive" - int persistedState; - if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) { - persistedState = SAFE_MEDIA_VOLUME_ACTIVE; - // The state can already be "inactive" here if the user has forced it before - // the 30 seconds timeout for forced configuration. In this case we don't reset - // it to "active". - if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) { - if (mMusicActiveMs == 0) { - mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE; - enforceSafeMediaVolume(caller); - } else { - // We have existing playback time recorded, already confirmed. - mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE; - mLastMusicActiveTimeMs = 0; - } - } - } else { - persistedState = SAFE_MEDIA_VOLUME_DISABLED; - mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED; - } + updateSafeMediaVolume_l(caller); + mMcc = mcc; - mAudioHandler.sendMessageAtTime( - mAudioHandler.obtainMessage(MSG_PERSIST_SAFE_VOLUME_STATE, - persistedState, /*arg2=*/0, - /*obj=*/null), /*delay=*/0); } + } + } - updateCsdEnabled(caller); + @GuardedBy("mSafeMediaVolumeStateLock") + private void updateSafeMediaVolume_l(String caller) { + boolean safeMediaVolumeEnabled = + SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_FORCE, false) + || (mContext.getResources().getBoolean( + com.android.internal.R.bool.config_safe_media_volume_enabled) + && !mEnableCsd.get()); + boolean safeMediaVolumeBypass = + SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_BYPASS, false); + + // The persisted state is either "disabled" or "active": this is the state applied + // next time we boot and cannot be "inactive" + int persistedState; + if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) { + persistedState = SAFE_MEDIA_VOLUME_ACTIVE; + // The state can already be "inactive" here if the user has forced it before + // the 30 seconds timeout for forced configuration. In this case we don't reset + // it to "active". + if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) { + if (mMusicActiveMs == 0) { + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE; + enforceSafeMediaVolume(caller); + } else { + // We have existing playback time recorded, already confirmed. + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE; + mLastMusicActiveTimeMs = 0; + } + } + } else { + persistedState = SAFE_MEDIA_VOLUME_DISABLED; + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED; } + + mAudioHandler.sendMessageAtTime( + mAudioHandler.obtainMessage(MSG_PERSIST_SAFE_VOLUME_STATE, + persistedState, /*arg2=*/0, + /*obj=*/null), /*delay=*/0); } private void updateCsdEnabled(String caller) { - boolean newEnableCsd = SystemProperties.getBoolean("audio.safemedia.force", false); + boolean newEnableCsd = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, + false); if (!newEnableCsd) { final String featureFlagEnableCsdValue = DeviceConfig.getProperty( DeviceConfig.NAMESPACE_MEDIA, @@ -895,6 +920,10 @@ public class SoundDoseHelper { if (mEnableCsd.compareAndSet(!newEnableCsd, newEnableCsd)) { Log.i(TAG, caller + ": enable CSD " + newEnableCsd); initCsd(); + + synchronized (mSafeMediaVolumeStateLock) { + updateSafeMediaVolume_l(caller); + } } } diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index da822fab6af4..3e31bd1e820f 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -892,9 +892,8 @@ public class SyncManager { * @return true/false if contact sharing is enabled/disabled */ protected boolean isContactSharingAllowedForCloneProfile() { - // TODO(b/253449368): This method should also check for the config controlling - // all app-cloning features. - return mAppCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks(); + return mContext.getResources().getBoolean(R.bool.config_enableAppCloningBuildingBlocks) + && mAppCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks(); } /** diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index ac8ff21b7fa6..ba5907c4b4d0 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -256,6 +256,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, private final PackageManagerInternal mPmInternal; private volatile boolean mFeatureEnabled = PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT; + @GuardedBy("mDisabledPackages") private final ArraySet<String> mDisabledPackages = new ArraySet<>(); @Nullable @@ -272,7 +273,9 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, mInjector = null; mPmInternal = null; mFeatureEnabled = orig.mFeatureEnabled; - mDisabledPackages.addAll(orig.mDisabledPackages); + synchronized (orig.mDisabledPackages) { + mDisabledPackages.addAll(orig.mDisabledPackages); + } mLoggingEnabled = orig.mLoggingEnabled; } @@ -319,7 +322,9 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "packageIsEnabled"); } try { - return !mDisabledPackages.contains(pkg.getPackageName()); + synchronized (mDisabledPackages) { + return !mDisabledPackages.contains(pkg.getPackageName()); + } } finally { if (DEBUG_TRACING) { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -376,10 +381,12 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, final boolean enabled = mInjector.getCompatibility().isChangeEnabledInternalNoLogging( PackageManager.FILTER_APPLICATION_QUERY, AndroidPackageUtils.generateAppInfoWithoutState(pkg)); - if (enabled) { - mDisabledPackages.remove(pkg.getPackageName()); - } else { - mDisabledPackages.add(pkg.getPackageName()); + synchronized (mDisabledPackages) { + if (enabled) { + mDisabledPackages.remove(pkg.getPackageName()); + } else { + mDisabledPackages.add(pkg.getPackageName()); + } } if (mAppsFilter != null) { mAppsFilter.onChanged(); @@ -393,7 +400,9 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, || setting.getPkg().isDebuggable()); enableLogging(setting.getAppId(), enableLogging); if (removed) { - mDisabledPackages.remove(setting.getPackageName()); + synchronized (mDisabledPackages) { + mDisabledPackages.remove(setting.getPackageName()); + } if (mAppsFilter != null) { mAppsFilter.onChanged(); } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 9b1a80bed17b..0bd6dffed024 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -1330,7 +1330,11 @@ public class LauncherAppsService extends SystemService { @Override public PendingIntent getActivityLaunchIntent(String callingPackage, ComponentName component, UserHandle user) { - ensureShortcutPermission(callingPackage); + if (mContext.checkPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS, + injectBinderCallingPid(), injectBinderCallingUid()) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Permission START_TASKS_FROM_RECENTS required"); + } if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) { throw new ActivityNotFoundException("Activity could not be found"); } diff --git a/services/core/java/com/android/server/pm/NoFilteringResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java index 392389009398..ccd5b0ef4285 100644 --- a/services/core/java/com/android/server/pm/NoFilteringResolver.java +++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java @@ -23,6 +23,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Binder; +import com.android.internal.R; import com.android.internal.config.appcloning.AppCloningDeviceConfigHelper; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.resolution.ComponentResolverApi; @@ -61,7 +62,8 @@ public class NoFilteringResolver extends CrossProfileResolver { long flags) { final long token = Binder.clearCallingIdentity(); try { - return appCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks() + return context.getResources().getBoolean(R.bool.config_enableAppCloningBuildingBlocks) + && appCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks() && (resolveForStart || (((flags & PackageManager.MATCH_CLONE_PROFILE) != 0) && hasPermission(context, Manifest.permission.QUERY_CLONED_APPS))); } finally { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index dc56def09ee8..d7eff52af9b4 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3096,18 +3096,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { } int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId; + float minLinearBrightness = mPowerManager.getBrightnessConstraint( + PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); + float maxLinearBrightness = mPowerManager.getBrightnessConstraint( + PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); float linearBrightness = mDisplayManager.getBrightness(screenDisplayId); float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness); float adjustedGammaBrightness = gammaBrightness + 1f / BRIGHTNESS_STEPS * direction; - + adjustedGammaBrightness = MathUtils.constrain(adjustedGammaBrightness, 0f, + 1f); float adjustedLinearBrightness = BrightnessUtils.convertGammaToLinear( adjustedGammaBrightness); - - adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness, 0f, - 1f); - + adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness, + minLinearBrightness, maxLinearBrightness); mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness); startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG), diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 1c89ec445426..3c5ad2acc8a0 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -1573,7 +1573,7 @@ public class TrustManagerService extends SystemService { @Override public void reportUserMayRequestUnlock(int userId) throws RemoteException { enforceReportPermission(); - mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK, userId).sendToTarget(); + mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK, userId, /*arg2=*/ 0).sendToTarget(); } @Override diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 88c9042c963e..1f5c1cf6a959 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -2629,6 +2629,10 @@ public final class TvInputManagerService extends SystemService { getSessionLocked(sessionState).notifyAdBufferReady(buffer); } catch (RemoteException | SessionNotFoundException e) { Slog.e(TAG, "error in notifyAdBuffer", e); + } finally { + if (buffer != null) { + buffer.getSharedMemory().close(); + } } } } finally { @@ -3891,10 +3895,13 @@ public final class TvInputManagerService extends SystemService { return; } try { - mSessionState.client.onAdBufferConsumed( - buffer, mSessionState.seq); + mSessionState.client.onAdBufferConsumed(buffer, mSessionState.seq); } catch (RemoteException e) { Slog.e(TAG, "error in onAdBufferConsumed", e); + } finally { + if (buffer != null) { + buffer.getSharedMemory().close(); + } } } } diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java index 80cb0855922e..e6273d331fce 100644 --- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -2007,6 +2007,10 @@ public class TvInteractiveAppManagerService extends SystemService { getSessionLocked(sessionState).notifyAdBufferConsumed(buffer); } catch (RemoteException | SessionNotFoundException e) { Slogf.e(TAG, "error in notifyAdBufferConsumed", e); + } finally { + if (buffer != null) { + buffer.getSharedMemory().close(); + } } } } finally { @@ -3063,6 +3067,10 @@ public class TvInteractiveAppManagerService extends SystemService { mSessionState.mClient.onAdBufferReady(buffer, mSessionState.mSeq); } catch (RemoteException e) { Slogf.e(TAG, "error in onAdBuffer", e); + } finally { + if (buffer != null) { + buffer.getSharedMemory().close(); + } } } } 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/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index c40d72c3746e..6b90181cff0b 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1159,9 +1159,10 @@ class ActivityClientController extends IActivityClientController.Stub { } return; } + transition.collect(topFocusedRootTask); + executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask); r.mTransitionController.requestStartTransition(transition, topFocusedRootTask, null /* remoteTransition */, null /* displayChange */); - executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask); transition.setReady(topFocusedRootTask, true); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0b572dfea3e3..f35343c98711 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3555,7 +3555,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mRootWindowContainer.forAllDisplays(displayContent -> { mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags); }); - getWallpaperManagerInternal().onKeyguardGoingAway(); + WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal(); + if (wallpaperManagerInternal != null) { + wallpaperManagerInternal.onKeyguardGoingAway(); + } } } finally { Binder.restoreCallingIdentity(token); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index be52e5a4566b..911591c6a508 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -22,6 +22,7 @@ import static android.view.InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE; import static android.view.InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS; import static android.view.InsetsFrameProvider.SOURCE_DISPLAY; import static android.view.InsetsFrameProvider.SOURCE_FRAME; +import static android.view.ViewRootImpl.CLIENT_TRANSIENT; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -184,7 +185,6 @@ public class DisplayPolicy { private final boolean mCarDockEnablesAccelerometer; private final boolean mDeskDockEnablesAccelerometer; - private final boolean mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer; private final AccessibilityManager mAccessibilityManager; private final ImmersiveModeConfirmation mImmersiveModeConfirmation; private final ScreenshotHelper mScreenshotHelper; @@ -214,7 +214,8 @@ public class DisplayPolicy { } } - private final SystemGesturesPointerEventListener mSystemGestures; + // Will be null in client transient mode. + private SystemGesturesPointerEventListener mSystemGestures; final DecorInsets mDecorInsets; @@ -394,8 +395,6 @@ public class DisplayPolicy { final Resources r = mContext.getResources(); mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer); mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer); - mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer = - r.getBoolean(R.bool.config_deskRespectsNoSensorAndLockedWithoutAccelerometer); mCanSystemBarsBeShownByUser = !r.getBoolean( R.bool.config_remoteInsetsControllerControlsSystemBars) || r.getBoolean( R.bool.config_remoteInsetsControllerSystemBarsCanBeShownByUserAction); @@ -411,158 +410,162 @@ public class DisplayPolicy { final Looper looper = UiThread.getHandler().getLooper(); mHandler = new PolicyHandler(looper); // TODO(b/181821798) Migrate SystemGesturesPointerEventListener to use window context. - mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler, - new SystemGesturesPointerEventListener.Callbacks() { + if (!CLIENT_TRANSIENT) { + SystemGesturesPointerEventListener.Callbacks gesturesPointerEventCallbacks = + new SystemGesturesPointerEventListener.Callbacks() { - private static final long MOUSE_GESTURE_DELAY_MS = 500; + private static final long MOUSE_GESTURE_DELAY_MS = 500; - private Runnable mOnSwipeFromLeft = this::onSwipeFromLeft; - private Runnable mOnSwipeFromTop = this::onSwipeFromTop; - private Runnable mOnSwipeFromRight = this::onSwipeFromRight; - private Runnable mOnSwipeFromBottom = this::onSwipeFromBottom; + private Runnable mOnSwipeFromLeft = this::onSwipeFromLeft; + private Runnable mOnSwipeFromTop = this::onSwipeFromTop; + private Runnable mOnSwipeFromRight = this::onSwipeFromRight; + private Runnable mOnSwipeFromBottom = this::onSwipeFromBottom; - private Insets getControllableInsets(WindowState win) { - if (win == null) { - return Insets.NONE; - } - final InsetsSourceProvider provider = win.getControllableInsetProvider(); - if (provider == null) { - return Insets.NONE; - } - return provider.getSource().calculateInsets(win.getBounds(), - true /* ignoreVisibility */); + private Insets getControllableInsets(WindowState win) { + if (win == null) { + return Insets.NONE; } - - @Override - public void onSwipeFromTop() { - synchronized (mLock) { - requestTransientBars(mTopGestureHost, - getControllableInsets(mTopGestureHost).top > 0); - } + final InsetsSourceProvider provider = win.getControllableInsetProvider(); + if (provider == null) { + return Insets.NONE; } + return provider.getSource().calculateInsets(win.getBounds(), + true /* ignoreVisibility */); + } - @Override - public void onSwipeFromBottom() { - synchronized (mLock) { - requestTransientBars(mBottomGestureHost, - getControllableInsets(mBottomGestureHost).bottom > 0); - } + @Override + public void onSwipeFromTop() { + synchronized (mLock) { + requestTransientBars(mTopGestureHost, + getControllableInsets(mTopGestureHost).top > 0); } + } - private boolean allowsSideSwipe(Region excludedRegion) { - return mNavigationBarAlwaysShowOnSideGesture - && !mSystemGestures.currentGestureStartedInRegion(excludedRegion); + @Override + public void onSwipeFromBottom() { + synchronized (mLock) { + requestTransientBars(mBottomGestureHost, + getControllableInsets(mBottomGestureHost).bottom > 0); } + } - @Override - public void onSwipeFromRight() { - final Region excludedRegion = Region.obtain(); - synchronized (mLock) { - mDisplayContent.calculateSystemGestureExclusion( - excludedRegion, null /* outUnrestricted */); - final boolean hasWindow = - getControllableInsets(mRightGestureHost).right > 0; - if (hasWindow || allowsSideSwipe(excludedRegion)) { - requestTransientBars(mRightGestureHost, hasWindow); - } - } - excludedRegion.recycle(); - } + private boolean allowsSideSwipe(Region excludedRegion) { + return mNavigationBarAlwaysShowOnSideGesture + && !mSystemGestures.currentGestureStartedInRegion(excludedRegion); + } - @Override - public void onSwipeFromLeft() { - final Region excludedRegion = Region.obtain(); - synchronized (mLock) { - mDisplayContent.calculateSystemGestureExclusion( - excludedRegion, null /* outUnrestricted */); - final boolean hasWindow = - getControllableInsets(mLeftGestureHost).left > 0; - if (hasWindow || allowsSideSwipe(excludedRegion)) { - requestTransientBars(mLeftGestureHost, hasWindow); - } + @Override + public void onSwipeFromRight() { + final Region excludedRegion = Region.obtain(); + synchronized (mLock) { + mDisplayContent.calculateSystemGestureExclusion( + excludedRegion, null /* outUnrestricted */); + final boolean hasWindow = + getControllableInsets(mRightGestureHost).right > 0; + if (hasWindow || allowsSideSwipe(excludedRegion)) { + requestTransientBars(mRightGestureHost, hasWindow); } - excludedRegion.recycle(); } + excludedRegion.recycle(); + } - @Override - public void onFling(int duration) { - if (mService.mPowerManagerInternal != null) { - mService.mPowerManagerInternal.setPowerBoost( - Boost.INTERACTION, duration); + @Override + public void onSwipeFromLeft() { + final Region excludedRegion = Region.obtain(); + synchronized (mLock) { + mDisplayContent.calculateSystemGestureExclusion( + excludedRegion, null /* outUnrestricted */); + final boolean hasWindow = + getControllableInsets(mLeftGestureHost).left > 0; + if (hasWindow || allowsSideSwipe(excludedRegion)) { + requestTransientBars(mLeftGestureHost, hasWindow); } } + excludedRegion.recycle(); + } - @Override - public void onDebug() { - // no-op + @Override + public void onFling(int duration) { + if (mService.mPowerManagerInternal != null) { + mService.mPowerManagerInternal.setPowerBoost( + Boost.INTERACTION, duration); } + } - private WindowOrientationListener getOrientationListener() { - final DisplayRotation rotation = mDisplayContent.getDisplayRotation(); - return rotation != null ? rotation.getOrientationListener() : null; - } + @Override + public void onDebug() { + // no-op + } - @Override - public void onDown() { - final WindowOrientationListener listener = getOrientationListener(); - if (listener != null) { - listener.onTouchStart(); - } - } + private WindowOrientationListener getOrientationListener() { + final DisplayRotation rotation = mDisplayContent.getDisplayRotation(); + return rotation != null ? rotation.getOrientationListener() : null; + } - @Override - public void onUpOrCancel() { - final WindowOrientationListener listener = getOrientationListener(); - if (listener != null) { - listener.onTouchEnd(); - } + @Override + public void onDown() { + final WindowOrientationListener listener = getOrientationListener(); + if (listener != null) { + listener.onTouchStart(); } + } - @Override - public void onMouseHoverAtLeft() { - mHandler.removeCallbacks(mOnSwipeFromLeft); - mHandler.postDelayed(mOnSwipeFromLeft, MOUSE_GESTURE_DELAY_MS); + @Override + public void onUpOrCancel() { + final WindowOrientationListener listener = getOrientationListener(); + if (listener != null) { + listener.onTouchEnd(); } + } - @Override - public void onMouseHoverAtTop() { - mHandler.removeCallbacks(mOnSwipeFromTop); - mHandler.postDelayed(mOnSwipeFromTop, MOUSE_GESTURE_DELAY_MS); - } + @Override + public void onMouseHoverAtLeft() { + mHandler.removeCallbacks(mOnSwipeFromLeft); + mHandler.postDelayed(mOnSwipeFromLeft, MOUSE_GESTURE_DELAY_MS); + } - @Override - public void onMouseHoverAtRight() { - mHandler.removeCallbacks(mOnSwipeFromRight); - mHandler.postDelayed(mOnSwipeFromRight, MOUSE_GESTURE_DELAY_MS); - } + @Override + public void onMouseHoverAtTop() { + mHandler.removeCallbacks(mOnSwipeFromTop); + mHandler.postDelayed(mOnSwipeFromTop, MOUSE_GESTURE_DELAY_MS); + } - @Override - public void onMouseHoverAtBottom() { - mHandler.removeCallbacks(mOnSwipeFromBottom); - mHandler.postDelayed(mOnSwipeFromBottom, MOUSE_GESTURE_DELAY_MS); - } + @Override + public void onMouseHoverAtRight() { + mHandler.removeCallbacks(mOnSwipeFromRight); + mHandler.postDelayed(mOnSwipeFromRight, MOUSE_GESTURE_DELAY_MS); + } - @Override - public void onMouseLeaveFromLeft() { - mHandler.removeCallbacks(mOnSwipeFromLeft); - } + @Override + public void onMouseHoverAtBottom() { + mHandler.removeCallbacks(mOnSwipeFromBottom); + mHandler.postDelayed(mOnSwipeFromBottom, MOUSE_GESTURE_DELAY_MS); + } - @Override - public void onMouseLeaveFromTop() { - mHandler.removeCallbacks(mOnSwipeFromTop); - } + @Override + public void onMouseLeaveFromLeft() { + mHandler.removeCallbacks(mOnSwipeFromLeft); + } - @Override - public void onMouseLeaveFromRight() { - mHandler.removeCallbacks(mOnSwipeFromRight); - } + @Override + public void onMouseLeaveFromTop() { + mHandler.removeCallbacks(mOnSwipeFromTop); + } - @Override - public void onMouseLeaveFromBottom() { - mHandler.removeCallbacks(mOnSwipeFromBottom); - } - }); - displayContent.registerPointerEventListener(mSystemGestures); + @Override + public void onMouseLeaveFromRight() { + mHandler.removeCallbacks(mOnSwipeFromRight); + } + + @Override + public void onMouseLeaveFromBottom() { + mHandler.removeCallbacks(mOnSwipeFromBottom); + } + }; + mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler, + gesturesPointerEventCallbacks); + displayContent.registerPointerEventListener(mSystemGestures); + } mAppTransitionListener = new WindowManagerInternal.AppTransitionListener() { private Runnable mAppTransitionPending = () -> { @@ -648,7 +651,9 @@ public class DisplayPolicy { mContext, () -> { synchronized (mLock) { onConfigurationChanged(); - mSystemGestures.onConfigurationChanged(); + if (!CLIENT_TRANSIENT) { + mSystemGestures.onConfigurationChanged(); + } mDisplayContent.updateSystemGestureExclusion(); } }); @@ -670,7 +675,9 @@ public class DisplayPolicy { } void systemReady() { - mSystemGestures.systemReady(); + if (!CLIENT_TRANSIENT) { + mSystemGestures.systemReady(); + } if (mService.mPointerLocationEnabled) { setPointerLocationEnabled(true); } @@ -707,10 +714,6 @@ public class DisplayPolicy { return mDeskDockEnablesAccelerometer; } - boolean isDeskDockRespectsNoSensorAndLockedWithoutAccelerometer() { - return mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer; - } - public void setPersistentVrModeEnabled(boolean persistentVrModeEnabled) { mPersistentVrModeEnabled = persistentVrModeEnabled; } @@ -1315,7 +1318,9 @@ public class DisplayPolicy { } void onDisplayInfoChanged(DisplayInfo info) { - mSystemGestures.onDisplayInfoChanged(info); + if (!CLIENT_TRANSIENT) { + mSystemGestures.onDisplayInfoChanged(info); + } } /** @@ -1688,7 +1693,9 @@ public class DisplayPolicy { // Update the latest display size, cutout. mDisplayContent.updateDisplayInfo(); onConfigurationChanged(); - mSystemGestures.onConfigurationChanged(); + if (!CLIENT_TRANSIENT) { + mSystemGestures.onConfigurationChanged(); + } } /** @@ -1967,6 +1974,9 @@ public class DisplayPolicy { @VisibleForTesting void requestTransientBars(WindowState swipeTarget, boolean isGestureOnSystemBar) { + if (CLIENT_TRANSIENT) { + return; + } if (swipeTarget == null || !mService.mPolicy.isUserSetupComplete()) { // Swipe-up for navigation bar is disabled during setup return; @@ -2514,8 +2524,6 @@ public class DisplayPolicy { pw.print("mCarDockEnablesAccelerometer="); pw.print(mCarDockEnablesAccelerometer); pw.print(" mDeskDockEnablesAccelerometer="); pw.println(mDeskDockEnablesAccelerometer); - pw.print(" mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer="); - pw.println(mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer); pw.print(prefix); pw.print("mDockMode="); pw.print(Intent.dockStateToString(mDockMode)); pw.print(" mLidState="); pw.println(WindowManagerFuncs.lidStateToString(mLidState)); pw.print(prefix); pw.print("mAwake="); pw.print(mAwake); @@ -2617,7 +2625,9 @@ public class DisplayPolicy { final DecorInsets.Info info = mDecorInsets.mInfoForRotation[rotation]; pw.println(prefixInner + Surface.rotationToString(rotation) + "=" + info); } - mSystemGestures.dump(pw, prefix); + if (!CLIENT_TRANSIENT) { + mSystemGestures.dump(pw, prefix); + } pw.print(prefix); pw.println("Looper state:"); mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + " "); diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index a3d233df7b61..87de0f6e4aa0 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -1239,10 +1239,6 @@ public class DisplayRotation { mDisplayPolicy.isCarDockEnablesAccelerometer(); final boolean deskDockEnablesAccelerometer = mDisplayPolicy.isDeskDockEnablesAccelerometer(); - final boolean deskDockRespectsNoSensorAndLockedWithoutAccelerometer = - mDisplayPolicy.isDeskDockRespectsNoSensorAndLockedWithoutAccelerometer() - && (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED - || orientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); @Surface.Rotation final int preferredRotation; @@ -1263,10 +1259,10 @@ public class DisplayRotation { || dockMode == Intent.EXTRA_DOCK_STATE_LE_DESK || dockMode == Intent.EXTRA_DOCK_STATE_HE_DESK) && (deskDockEnablesAccelerometer || mDeskDockRotation >= 0) - && !deskDockRespectsNoSensorAndLockedWithoutAccelerometer) { + && !(orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED + || orientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR)) { // Ignore sensor when in desk dock unless explicitly enabled. - // This case can override the behavior of NOSENSOR, and can also - // enable 180 degree rotation while docked. + // This case can enable 180 degree rotation while docked. preferredRotation = deskDockEnablesAccelerometer ? sensorRotation : mDeskDockRotation; } else if (hdmiPlugged && mDemoHdmiRotationLock) { // Ignore sensor when plugged into HDMI when demo HDMI rotation lock enabled. diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index fc99f4c9eef7..0288e4bbb26e 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -832,13 +832,14 @@ final class LetterboxUiController { // Get the bounds of the "space-to-fill". The transformed bounds have the highest // priority because the activity is launched in a rotated environment. In multi-window - // mode, the task-level represents this. In fullscreen-mode, the task container does + // mode, the taskFragment-level represents this for both split-screen + // and activity-embedding. In fullscreen-mode, the task container does // (since the orientation letterbox is also applied to the task). final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds(); final Rect spaceToFill = transformedBounds != null ? transformedBounds : mActivityRecord.inMultiWindowMode() - ? mActivityRecord.getTask().getBounds() + ? mActivityRecord.getTaskFragment().getBounds() : mActivityRecord.getRootTask().getParent().getBounds(); // In case of translucent activities an option is to use the WindowState#getFrame() of // the first opaque activity beneath. In some cases (e.g. an opaque activity is using diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 50bf38b8ce16..bfd0d96a04ab 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -314,7 +314,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mLogger.mCreateWallTimeMs = System.currentTimeMillis(); mLogger.mCreateTimeNs = SystemClock.elapsedRealtimeNanos(); - controller.mTransitionTracer.logState(this); } @Nullable @@ -532,7 +531,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mLogger.mSyncId = mSyncId; mLogger.mCollectTimeNs = SystemClock.elapsedRealtimeNanos(); - mController.mTransitionTracer.logState(this); } /** @@ -555,7 +553,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { applyReady(); mLogger.mStartTimeNs = SystemClock.elapsedRealtimeNanos(); - mController.mTransitionTracer.logState(this); mController.updateAnimatingState(mTmpTransaction); // merge into the next-time the global transaction is applied. This is too-early to set @@ -1232,7 +1229,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { validateVisibility(); mState = STATE_FINISHED; - mController.mTransitionTracer.logState(this); // Rotation change may be deferred while there is a display change transition, so check // again in case there is a new pending change. if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) { @@ -1261,6 +1257,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId); mState = STATE_ABORT; + mLogger.mAbortTimeNs = SystemClock.elapsedRealtimeNanos(); mController.mTransitionTracer.logAbortedTransition(this); // Syncengine abort will call through to onTransactionReady() mSyncEngine.abort(mSyncId); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index c9316bf6e972..b697ab158003 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -941,7 +941,6 @@ class TransitionController { } mPlayingTransitions.add(transition); updateRunningRemoteAnimation(transition, true /* isPlaying */); - mTransitionTracer.logState(transition); // Sync engine should become idle after this, so the idle listener will check the queue. } @@ -1122,7 +1121,6 @@ class TransitionController { mLatestOnTopTasksReported.clear(); } } - mTransitionTracer.logState(transition); // This is called during Transition.abort whose codepath will eventually check the queue // via sync-engine idle. } @@ -1416,12 +1414,11 @@ class TransitionController { long mReadyTimeNs; long mSendTimeNs; long mFinishTimeNs; + long mAbortTimeNs; TransitionRequestInfo mRequest; WindowContainerTransaction mStartWCT; int mSyncId; TransitionInfo mInfo; - ProtoOutputStream mProtoOutputStream = new ProtoOutputStream(); - long mProtoToken; private String buildOnSendLog() { StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId) diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java index 6aac81bc3b6c..afc14926f63c 100644 --- a/services/core/java/com/android/server/wm/TransitionTracer.java +++ b/services/core/java/com/android/server/wm/TransitionTracer.java @@ -50,10 +50,11 @@ public class TransitionTracer { private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB static final String WINSCOPE_EXT = ".winscope"; - private static final String TRACE_FILE = "/data/misc/wmtrace/transition_trace" + WINSCOPE_EXT; + private static final String TRACE_FILE = + "/data/misc/wmtrace/wm_transition_trace" + WINSCOPE_EXT; private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; - private final TransitionTraceBuffer mTraceBuffer = new TransitionTraceBuffer(); + private final TraceBuffer mTraceBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY); private final Object mEnabledLock = new Object(); private volatile boolean mActiveTracingEnabled = false; @@ -72,18 +73,22 @@ public class TransitionTracer { */ public void logSentTransition(Transition transition, ArrayList<ChangeInfo> targets, TransitionInfo info) { - // Dump the info to proto that will not be available when the transition finishes or - // is canceled - final ProtoOutputStream outputStream = transition.mLogger.mProtoOutputStream; - transition.mLogger.mProtoToken = outputStream - .start(com.android.server.wm.shell.TransitionTraceProto.FINISHED_TRANSITIONS); + final ProtoOutputStream outputStream = new ProtoOutputStream(); + final long protoToken = outputStream + .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS); + outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId()); + outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS, + transition.mLogger.mCreateTimeNs); + outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS, + transition.mLogger.mSendTimeNs); outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID, transition.getStartTransaction().getId()); outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID, transition.getFinishTransaction().getId()); dumpTransitionTargetsToProto(outputStream, transition, targets); + outputStream.end(protoToken); - logTransitionInfo(transition, info); + mTraceBuffer.add(outputStream); } /** @@ -93,17 +98,15 @@ public class TransitionTracer { * @param transition The transition that has finished. */ public void logFinishedTransition(Transition transition) { - if (transition.mLogger.mProtoToken == 0) { - // Transition finished but never sent, so open token never added - final ProtoOutputStream outputStream = transition.mLogger.mProtoOutputStream; - transition.mLogger.mProtoToken = outputStream - .start(com.android.server.wm.shell.TransitionTraceProto.FINISHED_TRANSITIONS); - } + final ProtoOutputStream outputStream = new ProtoOutputStream(); + final long protoToken = outputStream + .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS); + outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId()); + outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS, + transition.mLogger.mFinishTimeNs); + outputStream.end(protoToken); - // Dump the rest of the transition's info that wasn't dumped during logSentTransition - dumpFinishedTransitionToProto(transition.mLogger.mProtoOutputStream, transition); - transition.mLogger.mProtoOutputStream.end(transition.mLogger.mProtoToken); - mTraceBuffer.pushTransitionProto(transition.mLogger.mProtoOutputStream); + mTraceBuffer.add(outputStream); } /** @@ -113,38 +116,15 @@ public class TransitionTracer { * @param transition The transition that has been aborted */ public void logAbortedTransition(Transition transition) { - // We don't care about aborted transitions unless actively tracing - if (!mActiveTracingEnabled) { - return; - } - logFinishedTransition(transition); - } - - /** - * Records the current state of a transition in the transition trace (if it is running). - * @param transition the transition that we want to record the state of. - */ - public void logState(com.android.server.wm.Transition transition) { - if (!mActiveTracingEnabled) { - return; - } final ProtoOutputStream outputStream = new ProtoOutputStream(); - dumpTransitionStateToProto(outputStream, transition); - mTraceBuffer.pushTransitionState(outputStream); - } - - /** - * Records the transition info that is being sent over to Shell. - * @param transition The transition the info is associated with. - * @param info The transition info we want to log. - */ - private void logTransitionInfo(Transition transition, TransitionInfo info) { - if (!mActiveTracingEnabled) { - return; - } - final ProtoOutputStream outputStream = new ProtoOutputStream(); - dumpTransitionInfoToProto(outputStream, transition, info); - mTraceBuffer.pushTransitionInfo(outputStream); + final long protoToken = outputStream + .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS); + outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId()); + outputStream.write(com.android.server.wm.shell.Transition.ABORT_TIME_NS, + transition.mLogger.mAbortTimeNs); + outputStream.end(protoToken); + + mTraceBuffer.add(outputStream); } private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream, @@ -189,139 +169,6 @@ public class TransitionTracer { Trace.endSection(); } - private void dumpFinishedTransitionToProto( - ProtoOutputStream outputStream, - Transition transition - ) { - Trace.beginSection("TransitionTracer#dumpFinishedTransitionToProto"); - - outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS, - transition.mLogger.mCreateTimeNs); - outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS, - transition.mLogger.mSendTimeNs); - outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS, - transition.mLogger.mFinishTimeNs); - - Trace.endSection(); - } - - private void dumpTransitionStateToProto(ProtoOutputStream outputStream, Transition transition) { - Trace.beginSection("TransitionTracer#dumpTransitionStateToProto"); - - final long stateToken = outputStream - .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_STATES); - - outputStream.write(com.android.server.wm.shell.TransitionState.TIME_NS, - SystemClock.elapsedRealtimeNanos()); - outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_ID, - transition.getSyncId()); - outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_TYPE, - transition.mType); - outputStream.write(com.android.server.wm.shell.TransitionState.STATE, - transition.getState()); - outputStream.write(com.android.server.wm.shell.TransitionState.FLAGS, - transition.getFlags()); - - for (int i = 0; i < transition.mChanges.size(); ++i) { - final WindowContainer window = transition.mChanges.keyAt(i); - final ChangeInfo changeInfo = transition.mChanges.valueAt(i); - dumpChangeInfoToProto(outputStream, window, changeInfo); - } - - for (int i = 0; i < transition.mParticipants.size(); ++i) { - final WindowContainer window = transition.mParticipants.valueAt(i); - window.writeIdentifierToProto(outputStream, - com.android.server.wm.shell.TransitionState.PARTICIPANTS); - } - - outputStream.end(stateToken); - Trace.endSection(); - } - - private void dumpChangeInfoToProto(ProtoOutputStream outputStream, WindowContainer window, - ChangeInfo changeInfo) { - Trace.beginSection("TransitionTraceBuffer#writeChange"); - final long changeEntryToken = - outputStream.start(com.android.server.wm.shell.TransitionState.CHANGE); - - final int transitMode = changeInfo.getTransitMode(window); - final boolean hasChanged = changeInfo.hasChanged(); - final int changeFlags = changeInfo.getChangeFlags(window); - final int windowingMode = changeInfo.mWindowingMode; - - outputStream.write(com.android.server.wm.shell.ChangeInfo.TRANSIT_MODE, transitMode); - outputStream.write(com.android.server.wm.shell.ChangeInfo.HAS_CHANGED, hasChanged); - outputStream.write(com.android.server.wm.shell.ChangeInfo.CHANGE_FLAGS, changeFlags); - outputStream.write(com.android.server.wm.shell.ChangeInfo.WINDOWING_MODE, windowingMode); - window.writeIdentifierToProto( - outputStream, com.android.server.wm.shell.ChangeInfo.WINDOW_IDENTIFIER); - - outputStream.end(changeEntryToken); - Trace.endSection(); - } - - private void dumpTransitionInfoToProto(ProtoOutputStream outputStream, - Transition transition, TransitionInfo info) { - Trace.beginSection("TransitionTracer#dumpTransitionInfoToProto"); - final long transitionInfoToken = outputStream - .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_INFO); - - outputStream.write(com.android.server.wm.shell.TransitionInfo.TRANSITION_ID, - transition.getSyncId()); - for (int i = 0; i < info.getChanges().size(); ++i) { - TransitionInfo.Change change = info.getChanges().get(i); - dumpTransitionInfoChangeToProto(outputStream, change); - } - - outputStream.end(transitionInfoToken); - Trace.endSection(); - } - - private void dumpTransitionInfoChangeToProto( - ProtoOutputStream outputStream, - TransitionInfo.Change change - ) { - Trace.beginSection("TransitionTracer#dumpTransitionInfoChangeToProto"); - final long changeEntryToken = outputStream - .start(com.android.server.wm.shell.TransitionInfo.CHANGE); - - outputStream.write(com.android.server.wm.shell.TransitionInfoChange.LAYER_ID, - change.getLeash().getLayerId()); - outputStream.write(com.android.server.wm.shell.TransitionInfoChange.MODE, change.getMode()); - - outputStream.end(changeEntryToken); - Trace.endSection(); - } - - private class TransitionTraceBuffer { - private final TraceBuffer mTransitionBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY); - private final TraceBuffer mStateBuffer = new TraceBuffer(ACTIVE_TRACING_BUFFER_CAPACITY); - private final TraceBuffer mTransitionInfoBuffer = - new TraceBuffer(ACTIVE_TRACING_BUFFER_CAPACITY); - - private void pushTransitionProto(ProtoOutputStream outputStream) { - mTransitionBuffer.add(outputStream); - } - - private void pushTransitionState(ProtoOutputStream outputStream) { - mStateBuffer.add(outputStream); - } - - private void pushTransitionInfo(ProtoOutputStream outputStream) { - mTransitionInfoBuffer.add(outputStream); - } - - public void writeToFile(File file, ProtoOutputStream proto) throws IOException { - mTransitionBuffer.writeTraceToFile(file, proto); - } - - public void reset() { - mTransitionBuffer.resetBuffer(); - mStateBuffer.resetBuffer(); - mTransitionInfoBuffer.resetBuffer(); - } - } - /** * Starts collecting transitions for the trace. * If called while a trace is already running, this will reset the trace. @@ -335,8 +182,8 @@ public class TransitionTracer { LogAndPrintln.i(pw, "Starting shell transition trace."); synchronized (mEnabledLock) { mActiveTracingEnabled = true; - mTraceBuffer.mTransitionBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY); - mTraceBuffer.reset(); + mTraceBuffer.resetBuffer(); + mTraceBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY); } Trace.endSection(); } @@ -364,8 +211,8 @@ public class TransitionTracer { synchronized (mEnabledLock) { mActiveTracingEnabled = false; writeTraceToFileLocked(pw, outputFile); - mTraceBuffer.reset(); - mTraceBuffer.mTransitionBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY); + mTraceBuffer.resetBuffer(); + mTraceBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY); } Trace.endSection(); } @@ -404,7 +251,7 @@ public class TransitionTracer { int pid = android.os.Process.myPid(); LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath() + " from process " + pid); - mTraceBuffer.writeToFile(file, proto); + mTraceBuffer.writeTraceToFile(file, proto); } catch (IOException e) { LogAndPrintln.e(pw, "Unable to write buffer to file", e); } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 3ccf183920d3..829a33d4bfb7 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -3592,11 +3592,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< && !mTransitionController.useShellTransitionsRotation()) { if (deltaRotation != Surface.ROTATION_0) { updateSurfaceRotation(t, deltaRotation, null /* positionLeash */); - t.setFixedTransformHint(mSurfaceControl, + getPendingTransaction().setFixedTransformHint(mSurfaceControl, getWindowConfiguration().getDisplayRotation()); } else if (deltaRotation != mLastDeltaRotation) { t.setMatrix(mSurfaceControl, 1, 0, 0, 1); - t.unsetFixedTransformHint(mSurfaceControl); + getPendingTransaction().unsetFixedTransformHint(mSurfaceControl); } } mLastDeltaRotation = deltaRotation; 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/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 4c5efef795d8..31afcbf26220 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -596,7 +596,8 @@ class WindowToken extends WindowContainer<WindowState> { .build(); t.setPosition(leash, mLastSurfacePosition.x, mLastSurfacePosition.y); t.reparent(getSurfaceControl(), leash); - t.setFixedTransformHint(leash, getWindowConfiguration().getDisplayRotation()); + getPendingTransaction().setFixedTransformHint(leash, + getWindowConfiguration().getDisplayRotation()); mFixedRotationTransformLeash = leash; updateSurfaceRotation(t, rotation, mFixedRotationTransformLeash); return mFixedRotationTransformLeash; diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java index 04ecd6ebd2d1..e3d4c224f1fd 100644 --- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java @@ -19,6 +19,7 @@ package com.android.server.credentials; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.credentials.ClearCredentialStateException; import android.credentials.ClearCredentialStateRequest; import android.credentials.CredentialProviderInfo; import android.credentials.IClearCredentialStateCallback; @@ -29,6 +30,8 @@ import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.util.Slog; +import com.android.server.credentials.metrics.ProviderSessionMetric; + import java.util.ArrayList; import java.util.Set; @@ -92,9 +95,12 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta public void onFinalResponseReceived( ComponentName componentName, Void response) { - mRequestSessionMetric.collectChosenMetricViaCandidateTransfer( - mProviders.get(componentName.flattenToString()).mProviderSessionMetric - .getCandidatePhasePerProviderMetric()); + if (mProviders.get(componentName.flattenToString()) != null) { + ProviderSessionMetric providerSessionMetric = + mProviders.get(componentName.flattenToString()).mProviderSessionMetric; + mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric + .getCandidatePhasePerProviderMetric()); + } respondToClientWithResponseAndFinish(null); } @@ -141,8 +147,9 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta return; } } - // TODO: Replace with properly defined error type - respondToClientWithErrorAndFinish("UNKNOWN", "All providers failed"); + String exception = ClearCredentialStateException.TYPE_UNKNOWN; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "All providers failed"); } @Override diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index 0af56470965c..2d6e74f0ccf1 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -35,6 +35,7 @@ import android.service.credentials.CallingAppInfo; import android.service.credentials.PermissionUtils; import android.util.Slog; +import com.android.server.credentials.metrics.ProviderSessionMetric; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -131,9 +132,12 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Nullable CreateCredentialResponse response) { Slog.i(TAG, "Final credential received from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); - mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get( - componentName.flattenToString()).mProviderSessionMetric - .getCandidatePhasePerProviderMetric()); + if (mProviders.get(componentName.flattenToString()) != null) { + ProviderSessionMetric providerSessionMetric = + mProviders.get(componentName.flattenToString()).mProviderSessionMetric; + mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric + .getCandidatePhasePerProviderMetric()); + } if (response != null) { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); @@ -141,7 +145,9 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR } else { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, + String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "Invalid response"); } } @@ -154,18 +160,21 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Override public void onUiCancellation(boolean isUserCancellation) { - if (isUserCancellation) { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_USER_CANCELED, - "User cancelled the selector"); - } else { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_INTERRUPTED, - "The UI was interrupted - please try again."); + String exception = CreateCredentialException.TYPE_USER_CANCELED; + String message = "User cancelled the selector"; + if (!isUserCancellation) { + exception = CreateCredentialException.TYPE_INTERRUPTED; + message = "The UI was interrupted - please try again."; } + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, message); } @Override public void onUiSelectorInvocationFailure() { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, + String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No create options available."); } @@ -181,7 +190,9 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR Slog.i(TAG, "Provider status changed - ui invocation is needed"); getProviderDataAndInitiateUi(); } else { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, + String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No create options available."); } } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 8728c3c64715..86dbe11d5ddc 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -482,7 +482,7 @@ public final class CredentialManagerService callback, request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), - getEnabledProviders(), + getEnabledProvidersForUser(userId), CancellationSignal.fromTransport(cancelTransport), timestampBegan); addSessionLocked(userId, session); @@ -537,7 +537,7 @@ public final class CredentialManagerService getCredentialCallback, request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), - getEnabledProviders(), + getEnabledProvidersForUser(userId), CancellationSignal.fromTransport(cancelTransport), timestampBegan, prepareGetCredentialCallback); @@ -655,7 +655,7 @@ public final class CredentialManagerService request, callback, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), - getEnabledProviders(), + getEnabledProvidersForUser(userId), getPrimaryProvidersForUserId(getContext(), userId), CancellationSignal.fromTransport(cancelTransport), timestampBegan); @@ -726,10 +726,13 @@ public final class CredentialManagerService "setEnabledProviders", null); + Set<String> enableProvider = new HashSet<>(providers); + enableProvider.addAll(primaryProviders); + boolean writeEnabledStatus = Settings.Secure.putStringForUser(getContext().getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE, - String.join(":", providers), + String.join(":", enableProvider), userId); boolean writePrimaryStatus = @@ -804,7 +807,7 @@ public final class CredentialManagerService verifyGetProvidersPermission(); return CredentialProviderInfoFactory.getCredentialProviderServices( - mContext, userId, providerFilter, getEnabledProviders(), + mContext, userId, providerFilter, getEnabledProvidersForUser(userId), getPrimaryProvidersForUserId(mContext, userId)); } @@ -815,7 +818,7 @@ public final class CredentialManagerService final int userId = UserHandle.getCallingUserId(); return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting( - mContext, userId, providerFilter, getEnabledProviders(), + mContext, userId, providerFilter, getEnabledProvidersForUser(userId), getPrimaryProvidersForUserId(mContext, userId)); } @@ -832,24 +835,26 @@ public final class CredentialManagerService } } - @SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same - // this.mLock - private Set<ComponentName> getEnabledProviders() { + private Set<ComponentName> getEnabledProvidersForUser(int userId) { + final int resolvedUserId = ActivityManager.handleIncomingUser( + Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, false, + "getEnabledProvidersForUser", null); + Set<ComponentName> enabledProviders = new HashSet<>(); - synchronized (mLock) { - runForUser( - (service) -> { - try { - enabledProviders.add( - service.getCredentialProviderInfo() - .getServiceInfo().getComponentName()); - } catch (NullPointerException e) { - // Safe check - Slog.e(TAG, "Skipping provider as either the providerInfo" - + " or serviceInfo is null - weird"); - } - }); + String directValue = Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE, resolvedUserId); + + if (!TextUtils.isEmpty(directValue)) { + String[] components = directValue.split(":"); + for (String componentString : components) { + ComponentName component = ComponentName.unflattenFromString(componentString); + if (component != null) { + enabledProviders.add(component); + } + } } + return enabledProviders; } @@ -879,7 +884,7 @@ public final class CredentialManagerService callback, request, constructCallingAppInfo(callingPackage, userId, null), - getEnabledProviders(), + getEnabledProvidersForUser(userId), CancellationSignal.fromTransport(cancelTransport), timestampBegan); addSessionLocked(userId, session); diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index 15034104b5e0..9eb3b3b5b7cd 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -19,6 +19,7 @@ package com.android.server.credentials; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.credentials.CredentialOption; import android.credentials.CredentialProviderInfo; import android.credentials.GetCredentialException; import android.credentials.GetCredentialRequest; @@ -31,6 +32,7 @@ import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.util.Slog; +import com.android.server.credentials.metrics.ProviderSessionMetric; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -52,11 +54,22 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, CancellationSignal cancellationSignal, long startedTimestamp) { super(context, sessionCallback, lock, userId, callingUid, request, callback, - RequestInfo.TYPE_GET, callingAppInfo, enabledProviders, cancellationSignal, - startedTimestamp); + getRequestInfoFromRequest(request), callingAppInfo, enabledProviders, + cancellationSignal, startedTimestamp); mRequestSessionMetric.collectGetFlowInitialMetricInfo(request); } + private static String getRequestInfoFromRequest(GetCredentialRequest request) { + for (CredentialOption option : request.getCredentialOptions()) { + if (option.getCredentialRetrievalData().getStringArrayList( + CredentialOption + .SUPPORTED_ELEMENT_KEYS) != null) { + return RequestInfo.TYPE_GET_VIA_REGISTRY; + } + } + return RequestInfo.TYPE_GET; + } + /** * Creates a new provider session, and adds it list of providers that are contributing to * this session. @@ -94,8 +107,10 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, } catch (RemoteException e) { mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false); mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED); + String exception = GetCredentialException.TYPE_UNKNOWN; + mRequestSessionMetric.collectFrameworkException(exception); respondToClientWithErrorAndFinish( - GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector"); + exception, "Unable to instantiate selector"); } } @@ -116,9 +131,12 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Nullable GetCredentialResponse response) { Slog.i(TAG, "onFinalResponseReceived from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); - mRequestSessionMetric.collectChosenMetricViaCandidateTransfer( - mProviders.get(componentName.flattenToString()) - .mProviderSessionMetric.getCandidatePhasePerProviderMetric()); + if (mProviders.get(componentName.flattenToString()) != null) { + ProviderSessionMetric providerSessionMetric = + mProviders.get(componentName.flattenToString()).mProviderSessionMetric; + mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric + .getCandidatePhasePerProviderMetric()); + } if (response != null) { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); @@ -126,7 +144,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, } else { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "Invalid response from provider"); } } @@ -140,18 +160,21 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override public void onUiCancellation(boolean isUserCancellation) { - if (isUserCancellation) { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_USER_CANCELED, - "User cancelled the selector"); - } else { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_INTERRUPTED, - "The UI was interrupted - please try again."); + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + String message = "User cancelled the selector"; + if (!isUserCancellation) { + exception = GetCredentialException.TYPE_INTERRUPTED; + message = "The UI was interrupted - please try again."; } + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, message); } @Override public void onUiSelectorInvocationFailure() { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No credentials available."); } @@ -175,7 +198,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, Slog.i(TAG, "Provider status changed - ui invocation is needed"); getProviderDataAndInitiateUi(); } else { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No credentials available"); } } @@ -196,7 +221,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, // Respond to client if all auth entries are empty and nothing else to show on the UI if (providerDataContainsEmptyAuthEntriesOnly()) { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No credentials available"); } } diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index e4c6b3a10dd8..f9c44a94f89b 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -24,12 +24,14 @@ import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.CandidateAggregateMetric; import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric; import com.android.server.credentials.metrics.CandidatePhaseMetric; import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric; import com.android.server.credentials.metrics.EntryEnum; import com.android.server.credentials.metrics.InitialPhaseMetric; +import java.security.SecureRandom; import java.util.List; import java.util.Map; @@ -48,12 +50,15 @@ public class MetricUtilities { public static final String DEFAULT_STRING = ""; public static final int[] DEFAULT_REPEATED_INT_32 = new int[0]; public static final String[] DEFAULT_REPEATED_STR = new String[0]; + public static final boolean[] DEFAULT_REPEATED_BOOL = new boolean[0]; // Used for single count metric emits, such as singular amounts of various types public static final int UNIT = 1; // Used for zero count metric emits, such as zero amounts of various types public static final int ZERO = 0; // The number of characters at the end of the string to use as a key - public static final int DELTA_CUT = 20; + public static final int DELTA_RESPONSES_CUT = 20; + // The cut for exception strings from the end - used to keep metrics small + public static final int DELTA_EXCEPTION_CUT = 30; /** * This retrieves the uid of any package name, given a context and a component name for the @@ -76,6 +81,15 @@ public class MetricUtilities { } /** + * Used to help generate random sequences for local sessions, in the time-scale of credential + * manager flows. + * @return a high entropy int useful to use in reasonable time-frame sessions. + */ + public static int getHighlyUniqueInteger() { + return new SecureRandom().nextInt(); + } + + /** * Given any two timestamps in nanoseconds, this gets the difference and converts to * milliseconds. Assumes the difference is not larger than the maximum int size. * @@ -127,7 +141,7 @@ public class MetricUtilities { index++; } FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED, - /* session_id */ finalPhaseMetric.getSessionId(), + /* session_id */ finalPhaseMetric.getSessionIdProvider(), /* sequence_num */ emitSequenceId, /* ui_returned_final_start */ finalPhaseMetric.isUiReturned(), /* chosen_provider_uid */ finalPhaseMetric.getChosenUid(), @@ -165,8 +179,9 @@ public class MetricUtilities { finalPhaseMetric.getResponseCollective().getUniqueResponseStrings(), /* per_classtype_counts */ finalPhaseMetric.getResponseCollective().getUniqueResponseCounts(), - /* framework_exception_unique_classtypes */ - DEFAULT_STRING + /* framework_exception_unique_classtype */ + finalPhaseMetric.getFrameworkException(), + /* primary_indicated */ false ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during final provider uid emit: " + e); @@ -210,7 +225,7 @@ public class MetricUtilities { CandidatePhaseMetric metric = session.mProviderSessionMetric .getCandidatePhasePerProviderMetric(); if (sessionId == -1) { - sessionId = metric.getSessionId(); + sessionId = metric.getSessionIdProvider(); } if (!queryReturned) { queryReturned = metric.isQueryReturned(); @@ -268,7 +283,9 @@ public class MetricUtilities { /* per_classtype_counts */ initialPhaseMetric.getUniqueRequestCounts(), /* api_name */ - initialPhaseMetric.getApiName() + initialPhaseMetric.getApiName(), + /* primary_candidates_indicated */ + DEFAULT_REPEATED_BOOL ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during candidate provider uid metric emit: " + e); @@ -312,7 +329,7 @@ public class MetricUtilities { FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_INIT_PHASE_REPORTED, /* api_name */ initialPhaseMetric.getApiName(), /* caller_uid */ initialPhaseMetric.getCallerUid(), - /* session_id */ initialPhaseMetric.getSessionId(), + /* session_id */ initialPhaseMetric.getSessionIdCaller(), /* sequence_num */ sequenceNum, /* initial_timestamp_reference_nanoseconds */ initialPhaseMetric.getCredentialServiceStartedTimeNanoseconds(), @@ -329,4 +346,129 @@ public class MetricUtilities { Slog.w(TAG, "Unexpected error during initial metric emit: " + e); } } + + /** + * A logging utility focused on track 1, where the calling app is known. This captures all + * aggregate information for the candidate phase. + * + * @param candidateAggregateMetric the aggregate candidate metric information collected + * @param sequenceNum the sequence number for this api call session emit + */ + public static void logApiCalledAggregateCandidate( + CandidateAggregateMetric candidateAggregateMetric, + int sequenceNum) { + try { + if (!LOG_FLAG) { + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_TOTAL_REPORTED, + /*session_id*/ candidateAggregateMetric.getSessionIdProvider(), + /*sequence_num*/ sequenceNum, + /*query_returned*/ candidateAggregateMetric.isQueryReturned(), + /*num_providers*/ candidateAggregateMetric.getNumProviders(), + /*min_query_start_timestamp_microseconds*/ + DEFAULT_INT_32, + /*max_query_end_timestamp_microseconds*/ + DEFAULT_INT_32, + /*query_response_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*query_per_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*query_unique_entries*/ + DEFAULT_REPEATED_INT_32, + /*query_per_entry_counts*/ + DEFAULT_REPEATED_INT_32, + /*query_total_candidate_failure*/ + DEFAULT_INT_32, + /*query_framework_exception_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*query_per_exception_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*auth_response_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*auth_per_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*auth_unique_entries*/ + DEFAULT_REPEATED_INT_32, + /*auth_per_entry_counts*/ + DEFAULT_REPEATED_INT_32, + /*auth_total_candidate_failure*/ + DEFAULT_INT_32, + /*auth_framework_exception_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*auth_per_exception_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*num_auth_clicks*/ + DEFAULT_INT_32, + /*auth_returned*/ false + ); + } + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during metric logging: " + e); + } + } + + /** + * A logging utility used primarily for the final phase of the current metric setup for track 1. + * + * @param finalPhaseMetric the coalesced data of the chosen provider + * @param browsingPhaseMetrics the coalesced data of the browsing phase + * @param apiStatus the final status of this particular api call + * @param emitSequenceId an emitted sequence id for the current session + */ + public static void logApiCalledNoUidFinal(ChosenProviderFinalPhaseMetric finalPhaseMetric, + List<CandidateBrowsingPhaseMetric> browsingPhaseMetrics, int apiStatus, + int emitSequenceId) { + try { + if (!LOG_FLAG) { + return; + } + int browsedSize = browsingPhaseMetrics.size(); + int[] browsedClickedEntries = new int[browsedSize]; + int[] browsedProviderUid = new int[browsedSize]; + int index = 0; + for (CandidateBrowsingPhaseMetric metric : browsingPhaseMetrics) { + browsedClickedEntries[index] = metric.getEntryEnum(); + browsedProviderUid[index] = metric.getProviderUid(); + index++; + } + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINALNOUID_REPORTED, + /* session_id */ finalPhaseMetric.getSessionIdCaller(), + /* sequence_num */ emitSequenceId, + /* ui_returned_final_start */ finalPhaseMetric.isUiReturned(), + /* chosen_provider_query_start_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getQueryStartTimeNanoseconds()), + /* chosen_provider_query_end_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getQueryEndTimeNanoseconds()), + /* chosen_provider_ui_invoked_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getUiCallStartTimeNanoseconds()), + /* chosen_provider_ui_finished_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getUiCallEndTimeNanoseconds()), + /* chosen_provider_finished_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getFinalFinishTimeNanoseconds()), + /* chosen_provider_status */ finalPhaseMetric.getChosenProviderStatus(), + /* chosen_provider_has_exception */ finalPhaseMetric.isHasException(), + /* unique_entries */ + finalPhaseMetric.getResponseCollective().getUniqueEntries(), + /* per_entry_counts */ + finalPhaseMetric.getResponseCollective().getUniqueEntryCounts(), + /* unique_response_classtypes */ + finalPhaseMetric.getResponseCollective().getUniqueResponseStrings(), + /* per_classtype_counts */ + finalPhaseMetric.getResponseCollective().getUniqueResponseCounts(), + /* framework_exception_unique_classtype */ + finalPhaseMetric.getFrameworkException(), + /* clicked_entries */ browsedClickedEntries, + /* provider_of_clicked_entry */ browsedProviderUid, + /* api_status */ apiStatus, + /* primary_indicated */ false + ); + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during metric logging: " + e); + } + } + } diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java index 1f0346a2b9d6..d4b88001111e 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java @@ -132,7 +132,8 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS protected void invokeSession() { if (mRemoteCredentialService != null) { startCandidateMetrics(); - mRemoteCredentialService.onClearCredentialState(mProviderRequest, this); + mRemoteCredentialService.setCallback(this); + mRemoteCredentialService.onClearCredentialState(mProviderRequest); } } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java index 16beaa4eb13e..25f20caee16d 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -193,11 +193,11 @@ public final class ProviderCreateSession extends ProviderSession< mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(), response.getRemoteCreateEntry()); if (mProviderResponseDataHandler.isEmptyResponse(response)) { - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } else { - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } @@ -248,7 +248,8 @@ public final class ProviderCreateSession extends ProviderSession< protected void invokeSession() { if (mRemoteCredentialService != null) { startCandidateMetrics(); - mRemoteCredentialService.onBeginCreateCredential(mProviderRequest, this); + mRemoteCredentialService.setCallback(this); + mRemoteCredentialService.onBeginCreateCredential(mProviderRequest); } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 1e80703378e0..51af25b58992 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -309,7 +309,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential protected void invokeSession() { if (mRemoteCredentialService != null) { startCandidateMetrics(); - mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this); + mRemoteCredentialService.setCallback(this); + mRemoteCredentialService.onBeginGetCredential(mProviderRequest); } } @@ -432,6 +433,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential BeginGetCredentialResponse response = PendingIntentResultHandler .extractResponseContent(providerPendingIntentResponse .getResultData()); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/true); if (response != null && !mProviderResponseDataHandler.isEmptyResponse(response)) { addToInitialRemoteResponse(response, /*isInitialResponse=*/ false); // Additional content received is in the form of new response content. @@ -469,12 +471,12 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential addToInitialRemoteResponse(response, /*isInitialResponse=*/true); // Log the data. if (mProviderResponseDataHandler.isEmptyResponse(response)) { - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE, /*source=*/ CredentialsSource.REMOTE_PROVIDER); return; } - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index 27b78a4b7b15..068ca7928117 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -71,7 +71,7 @@ public abstract class ProviderSession<T, R> @NonNull protected Boolean mProviderResponseSet = false; @NonNull - protected final ProviderSessionMetric mProviderSessionMetric = new ProviderSessionMetric(); + protected final ProviderSessionMetric mProviderSessionMetric; @NonNull private int mProviderSessionUid; @@ -114,6 +114,13 @@ public abstract class ProviderSession<T, R> } /** + * Gives access to the objects metric collectors. + */ + public ProviderSessionMetric getProviderSessionMetric() { + return this.mProviderSessionMetric; + } + + /** * Interface to be implemented by any class that wishes to get a callback when a particular * provider session's status changes. Typically, implemented by the {@link RequestSession} * class. @@ -147,6 +154,8 @@ public abstract class ProviderSession<T, R> mComponentName = componentName; mRemoteCredentialService = remoteCredentialService; mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName); + mProviderSessionMetric = new ProviderSessionMetric( + ((RequestSession) mCallbacks).mRequestSessionMetric.getSessionIdTrackTwo()); } /** Provider status at various states of the provider session. */ @@ -206,10 +215,10 @@ public abstract class ProviderSession<T, R> CredentialsSource source) { setStatus(status); mProviderSessionMetric.collectCandidateMetricUpdate(isTerminatingStatus(status), - isCompletionStatus(status), mProviderSessionUid); + isCompletionStatus(status), mProviderSessionUid, + source == CredentialsSource.AUTH_ENTRY); mCallbacks.onProviderStatusChanged(status, mComponentName, source); } - /** Common method that transfers metrics from the init phase to candidates */ protected void startCandidateMetrics() { mProviderSessionMetric.collectCandidateMetricSetupViaInitialMetric( diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java index f5e3b86213a1..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; /** @@ -59,13 +60,17 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr private static final String TAG = "RemoteCredentialService"; /** Timeout for a single request. */ - private static final long TIMEOUT_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; + private static final long TIMEOUT_REQUEST_MILLIS = 3 * DateUtils.SECOND_IN_MILLIS; /** Timeout to unbind after the task queue is empty. */ private static final long TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; private final ComponentName mComponentName; + private AtomicBoolean mOngoingRequest = new AtomicBoolean(false); + + @Nullable private ProviderCallbacks mCallback; + /** * Callbacks to be invoked when the provider remote service responds with a * success or failure. @@ -94,12 +99,35 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr mComponentName = componentName; } + public void setCallback(ProviderCallbacks callback) { + mCallback = callback; + } + /** Unbinds automatically after this amount of time. */ @Override protected long getAutoDisconnectTimeoutMs() { return TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS; } + @Override + public void onBindingDied(ComponentName name) { + super.onBindingDied(name); + + Slog.w(TAG, "binding died for: " + name); + } + + @Override + public void binderDied() { + super.binderDied(); + Slog.w(TAG, "binderDied"); + + if (mCallback != null) { + mOngoingRequest.set(false); + mCallback.onProviderServiceDied(this); + } + + } + /** Return the componentName of the service to be connected. */ @NonNull public ComponentName getComponentName() { @@ -116,11 +144,14 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * provider service. * * @param request the request to be sent to the provider - * @param callback the callback to be used to send back the provider response to the - * {@link ProviderGetSession} class that maintains provider state */ - public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request, - ProviderCallbacks<BeginGetCredentialResponse> callback) { + public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request) { + if (mCallback == null) { + Slog.w(TAG, "Callback is not set"); + return; + } + mOngoingRequest.set(true); + AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef = new AtomicReference<>(); @@ -154,7 +185,9 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr dispatchCancellationSignal(cancellation); } else { cancellationSink.set(cancellation); - callback.onProviderCancellable(cancellation); + if (mCallback != null) { + mCallback.onProviderCancellable(cancellation); + } } } }); @@ -166,7 +199,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> - handleExecutionResponse(result, error, cancellationSink, callback))); + handleExecutionResponse(result, error, cancellationSink))); } /** @@ -174,11 +207,14 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * provider service. * * @param request the request to be sent to the provider - * @param callback the callback to be used to send back the provider response to the - * {@link ProviderCreateSession} class that maintains provider state */ - public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request, - ProviderCallbacks<BeginCreateCredentialResponse> callback) { + public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request) { + if (mCallback == null) { + Slog.w(TAG, "Callback is not set"); + return; + } + mOngoingRequest.set(true); + AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef = new AtomicReference<>(); @@ -212,7 +248,9 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr dispatchCancellationSignal(cancellation); } else { cancellationSink.set(cancellation); - callback.onProviderCancellable(cancellation); + if (mCallback != null) { + mCallback.onProviderCancellable(cancellation); + } } } }); @@ -224,7 +262,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> - handleExecutionResponse(result, error, cancellationSink, callback))); + handleExecutionResponse(result, error, cancellationSink))); } /** @@ -232,11 +270,14 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * provider service. * * @param request the request to be sent to the provider - * @param callback the callback to be used to send back the provider response to the - * {@link ProviderClearSession} class that maintains provider state */ - public void onClearCredentialState(@NonNull ClearCredentialStateRequest request, - ProviderCallbacks<Void> callback) { + public void onClearCredentialState(@NonNull ClearCredentialStateRequest request) { + if (mCallback == null) { + Slog.w(TAG, "Callback is not set"); + return; + } + mOngoingRequest.set(true); + AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>(); @@ -269,7 +310,9 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr dispatchCancellationSignal(cancellation); } else { cancellationSink.set(cancellation); - callback.onProviderCancellable(cancellation); + if (mCallback != null) { + mCallback.onProviderCancellable(cancellation); + } } } }); @@ -281,40 +324,58 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> - handleExecutionResponse(result, error, cancellationSink, callback))); + handleExecutionResponse(result, error, cancellationSink))); } private <T> void handleExecutionResponse(T result, Throwable error, - AtomicReference<ICancellationSignal> cancellationSink, - ProviderCallbacks<T> callback) { + AtomicReference<ICancellationSignal> cancellationSink) { if (error == null) { - callback.onProviderResponseSuccess(result); + if (mCallback != null) { + mCallback.onProviderResponseSuccess(result); + } } else { if (error instanceof TimeoutException) { Slog.i(TAG, "Remote provider response timed tuo for: " + mComponentName); + if (!mOngoingRequest.get()) { + return; + } dispatchCancellationSignal(cancellationSink.get()); - callback.onProviderResponseFailure( - CredentialProviderErrors.ERROR_TIMEOUT, - null); + if (mCallback != null) { + mOngoingRequest.set(false); + mCallback.onProviderResponseFailure( + CredentialProviderErrors.ERROR_TIMEOUT, null); + } } else if (error instanceof CancellationException) { Slog.i(TAG, "Cancellation exception for remote provider: " + mComponentName); + if (!mOngoingRequest.get()) { + return; + } dispatchCancellationSignal(cancellationSink.get()); - callback.onProviderResponseFailure( - CredentialProviderErrors.ERROR_TASK_CANCELED, - null); + if (mCallback != null) { + mOngoingRequest.set(false); + mCallback.onProviderResponseFailure( + CredentialProviderErrors.ERROR_TASK_CANCELED, + null); + } } else if (error instanceof GetCredentialException) { - callback.onProviderResponseFailure( - CredentialProviderErrors.ERROR_PROVIDER_FAILURE, - (GetCredentialException) error); + if (mCallback != null) { + mCallback.onProviderResponseFailure( + CredentialProviderErrors.ERROR_PROVIDER_FAILURE, + (GetCredentialException) error); + } } else if (error instanceof CreateCredentialException) { - callback.onProviderResponseFailure( - CredentialProviderErrors.ERROR_PROVIDER_FAILURE, - (CreateCredentialException) error); + if (mCallback != null) { + mCallback.onProviderResponseFailure( + CredentialProviderErrors.ERROR_PROVIDER_FAILURE, + (CreateCredentialException) error); + } } else { - callback.onProviderResponseFailure( - CredentialProviderErrors.ERROR_UNKNOWN, - (Exception) error); + if (mCallback != null) { + mCallback.onProviderResponseFailure( + CredentialProviderErrors.ERROR_UNKNOWN, + (Exception) error); + } } } } diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index 7caa921eacda..a41b5713ee14 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -75,6 +75,8 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential protected final Handler mHandler; @UserIdInt protected final int mUserId; + + protected final int mUniqueSessionInteger; private final int mCallingUid; @NonNull protected final CallingAppInfo mClientAppInfo; @@ -82,7 +84,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential protected final CancellationSignal mCancellationSignal; protected final Map<String, ProviderSession> mProviders = new ConcurrentHashMap<>(); - protected final RequestSessionMetric mRequestSessionMetric = new RequestSessionMetric(); + protected final RequestSessionMetric mRequestSessionMetric; protected final String mHybridService; protected final Object mLock; @@ -132,7 +134,10 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential mUserId, this, mEnabledProviders); mHybridService = context.getResources().getString( R.string.config_defaultCredentialManagerHybridService); - mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mRequestId, + mUniqueSessionInteger = MetricUtilities.getHighlyUniqueInteger(); + mRequestSessionMetric = new RequestSessionMetric(mUniqueSessionInteger, + MetricUtilities.getHighlyUniqueInteger()); + mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType)); setCancellationListener(); } diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java index 1930a4859e87..fd497965b5b1 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java @@ -18,11 +18,13 @@ package com.android.server.credentials.metrics; import static android.credentials.ui.RequestInfo.TYPE_CREATE; import static android.credentials.ui.RequestInfo.TYPE_GET; +import static android.credentials.ui.RequestInfo.TYPE_GET_VIA_REGISTRY; import static android.credentials.ui.RequestInfo.TYPE_UNDEFINED; import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CLEAR_CREDENTIAL; import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CREATE_CREDENTIAL; import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL_VIA_REGISTRY; import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE; import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_UNKNOWN; @@ -35,6 +37,8 @@ import java.util.Map; public enum ApiName { UNKNOWN(CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_UNKNOWN), GET_CREDENTIAL(CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL), + GET_CREDENTIAL_VIA_REGISTRY( +CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL_VIA_REGISTRY), CREATE_CREDENTIAL( CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CREATE_CREDENTIAL), CLEAR_CREDENTIAL( @@ -52,6 +56,8 @@ CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_IS_ENABLED_CREDENT CREATE_CREDENTIAL.mInnerMetricCode), new AbstractMap.SimpleEntry<>(TYPE_GET, GET_CREDENTIAL.mInnerMetricCode), + new AbstractMap.SimpleEntry<>(TYPE_GET_VIA_REGISTRY, + GET_CREDENTIAL_VIA_REGISTRY.mInnerMetricCode), new AbstractMap.SimpleEntry<>(TYPE_UNDEFINED, CLEAR_CREDENTIAL.mInnerMetricCode) ); diff --git a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java new file mode 100644 index 000000000000..51e86d51acdf --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.credentials.metrics; + +/** + * Encapsulates an authentication entry click atom, as a part of track 2. + * Contains information about what was collected from the authentication entry output. + */ +public class BrowsedAuthenticationMetric { + // The session id of this provider known flow related metric + private final int mSessionIdProvider; + // TODO(b/271135048) - Match the atom and provide a clean per provider session metric + // encapsulation. + + public BrowsedAuthenticationMetric(int sessionIdProvider) { + mSessionIdProvider = sessionIdProvider; + } + + public int getSessionIdProvider() { + return mSessionIdProvider; + } +} diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java new file mode 100644 index 000000000000..08e75837a274 --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.credentials.metrics; + +import com.android.server.credentials.ProviderSession; + +import java.util.Map; + +/** + * This will generate most of its data via using the information of {@link CandidatePhaseMetric} + * across all the providers. This belongs to the metric flow where the calling app is known. + */ +public class CandidateAggregateMetric { + + private static final String TAG = "CandidateProviderMetric"; + // The session id of this provider metric + private final int mSessionIdProvider; + // Indicates if this provider returned from the query phase, default false + private boolean mQueryReturned = false; + // Indicates the total number of providers this aggregate captures information for, default 0 + private int mNumProviders = 0; + // Indicates the total number of authentication entries that were tapped in aggregate, default 0 + private int mNumAuthEntriesTapped = 0; + + public CandidateAggregateMetric(int sessionIdTrackOne) { + mSessionIdProvider = sessionIdTrackOne; + } + + public int getSessionIdProvider() { + return mSessionIdProvider; + } + + /** + * This will take all the candidate data captured and aggregate that information. + * TODO(b/271135048) : Add on authentication entry outputs from track 2 here as well once + * generated + * @param providers the providers associated with the candidate flow + */ + public void collectAverages(Map<String, ProviderSession> providers) { + // TODO(b/271135048) : Complete this method + mNumProviders = providers.size(); + var providerSessions = providers.values(); + for (var session : providerSessions) { + var metric = session.getProviderSessionMetric(); + mQueryReturned = mQueryReturned || metric + .mCandidatePhasePerProviderMetric.isQueryReturned(); + } + } + + public int getNumProviders() { + return mNumProviders; + } + + public boolean isQueryReturned() { + return mQueryReturned; + } + + public int getNumAuthEntriesTapped() { + return mNumAuthEntriesTapped; + } +} diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java index 07af6549411e..6b74252dec19 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java @@ -27,23 +27,11 @@ package com.android.server.credentials.metrics; * though collection will begin in the candidate phase when the user begins browsing options. */ public class CandidateBrowsingPhaseMetric { - // The session id associated with the API Call this candidate provider is a part of, default -1 - private int mSessionId = -1; // The EntryEnum that was pressed, defaults to -1 private int mEntryEnum = EntryEnum.UNKNOWN.getMetricCode(); // The provider associated with the press, defaults to -1 private int mProviderUid = -1; - /* -- The session ID -- */ - - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; - } - /* -- The Entry of this tap -- */ public void setEntryEnum(int entryEnum) { diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java index 3ea9b1ce86f8..d9bf4a134adb 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java @@ -32,8 +32,8 @@ import java.util.Map; public class CandidatePhaseMetric { private static final String TAG = "CandidateProviderMetric"; - // The session id of this provider, default set to -1 - private int mSessionId = -1; + // The session id of this provider metric + private final int mSessionIdProvider; // Indicates if this provider returned from the query phase, default false private boolean mQueryReturned = false; @@ -53,13 +53,15 @@ public class CandidatePhaseMetric { private int mProviderQueryStatus = -1; // Indicates if an exception was thrown by this provider, false by default private boolean mHasException = false; + // Indicates the framework only exception belonging to this provider private String mFrameworkException = ""; // Stores the response credential information, as well as the response entry information which // by default, contains empty info private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of()); - public CandidatePhaseMetric() { + public CandidatePhaseMetric(int sessionIdTrackTwo) { + mSessionIdProvider = sessionIdTrackTwo; } /* ---------- Latencies ---------- */ @@ -141,12 +143,8 @@ public class CandidatePhaseMetric { /* -------------- Session Id ---------------- */ - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; + public int getSessionIdProvider() { + return mSessionIdProvider; } /* -------------- Query Returned Status ---------------- */ diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java index 93a82906aa50..e8af86012aaf 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java @@ -32,8 +32,12 @@ import java.util.Map; */ public class ChosenProviderFinalPhaseMetric { private static final String TAG = "ChosenFinalPhaseMetric"; - // The session id associated with this API call, used to unite split emits - private int mSessionId = -1; + // The session id associated with this API call, used to unite split emits, for the flow + // where we know the calling app + private final int mSessionIdCaller; + // The session id associated with this API call, used to unite split emits, for the flow + // where we know the provider apps + private final int mSessionIdProvider; // Reveals if the UI was returned, false by default private boolean mUiReturned = false; private int mChosenUid = -1; @@ -66,13 +70,17 @@ public class ChosenProviderFinalPhaseMetric { private int mChosenProviderStatus = -1; // Indicates if an exception was thrown by this provider, false by default private boolean mHasException = false; + // Indicates a framework only exception that occurs in the final phase of the flow + private String mFrameworkException = ""; // Stores the response credential information, as well as the response entry information which // by default, contains empty info private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of()); - public ChosenProviderFinalPhaseMetric() { + public ChosenProviderFinalPhaseMetric(int sessionIdCaller, int sessionIdProvider) { + mSessionIdCaller = sessionIdCaller; + mSessionIdProvider = sessionIdProvider; } /* ------------------- UID ------------------- */ @@ -235,12 +243,8 @@ public class ChosenProviderFinalPhaseMetric { /* ----------- Session ID -------------- */ - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; + public int getSessionIdProvider() { + return mSessionIdProvider; } /* ----------- UI Returned Successfully -------------- */ @@ -272,4 +276,20 @@ public class ChosenProviderFinalPhaseMetric { public ResponseCollective getResponseCollective() { return mResponseCollective; } + + /* -------------- Framework Exception ---------------- */ + + public void setFrameworkException(String frameworkException) { + mFrameworkException = frameworkException; + } + + public String getFrameworkException() { + return mFrameworkException; + } + + /* -------------- Session ID for Track One (Known Calling App) ---------------- */ + + public int getSessionIdCaller() { + return mSessionIdCaller; + } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java index 060e56ce965b..8e965e3e5ba5 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java @@ -32,8 +32,8 @@ public class InitialPhaseMetric { private int mApiName = ApiName.UNKNOWN.getMetricCode(); // The caller uid of the calling application, default to -1 private int mCallerUid = -1; - // The session id to unite multiple atom emits, default to -1 - private int mSessionId = -1; + // The session id to unite multiple atom emits + private final int mSessionIdCaller; // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a // reference point. @@ -50,7 +50,8 @@ public class InitialPhaseMetric { private Map<String, Integer> mRequestCounts = new LinkedHashMap<>(); - public InitialPhaseMetric() { + public InitialPhaseMetric(int sessionIdTrackOne) { + mSessionIdCaller = sessionIdTrackOne; } /* ---------- Latencies ---------- */ @@ -105,12 +106,8 @@ public class InitialPhaseMetric { /* ------ SessionId ------ */ - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; + public int getSessionIdCaller() { + return mSessionIdCaller; } /* ------ Count Request Class Types ------ */ diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java index f011b554fe53..47db8f59ff35 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java @@ -16,7 +16,7 @@ package com.android.server.credentials.metrics; -import static com.android.server.credentials.MetricUtilities.DELTA_CUT; +import static com.android.server.credentials.MetricUtilities.DELTA_RESPONSES_CUT; import static com.android.server.credentials.MetricUtilities.generateMetricKey; import android.annotation.NonNull; @@ -44,10 +44,17 @@ public class ProviderSessionMetric { // Specific candidate provider metric for the provider this session handles @NonNull - protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric = - new CandidatePhaseMetric(); + protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric; - public ProviderSessionMetric() {} + // IFF there was an authentication entry clicked, this stores all required information for + // that event. This is for the 'get' flow. + @NonNull + protected final BrowsedAuthenticationMetric mBrowsedAuthenticationMetric; + + public ProviderSessionMetric(int sessionIdTrackTwo) { + mCandidatePhasePerProviderMetric = new CandidatePhaseMetric(sessionIdTrackTwo); + mBrowsedAuthenticationMetric = new BrowsedAuthenticationMetric(sessionIdTrackTwo); + } /** * Retrieve the candidate provider phase metric and the data it contains. @@ -56,6 +63,7 @@ public class ProviderSessionMetric { return mCandidatePhasePerProviderMetric; } + /** * This collects for ProviderSessions, with respect to the candidate providers, whether * an exception occurred in the candidate call. @@ -78,6 +86,13 @@ public class ProviderSessionMetric { } } + private void collectAuthEntryUpdate(boolean isFailureStatus, + boolean isCompletionStatus, int providerSessionUid) { + // TODO(b/271135048) - Mimic typical candidate update, but with authentication metric + // Collect the final timestamps (and start timestamp), status, exceptions and the provider + // uid. This occurs typically *after* the collection is complete. + } + /** * Used to collect metrics at the update stage when a candidate provider gives back an update. * @@ -86,8 +101,12 @@ public class ProviderSessionMetric { * @param providerSessionUid the uid of the provider */ public void collectCandidateMetricUpdate(boolean isFailureStatus, - boolean isCompletionStatus, int providerSessionUid) { + boolean isCompletionStatus, int providerSessionUid, boolean isAuthEntry) { try { + if (isAuthEntry) { + collectAuthEntryUpdate(isFailureStatus, isCompletionStatus, providerSessionUid); + return; + } mCandidatePhasePerProviderMetric.setCandidateUid(providerSessionUid); mCandidatePhasePerProviderMetric .setQueryFinishTimeNanoseconds(System.nanoTime()); @@ -119,7 +138,6 @@ public class ProviderSessionMetric { */ public void collectCandidateMetricSetupViaInitialMetric(InitialPhaseMetric initMetric) { try { - mCandidatePhasePerProviderMetric.setSessionId(initMetric.getSessionId()); mCandidatePhasePerProviderMetric.setServiceBeganTimeNanoseconds( initMetric.getCredentialServiceStartedTimeNanoseconds()); mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime()); @@ -133,13 +151,14 @@ public class ProviderSessionMetric { * purposes. * * @param response contains entries and data from the candidate provider responses + * @param isAuthEntry indicates if this is an auth entry collection or not * @param <R> the response type associated with the API flow in progress */ - public <R> void collectCandidateEntryMetrics(R response) { + public <R> void collectCandidateEntryMetrics(R response, boolean isAuthEntry) { try { if (response instanceof BeginGetCredentialResponse) { beginGetCredentialResponseCollectionCandidateEntryMetrics( - (BeginGetCredentialResponse) response); + (BeginGetCredentialResponse) response, isAuthEntry); } else if (response instanceof BeginCreateCredentialResponse) { beginCreateCredentialResponseCollectionCandidateEntryMetrics( (BeginCreateCredentialResponse) response); @@ -170,7 +189,7 @@ public class ProviderSessionMetric { entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries); entries.forEach(entry -> { - String entryKey = generateMetricKey(entry.getType(), DELTA_CUT); + String entryKey = generateMetricKey(entry.getType(), DELTA_RESPONSES_CUT); responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1); }); @@ -198,7 +217,7 @@ public class ProviderSessionMetric { } private void beginGetCredentialResponseCollectionCandidateEntryMetrics( - BeginGetCredentialResponse response) { + BeginGetCredentialResponse response, boolean isAuthEntry) { Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>(); Map<String, Integer> responseCounts = new LinkedHashMap<>(); int numCredEntries = response.getCredentialEntries().size(); @@ -212,11 +231,16 @@ public class ProviderSessionMetric { entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries); response.getCredentialEntries().forEach(entry -> { - String entryKey = generateMetricKey(entry.getType(), DELTA_CUT); + String entryKey = generateMetricKey(entry.getType(), DELTA_RESPONSES_CUT); responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1); }); ResponseCollective responseCollective = new ResponseCollective(responseCounts, entryCounts); - mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective); + + if (!isAuthEntry) { + mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective); + } else { + // TODO(b/immediately) - Add the auth entry get logic + } } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java index 4624e0b3701a..03ffe23f9886 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -16,14 +16,16 @@ package com.android.server.credentials.metrics; -import static com.android.server.credentials.MetricUtilities.DELTA_CUT; +import static com.android.server.credentials.MetricUtilities.DELTA_EXCEPTION_CUT; +import static com.android.server.credentials.MetricUtilities.DELTA_RESPONSES_CUT; import static com.android.server.credentials.MetricUtilities.generateMetricKey; import static com.android.server.credentials.MetricUtilities.logApiCalledCandidatePhase; import static com.android.server.credentials.MetricUtilities.logApiCalledFinalPhase; +import static com.android.server.credentials.MetricUtilities.logApiCalledNoUidFinal; +import android.annotation.NonNull; import android.credentials.GetCredentialRequest; import android.credentials.ui.UserSelectionDialogResult; -import android.os.IBinder; import android.util.Slog; import com.android.server.credentials.ProviderSession; @@ -47,13 +49,24 @@ public class RequestSessionMetric { // As emits occur in sequential order, increment this counter and utilize protected int mSequenceCounter = 0; - protected final InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric(); + protected final InitialPhaseMetric mInitialPhaseMetric; protected final ChosenProviderFinalPhaseMetric - mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric(); + mChosenProviderFinalPhaseMetric; // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4) protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>(); + // Specific aggregate candidate provider metric for the provider this session handles + @NonNull + protected final CandidateAggregateMetric mCandidateAggregateMetric; + // Since track two is shared, this allows provider sessions to capture a metric-specific + // session token for the flow where the provider is known + private final int mSessionIdTrackTwo; - public RequestSessionMetric() { + public RequestSessionMetric(int sessionIdTrackOne, int sessionIdTrackTwo) { + mSessionIdTrackTwo = sessionIdTrackTwo; + mInitialPhaseMetric = new InitialPhaseMetric(sessionIdTrackOne); + mCandidateAggregateMetric = new CandidateAggregateMetric(sessionIdTrackOne); + mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric( + sessionIdTrackOne, sessionIdTrackTwo); } /** @@ -75,18 +88,25 @@ public class RequestSessionMetric { } /** + * @return the aggregate candidate phase metrics associated with the request session + */ + public CandidateAggregateMetric getCandidateAggregateMetric() { + return mCandidateAggregateMetric; + } + + /** * Upon starting the service, this fills the initial phase metric properly. * * @param timestampStarted the timestamp the service begins at - * @param mRequestId the IBinder used to retrieve a unique id * @param mCallingUid the calling process's uid * @param metricCode typically pulled from {@link ApiName} + * @param callingAppFlowUniqueInt the unique integer used as the session id for the calling app + * known flow */ - public void collectInitialPhaseMetricInfo(long timestampStarted, IBinder mRequestId, + public void collectInitialPhaseMetricInfo(long timestampStarted, int mCallingUid, int metricCode) { try { mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted); - mInitialPhaseMetric.setSessionId(mRequestId.hashCode()); mInitialPhaseMetric.setCallerUid(mCallingUid); mInitialPhaseMetric.setApiName(metricCode); } catch (Exception e) { @@ -168,11 +188,9 @@ public class RequestSessionMetric { Map<String, Integer> uniqueRequestCounts = new LinkedHashMap<>(); try { request.getCredentialOptions().forEach(option -> { - String optionKey = generateMetricKey(option.getType(), DELTA_CUT); - if (!uniqueRequestCounts.containsKey(optionKey)) { - uniqueRequestCounts.put(optionKey, 0); - } - uniqueRequestCounts.put(optionKey, uniqueRequestCounts.get(optionKey) + 1); + String optionKey = generateMetricKey(option.getType(), DELTA_RESPONSES_CUT); + uniqueRequestCounts.put(optionKey, uniqueRequestCounts.getOrDefault(optionKey, + 0) + 1); }); } catch (Exception e) { Slog.i(TAG, "Unexpected error during get request metric logging: " + e); @@ -207,7 +225,6 @@ public class RequestSessionMetric { CandidatePhaseMetric selectedProviderPhaseMetric) { try { CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric(); - browsingPhaseMetric.setSessionId(mInitialPhaseMetric.getSessionId()); browsingPhaseMetric.setEntryEnum( EntryEnum.getMetricCodeFromString(selection.getEntryKey())); browsingPhaseMetric.setProviderUid(selectedProviderPhaseMetric.getCandidateUid()); @@ -218,7 +235,7 @@ public class RequestSessionMetric { } /** - * Updates the final phase metric with the designated bit + * Updates the final phase metric with the designated bit. * * @param exceptionBitFinalPhase represents if the final phase provider had an exception */ @@ -231,6 +248,21 @@ public class RequestSessionMetric { } /** + * This allows collecting the framework exception string for the final phase metric. + * NOTE that this exception will be cut for space optimizations. + * + * @param exception the framework exception that is being recorded + */ + public void collectFrameworkException(String exception) { + try { + mChosenProviderFinalPhaseMetric.setFrameworkException( + generateMetricKey(exception, DELTA_EXCEPTION_CUT)); + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during metric logging: " + e); + } + } + + /** * Allows encapsulating the overall final phase metric status from the chosen and final * provider. * @@ -260,7 +292,6 @@ public class RequestSessionMetric { */ public void collectChosenMetricViaCandidateTransfer(CandidatePhaseMetric candidatePhaseMetric) { try { - mChosenProviderFinalPhaseMetric.setSessionId(candidatePhaseMetric.getSessionId()); mChosenProviderFinalPhaseMetric.setChosenUid(candidatePhaseMetric.getCandidateUid()); mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds( @@ -284,7 +315,7 @@ public class RequestSessionMetric { * In the final phase, this helps log use cases that were either pure failures or user * canceled. It's expected that {@link #collectFinalPhaseProviderMetricStatus(boolean, * ProviderStatusForMetrics) collectFinalPhaseProviderMetricStatus} is called prior to this. - * Otherwise, the logging will miss required bits + * Otherwise, the logging will miss required bits. * * @param isUserCanceledError a boolean indicating if the error was due to user cancelling */ @@ -318,6 +349,20 @@ public class RequestSessionMetric { } /** + * Handles aggregate candidate phase metric emits in the RequestSession context, after the + * candidate phase completes. + * + * @param providers a map with known providers and their held metric objects + */ + public void logCandidateAggregateMetrics(Map<String, ProviderSession> providers) { + try { + mCandidateAggregateMetric.collectAverages(providers); + } catch (Exception e) { + Slog.i(TAG, "Unexpected error during aggregate candidate logging " + e); + } + } + + /** * Handles the final logging for RequestSession context for the final phase. * * @param apiStatus the final status of the api being called @@ -327,9 +372,15 @@ public class RequestSessionMetric { logApiCalledFinalPhase(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric, apiStatus, ++mSequenceCounter); + logApiCalledNoUidFinal(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric, + apiStatus, + ++mSequenceCounter); } catch (Exception e) { Slog.i(TAG, "Unexpected error during final metric emit: " + e); } } + public int getSessionIdTrackTwo() { + return mSessionIdTrackTwo; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index debfedcd1806..bb3b4386a4de 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -23200,6 +23200,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_ACROSS_USERS); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEFAULT_SMS, MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_INPUT_METHODS, + MANAGE_DEVICE_POLICY_ACROSS_USERS); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE, MANAGE_DEVICE_POLICY_ACROSS_USERS); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MOBILE_NETWORK, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 8030bb7b4fd9..bac39e021d2f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -265,7 +265,7 @@ final class PolicyDefinition<V> { // never used, but might need some refactoring to not always assume a non-null // mechanism. TRUE_MORE_RESTRICTIVE, - POLICY_FLAG_LOCAL_ONLY_POLICY, + POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE, PolicyEnforcerCallbacks::setApplicationHidden, new BooleanPolicySerializer()); @@ -290,7 +290,7 @@ final class PolicyDefinition<V> { new AccountTypePolicyKey( DevicePolicyIdentifiers.ACCOUNT_MANAGEMENT_DISABLED_POLICY), TRUE_MORE_RESTRICTIVE, - POLICY_FLAG_LOCAL_ONLY_POLICY, + POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE, // Nothing is enforced, we just need to store it (Boolean value, Context context, Integer userId, PolicyKey policyKey) -> true, new BooleanPolicySerializer()); @@ -311,7 +311,7 @@ final class PolicyDefinition<V> { static PolicyDefinition<Set<String>> PERMITTED_INPUT_METHODS = new PolicyDefinition<>( new NoArgsPolicyKey(DevicePolicyIdentifiers.PERMITTED_INPUT_METHODS_POLICY), new MostRecent<>(), - POLICY_FLAG_LOCAL_ONLY_POLICY, + POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE, (Set<String> value, Context context, Integer userId, PolicyKey policyKey) -> true, new StringSetPolicySerializer()); @@ -319,14 +319,14 @@ final class PolicyDefinition<V> { static PolicyDefinition<Boolean> SCREEN_CAPTURE_DISABLED = new PolicyDefinition<>( new NoArgsPolicyKey(DevicePolicyIdentifiers.SCREEN_CAPTURE_DISABLED_POLICY), TRUE_MORE_RESTRICTIVE, - /* flags= */ 0, + POLICY_FLAG_INHERITABLE, PolicyEnforcerCallbacks::setScreenCaptureDisabled, new BooleanPolicySerializer()); static PolicyDefinition<Boolean> PERSONAL_APPS_SUSPENDED = new PolicyDefinition<>( new NoArgsPolicyKey(DevicePolicyIdentifiers.PERSONAL_APPS_SUSPENDED_POLICY), new MostRecent<>(), - POLICY_FLAG_LOCAL_ONLY_POLICY, + POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE, PolicyEnforcerCallbacks::setPersonalAppsSuspended, new BooleanPolicySerializer()); @@ -547,7 +547,7 @@ final class PolicyDefinition<V> { String restriction, int flags) { String identifier = DevicePolicyIdentifiers.getIdentifierForUserRestriction(restriction); UserRestrictionPolicyKey key = new UserRestrictionPolicyKey(identifier, restriction); - flags |= POLICY_FLAG_USER_RESTRICTION_POLICY; + flags |= (POLICY_FLAG_USER_RESTRICTION_POLICY | POLICY_FLAG_INHERITABLE); PolicyDefinition<Boolean> definition = new PolicyDefinition<>( key, TRUE_MORE_RESTRICTIVE, diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java index 906cc83aea33..9e371649214c 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java @@ -93,6 +93,9 @@ public class PackageParserLegacyCoreTest { private static final int PLATFORM_VERSION = 20; private static final int NEWER_VERSION = 30; + private static final int DISALLOW_PRERELEASE = -1; + private static final int DISALLOW_RELEASED = -1; + @Rule public final Expect expect = Expect.create(); private void verifyComputeMinSdkVersion(int minSdkVersion, String minSdkCodename, @@ -149,8 +152,10 @@ public class PackageParserLegacyCoreTest { // Don't allow newer pre-release minSdkVersion on pre-release platform. // APP: Pre-release API 30 // DEV: Pre-release API 20 - verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1); - verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, -1); + verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, + DISALLOW_PRERELEASE); + verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, + DISALLOW_PRERELEASE); } @Test @@ -173,21 +178,27 @@ public class PackageParserLegacyCoreTest { // Don't allow older pre-release minSdkVersion on released platform. // APP: Pre-release API 10 // DEV: Released API 20 - verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1); - verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, -1); + verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, + DISALLOW_RELEASED); + verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, + DISALLOW_RELEASED); // Don't allow same pre-release minSdkVersion on released platform. // APP: Pre-release API 20 // DEV: Released API 20 - verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1); - verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, -1); + verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, + DISALLOW_RELEASED); + verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, + DISALLOW_RELEASED); // Don't allow newer pre-release minSdkVersion on released platform. // APP: Pre-release API 30 // DEV: Released API 20 - verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1); - verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, -1); + verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, + DISALLOW_RELEASED); + verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, + DISALLOW_RELEASED); } private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename, @@ -254,10 +265,10 @@ public class PackageParserLegacyCoreTest { // Don't allow newer pre-release targetSdkVersion on pre-release platform. // APP: Pre-release API 30 // DEV: Pre-release API 20 - verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, false, -1); + verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, false, + DISALLOW_PRERELEASE); verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, - false, -1 - ); + false, DISALLOW_PRERELEASE); // Do allow newer pre-release targetSdkVersion on pre-release platform when // allowUnknownCodenames is true. @@ -290,35 +301,35 @@ public class PackageParserLegacyCoreTest { // Don't allow older pre-release targetSdkVersion on released platform. // APP: Pre-release API 10 // DEV: Released API 20 - verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, false, -1); + verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, false, + DISALLOW_RELEASED); verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, - false, -1 - ); + false, DISALLOW_RELEASED); // Don't allow same pre-release targetSdkVersion on released platform. // APP: Pre-release API 20 // DEV: Released API 20 - verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, false, -1); + verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, false, + DISALLOW_RELEASED); verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, false, - -1 - ); + DISALLOW_RELEASED); // Don't allow same pre-release targetSdkVersion on released platform when // allowUnknownCodenames is true. // APP: Pre-release API 20 // DEV: Released API 20 verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, true, - -1); + DISALLOW_RELEASED); verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, true, - -1); + DISALLOW_RELEASED); // Don't allow newer pre-release targetSdkVersion on released platform. // APP: Pre-release API 30 // DEV: Released API 20 - verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, false, -1); + verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, false, + DISALLOW_RELEASED); verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, - false, -1 - ); + false, DISALLOW_RELEASED); // Do allow newer pre-release targetSdkVersion on released platform when // allowUnknownCodenames is true. // APP: Pre-release API 30 diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index acfea85d60a2..5d3b91368dcb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -769,7 +769,7 @@ public final class BroadcastQueueModernImplTest { BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); - queue.setPrioritizeEarliest(true); + queue.addPrioritizeEarliestRequest(); long timeCounter = 100; enqueueOrReplaceBroadcast(queue, @@ -814,6 +814,28 @@ public final class BroadcastQueueModernImplTest { queue.makeActiveNextPending(); assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE, queue.getActive().intent.getAction()); + + + queue.removePrioritizeEarliestRequest(); + + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED) + .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++); + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), + 0, timeCounter++); + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++); + + // Once the request to prioritize earliest is removed, we should expect broadcasts + // to be dispatched in the order of foreground, normal and then offload. + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction()); } /** diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 7be1d7cde27f..3a8d2c92eaff 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -1890,6 +1890,36 @@ public class BroadcastQueueTest { assertTrue(mQueue.isBeyondBarrierLocked(afterSecond)); } + @Test + public void testWaitForBroadcastDispatch() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); + + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); + assertTrue(mQueue.isDispatchedLocked(timeTick)); + + final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(timezone, callerApp, + List.of(makeRegisteredReceiver(receiverApp)))); + + assertTrue(mQueue.isDispatchedLocked(timeTick)); + assertFalse(mQueue.isDispatchedLocked(timezone)); + + enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, + List.of(makeRegisteredReceiver(receiverApp)))); + + assertFalse(mQueue.isDispatchedLocked(timeTick)); + assertFalse(mQueue.isDispatchedLocked(timezone)); + + mLooper.release(); + + mQueue.waitForDispatched(timeTick, LOG_WRITER_INFO); + assertTrue(mQueue.isDispatchedLocked(timeTick)); + + mQueue.waitForDispatched(timezone, LOG_WRITER_INFO); + assertTrue(mQueue.isDispatchedLocked(timezone)); + } + /** * Verify that we OOM adjust for manifest receivers. */ diff --git a/services/tests/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/SoundTriggerMiddlewareImplTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java index 3d963ed4fe37..56bd1929b1d4 100644 --- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java +++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java @@ -164,7 +164,7 @@ public class SoundTriggerMiddlewareImplTest { public void testAttachDetach() throws Exception { // Normal attachment / detachment. ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); assertNotNull(module); module.detach(); } @@ -172,7 +172,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testLoadUnloadModel() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); final int hwHandle = 7; int handle = loadGenericModel(module, hwHandle).first; @@ -183,7 +183,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testLoadPreemptModel() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); final int hwHandle = 7; Pair<Integer, SoundTriggerHwCallback> loadResult = loadGenericModel(module, hwHandle); @@ -202,7 +202,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testLoadUnloadPhraseModel() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); final int hwHandle = 73; int handle = loadPhraseModel(module, hwHandle).first; @@ -213,7 +213,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testStartStopRecognition() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); // Load the model. final int hwHandle = 7; @@ -233,7 +233,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testStartRecognitionBusy() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); // Load the model. final int hwHandle = 7; @@ -257,7 +257,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testStartStopPhraseRecognition() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); // Load the model. final int hwHandle = 67; @@ -277,7 +277,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testRecognition() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); // Load the model. final int hwHandle = 7; @@ -322,7 +322,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testPhraseRecognition() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); // Load the model. final int hwHandle = 7; @@ -352,7 +352,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testForceRecognition() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); // Load the model. final int hwHandle = 17; @@ -389,7 +389,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testForceRecognitionNotSupported() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); // Load the model. final int hwHandle = 17; @@ -420,7 +420,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testForcePhraseRecognition() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); // Load the model. final int hwHandle = 17; @@ -457,7 +457,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testForcePhraseRecognitionNotSupported() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); // Load the model. final int hwHandle = 17; @@ -489,7 +489,7 @@ public class SoundTriggerMiddlewareImplTest { public void testAbortRecognition() throws Exception { // Make sure the HAL doesn't support concurrent capture. ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); // Load the model. final int hwHandle = 11; @@ -521,7 +521,7 @@ public class SoundTriggerMiddlewareImplTest { public void testAbortPhraseRecognition() throws Exception { // Make sure the HAL doesn't support concurrent capture. ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); // Load the model. final int hwHandle = 11; @@ -552,7 +552,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testParameterSupported() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); final int hwHandle = 12; int modelHandle = loadGenericModel(module, hwHandle).first; @@ -574,7 +574,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testParameterNotSupported() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); final int hwHandle = 13; int modelHandle = loadGenericModel(module, hwHandle).first; @@ -592,7 +592,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testGetParameter() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); final int hwHandle = 14; int modelHandle = loadGenericModel(module, hwHandle).first; @@ -609,7 +609,7 @@ public class SoundTriggerMiddlewareImplTest { @Test public void testSetParameter() throws Exception { ISoundTriggerCallback callback = createCallbackMock(); - ISoundTriggerModule module = mService.attach(0, callback); + ISoundTriggerModule module = mService.attach(0, callback, false); final int hwHandle = 17; int modelHandle = loadGenericModel(module, hwHandle).first; 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 cc357d76cb4a..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 @@ -20,6 +20,7 @@ import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERAC import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.verify; @@ -37,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; @@ -91,18 +91,17 @@ public class SoundTriggerMiddlewareLoggingLatencyTest { } @Test - @FlakyTest(bugId = 275113847) public void testSetUpAndTearDown() { } @Test - @FlakyTest(bugId = 275113847) public void testOnPhraseRecognitionStartsLatencyTrackerWithSuccessfulPhraseIdTrigger() throws RemoteException { ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( ISoundTriggerCallback.class); - mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback); - verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture()); + mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback, false); + verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture(), + anyBoolean()); triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(), RecognitionStatus.SUCCESS, 100 /* keyphraseId */); @@ -112,12 +111,12 @@ public class SoundTriggerMiddlewareLoggingLatencyTest { } @Test - @FlakyTest(bugId = 275113847) public void testOnPhraseRecognitionRestartsActiveSession() throws RemoteException { ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( ISoundTriggerCallback.class); - mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback); - verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture()); + mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback, false); + verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture(), + anyBoolean()); triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(), RecognitionStatus.SUCCESS, 100 /* keyphraseId */); @@ -132,13 +131,13 @@ public class SoundTriggerMiddlewareLoggingLatencyTest { } @Test - @FlakyTest(bugId = 275113847) public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNonSuccessEvent() throws RemoteException { ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( ISoundTriggerCallback.class); - mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback); - verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture()); + mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback, false); + verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture(), + anyBoolean()); triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(), RecognitionStatus.ABORTED, 100 /* keyphraseId */); @@ -149,13 +148,13 @@ public class SoundTriggerMiddlewareLoggingLatencyTest { } @Test - @FlakyTest(bugId = 275113847) public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNoKeyphraseId() throws RemoteException { ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( ISoundTriggerCallback.class); - mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback); - verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture()); + mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback, false); + verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture(), + anyBoolean()); triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(), RecognitionStatus.SUCCESS); diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java index bf6901e963e0..9029bc48138c 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java @@ -18,6 +18,7 @@ package com.android.server.policy; import static android.view.KeyEvent.KEYCODE_ALT_LEFT; import static android.view.KeyEvent.KEYCODE_B; +import static android.view.KeyEvent.KEYCODE_BRIGHTNESS_DOWN; import static android.view.KeyEvent.KEYCODE_C; import static android.view.KeyEvent.KEYCODE_CTRL_LEFT; import static android.view.KeyEvent.KEYCODE_E; @@ -186,4 +187,19 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ENTER}, 0); mPhoneWindowManager.assertGoToHomescreen(); } + + /** + * Sends a KEYCODE_BRIGHTNESS_DOWN event and validates the brightness is decreased as expected; + */ + @Test + public void testKeyCodeBrightnessDown() { + float[] currentBrightness = new float[]{0.1f, 0.05f, 0.0f}; + float[] newBrightness = new float[]{0.065738f, 0.0275134f, 0.0f}; + + for (int i = 0; i < currentBrightness.length; i++) { + mPhoneWindowManager.prepareBrightnessDecrease(currentBrightness[i]); + sendKey(KEYCODE_BRIGHTNESS_DOWN); + mPhoneWindowManager.verifyNewBrightness(newBrightness[i]); + } + } } diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 1053fd5c4518..d38302429c02 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -89,6 +89,7 @@ import com.android.server.wm.WindowManagerInternal.AppTransitionListener; import junit.framework.Assert; +import org.mockito.AdditionalMatchers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockSettings; @@ -339,6 +340,20 @@ class TestPhoneWindowManager { setPhoneCallIsInProgress(); } + void prepareBrightnessDecrease(float currentBrightness) { + doReturn(0.0f).when(mPowerManager) + .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); + doReturn(1.0f).when(mPowerManager) + .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); + doReturn(currentBrightness).when(mDisplayManager) + .getBrightness(0); + } + + void verifyNewBrightness(float newBrightness) { + verify(mDisplayManager).setBrightness(Mockito.eq(0), + AdditionalMatchers.eq(newBrightness, 0.001f)); + } + void setPhoneCallIsInProgress() { // Let device has an ongoing phone call. doReturn(false).when(mTelecomManager).isRinging(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 353a8ecaf13d..8ac6b0f65b72 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -22,6 +22,7 @@ import static android.view.RoundedCorners.NO_ROUNDED_CORNERS; import static android.view.Surface.ROTATION_0; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.ViewRootImpl.CLIENT_TRANSIENT; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; @@ -63,6 +64,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; +import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; @@ -388,6 +390,7 @@ public class DisplayPolicyTests extends WindowTestsBase { @SetupWindows(addWindows = { W_ACTIVITY, W_NAVIGATION_BAR }) @Test public void testCanSystemBarsBeShownByUser() { + Assume.assumeFalse(CLIENT_TRANSIENT); ((TestWindowManagerPolicy) mWm.mPolicy).mIsUserSetupComplete = true; mAppWindow.mAttrs.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; mAppWindow.setRequestedVisibleTypes(0, navigationBars()); @@ -409,6 +412,7 @@ public class DisplayPolicyTests extends WindowTestsBase { @UseTestDisplay(addWindows = { W_NAVIGATION_BAR }) @Test public void testTransientBarsSuppressedOnDreams() { + Assume.assumeFalse(CLIENT_TRANSIENT); final WindowState win = createDreamWindow(); ((TestWindowManagerPolicy) mWm.mPolicy).mIsUserSetupComplete = true; diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index 8e80485bca7c..04e1d9c07a07 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -940,8 +940,6 @@ public class DisplayRotationTests { @Test public void testIgnoresDeskDockRotation_whenNoSensorAndLockedRespected() throws Exception { mBuilder.setDeskDockRotation(Surface.ROTATION_270).build(); - when(mMockDisplayPolicy.isDeskDockRespectsNoSensorAndLockedWithoutAccelerometer()) - .thenReturn(true); configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false); when(mMockDisplayPolicy.getDockMode()).thenReturn(Intent.EXTRA_DOCK_STATE_DESK); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 58bf184994e4..d3f68185a269 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -67,7 +67,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.pm.ActivityInfo; -import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.VirtualDisplay; @@ -515,12 +514,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_instrumentedProcessGetPermissionToSwitchTouchMode() { - // Disable global touch mode (config_perDisplayFocusEnabled set to true) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(true).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); + // Enable global touch mode + mWm.mPerDisplayFocusEnabled = true; // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); @@ -539,12 +534,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_nonInstrumentedProcessDontGetPermissionToSwitchTouchMode() { - // Disable global touch mode (config_perDisplayFocusEnabled set to true) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(true).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); + // Enable global touch mode + mWm.mPerDisplayFocusEnabled = true; // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); @@ -563,6 +554,9 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() { + // Disable global touch mode + mWm.mPerDisplayFocusEnabled = false; + // Create one extra display final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false); final VirtualDisplay virtualDisplayOwnTouchMode = @@ -570,17 +564,10 @@ public class WindowManagerServiceTests extends WindowTestsBase { final int numberOfDisplays = mWm.mRoot.mChildren.size(); assertThat(numberOfDisplays).isAtLeast(3); final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream() - .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0) - .count(); + .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0) + .count(); assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2); - // Enable global touch mode (config_perDisplayFocusEnabled set to false) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(false).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); int callingPid = Binder.getCallingPid(); @@ -598,18 +585,14 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() { + // Enable global touch mode + mWm.mPerDisplayFocusEnabled = true; + // Create one extra display final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false); final int numberOfDisplays = mWm.mRoot.mChildren.size(); assertThat(numberOfDisplays).isAtLeast(2); - // Disable global touch mode (config_perDisplayFocusEnabled set to true) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(true).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); int callingPid = Binder.getCallingPid(); @@ -628,18 +611,14 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() { + // Disable global touch mode + mWm.mPerDisplayFocusEnabled = false; + // Create one extra display final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true); final int numberOfDisplays = mWm.mRoot.mChildren.size(); assertThat(numberOfDisplays).isAtLeast(2); - // Enable global touch mode (config_perDisplayFocusEnabled set to false) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(false).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); int callingPid = Binder.getCallingPid(); @@ -667,19 +646,14 @@ public class WindowManagerServiceTests extends WindowTestsBase { } private void testSetInTouchModeOnAllDisplays(boolean perDisplayFocusEnabled) { + // Set global touch mode with the value passed as argument. + mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled; + // Create a couple of extra displays. // setInTouchModeOnAllDisplays should ignore the ownFocus setting. final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false); final VirtualDisplay virtualDisplayOwnFocus = createVirtualDisplay(/* ownFocus= */ true); - // Enable or disable global touch mode (config_perDisplayFocusEnabled setting). - // setInTouchModeOnAllDisplays should ignore this value. - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(perDisplayFocusEnabled).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index a4cad5e24dc1..863523f826b2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -197,6 +197,8 @@ class WindowTestsBase extends SystemServiceTestsBase { */ private static boolean sOverridesCheckedTestDisplay; + private boolean mOriginalPerDisplayFocusEnabled; + @BeforeClass public static void setUpOnceBase() { AttributeCache.init(getInstrumentation().getTargetContext()); @@ -208,6 +210,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mSupervisor = mAtm.mTaskSupervisor; mRootWindowContainer = mAtm.mRootWindowContainer; mWm = mSystemServicesTestRule.getWindowManagerService(); + mOriginalPerDisplayFocusEnabled = mWm.mPerDisplayFocusEnabled; SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock); mDefaultDisplay = mWm.mRoot.getDefaultDisplay(); @@ -279,6 +282,7 @@ class WindowTestsBase extends SystemServiceTestsBase { if (mUseFakeSettingsProvider) { FakeSettingsProvider.clearSettingsProvider(); } + mWm.mPerDisplayFocusEnabled = mOriginalPerDisplayFocusEnabled; } /** diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 67320009e9aa..a67524887086 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -303,6 +303,10 @@ public class SoundTriggerService extends SystemService { private SoundTriggerHelper newSoundTriggerHelper( ModuleProperties moduleProperties, EventLogger eventLogger) { + return newSoundTriggerHelper(moduleProperties, eventLogger, false); + } + private SoundTriggerHelper newSoundTriggerHelper( + ModuleProperties moduleProperties, EventLogger eventLogger, boolean isTrusted) { Identity middlemanIdentity = new Identity(); middlemanIdentity.packageName = ActivityThread.currentOpPackageName(); @@ -325,7 +329,7 @@ public class SoundTriggerService extends SystemService { eventLogger, (SoundTrigger.StatusListener statusListener) -> new SoundTriggerModule( mMiddlewareService, moduleId, statusListener, - Looper.getMainLooper(), middlemanIdentity, originatorIdentity), + Looper.getMainLooper(), middlemanIdentity, originatorIdentity, isTrusted), moduleId, () -> listUnderlyingModuleProperties(originatorIdentity) ); @@ -1724,7 +1728,8 @@ public class SoundTriggerService extends SystemService { } @Override - public Session attach(@NonNull IBinder client, ModuleProperties underlyingModule) { + public Session attach(@NonNull IBinder client, ModuleProperties underlyingModule, + boolean isTrusted) { var identity = IdentityContext.getNonNull(); int sessionId = mSessionIdCounter.getAndIncrement(); mServiceEventLogger.enqueue(new ServiceEvent( @@ -1733,7 +1738,7 @@ public class SoundTriggerService extends SystemService { "LocalSoundTriggerEventLogger for package: " + identity.packageName + "#" + sessionId); - return new SessionImpl(newSoundTriggerHelper(underlyingModule, eventLogger), + return new SessionImpl(newSoundTriggerHelper(underlyingModule, eventLogger, isTrusted), client, eventLogger, identity); } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java index 60f89da8e4d3..ca35b5188515 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java @@ -38,7 +38,10 @@ public interface ISoundTriggerMiddlewareInternal { * * listModules() must be called prior to calling this method and the provided handle must be * one of the handles from the returned list. + * @param isTrusted - {@code true} if this service should not note AppOps for recognitions, + * and should delegate these checks to the **trusted** client. */ public ISoundTriggerModule attach(int handle, - ISoundTriggerCallback callback); + ISoundTriggerCallback callback, + boolean isTrusted); } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java index c8c0f3d00cbf..3b800de2f30b 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java @@ -116,7 +116,8 @@ public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareIntern @Override public @NonNull - ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) { + ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback, + boolean isTrusted) { return mModules[handle].attach(callback); } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java index ecd65ae9fa2f..e3366f8e994b 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java @@ -149,22 +149,22 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt @Override public @NonNull - ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback) { + ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback, boolean isTrusted) { try { var originatorIdentity = IdentityContext.getNonNull(); String packageIdentification = originatorIdentity.packageName - + mSessionCount.getAndIncrement(); + + mSessionCount.getAndIncrement() + (isTrusted ? "trusted" : ""); ModuleLogging result = new ModuleLogging(); var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE, "Session logger for: " + packageIdentification); var callbackWrapper = new CallbackLogging(callback, eventLogger, originatorIdentity); - result.attach(mDelegate.attach(handle, callbackWrapper), eventLogger); + result.attach(mDelegate.attach(handle, callbackWrapper, isTrusted), eventLogger); mServiceEventLogger.enqueue(ServiceEvent.createForReturn( ServiceEvent.Type.ATTACH, - packageIdentification, result, handle, callback) + packageIdentification, result, handle, callback, isTrusted) .printLog(ALOGI, TAG)); mSessionEventLoggers.add(eventLogger); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java index 6b724de73488..2e641a26e9b4 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java @@ -85,11 +85,11 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware @Override public @NonNull ISoundTriggerModule attach(int handle, - @NonNull ISoundTriggerCallback callback) { + @NonNull ISoundTriggerCallback callback, boolean isTrusted) { Identity identity = getIdentity(); enforcePermissionsForPreflight(identity); - ModuleWrapper wrapper = new ModuleWrapper(identity, callback); - return wrapper.attach(mDelegate.attach(handle, wrapper.getCallbackWrapper())); + ModuleWrapper wrapper = new ModuleWrapper(identity, callback, isTrusted); + return wrapper.attach(mDelegate.attach(handle, wrapper.getCallbackWrapper(), isTrusted)); } // Override toString() in order to have the delegate's ID in it. @@ -204,11 +204,14 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware private ISoundTriggerModule mDelegate; private final @NonNull Identity mOriginatorIdentity; private final @NonNull CallbackWrapper mCallbackWrapper; + private final boolean mIsTrusted; ModuleWrapper(@NonNull Identity originatorIdentity, - @NonNull ISoundTriggerCallback callback) { + @NonNull ISoundTriggerCallback callback, + boolean isTrusted) { mOriginatorIdentity = originatorIdentity; mCallbackWrapper = new CallbackWrapper(callback); + mIsTrusted = isTrusted; } ModuleWrapper attach(@NonNull ISoundTriggerModule delegate) { @@ -347,7 +350,11 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware } private void enforcePermissions(String reason) { - enforcePermissionsForDataDelivery(mOriginatorIdentity, reason); + if (mIsTrusted) { + enforcePermissionsForPreflight(mOriginatorIdentity); + } else { + enforcePermissionsForDataDelivery(mOriginatorIdentity, reason); + } } } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java index 1558acf547d1..9de24383ba75 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java @@ -105,17 +105,17 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic public ISoundTriggerModule attachAsOriginator(int handle, Identity identity, ISoundTriggerCallback callback) { try (SafeCloseable ignored = establishIdentityDirect(Objects.requireNonNull(identity))) { - return new ModuleService(mDelegate.attach(handle, callback)); + return new ModuleService(mDelegate.attach(handle, callback, /* isTrusted= */ false)); } } @Override public ISoundTriggerModule attachAsMiddleman(int handle, Identity middlemanIdentity, - Identity originatorIdentity, ISoundTriggerCallback callback) { + Identity originatorIdentity, ISoundTriggerCallback callback, boolean isTrusted) { try (SafeCloseable ignored = establishIdentityIndirect( Objects.requireNonNull(middlemanIdentity), Objects.requireNonNull(originatorIdentity))) { - return new ModuleService(mDelegate.attach(handle, callback)); + return new ModuleService(mDelegate.attach(handle, callback, isTrusted)); } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java index 2924c124f22e..31fab89d1d4e 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java @@ -191,7 +191,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware @Override public @NonNull ISoundTriggerModule attach(int handle, - @NonNull ISoundTriggerCallback callback) { + @NonNull ISoundTriggerCallback callback, boolean isTrusted) { // Input validation. Objects.requireNonNull(callback); Objects.requireNonNull(callback.asBinder()); @@ -209,7 +209,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware // From here on, every exception isn't client's fault. try { Session session = new Session(handle, callback); - session.attach(mDelegate.attach(handle, session.getCallbackWrapper())); + session.attach(mDelegate.attach(handle, session.getCallbackWrapper(), isTrusted)); return session; } catch (Exception e) { throw handleException(e); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 27f3fb3898ee..423a81ac0523 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -381,51 +381,21 @@ public class VoiceInteractionManagerService extends SystemService { @NonNull Identity originatorIdentity, IBinder client, ModuleProperties moduleProperties) { Objects.requireNonNull(originatorIdentity); - boolean forHotwordDetectionService; + boolean forHotwordDetectionService = false; synchronized (VoiceInteractionManagerServiceStub.this) { enforceIsCurrentVoiceInteractionService(); forHotwordDetectionService = mImpl != null && mImpl.mHotwordDetectionConnection != null; } - IVoiceInteractionSoundTriggerSession session; - if (forHotwordDetectionService) { - // Use our own identity and handle the permission checks ourselves. This allows - // properly checking/noting against the voice interactor or hotword detection - // service as needed. - if (HotwordDetectionConnection.DEBUG) { - Slog.d(TAG, "Creating a SoundTriggerSession for a HotwordDetectionService"); - } - originatorIdentity.uid = Binder.getCallingUid(); - originatorIdentity.pid = Binder.getCallingPid(); - session = new SoundTriggerSessionPermissionsDecorator( - createSoundTriggerSessionForSelfIdentity(client, moduleProperties), - mContext, - originatorIdentity); - } else { - if (HotwordDetectionConnection.DEBUG) { - Slog.d(TAG, "Creating a SoundTriggerSession"); - } - try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect( - originatorIdentity)) { - session = new SoundTriggerSession(mSoundTriggerInternal.attach(client, - moduleProperties)); - } + if (HotwordDetectionConnection.DEBUG) { + Slog.d(TAG, "Creating a SoundTriggerSession, for HDS: " + + forHotwordDetectionService); + } + try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect( + originatorIdentity)) { + return new SoundTriggerSession(mSoundTriggerInternal.attach(client, + moduleProperties, forHotwordDetectionService)); } - return new SoundTriggerSessionBinderProxy(session); - } - - private IVoiceInteractionSoundTriggerSession createSoundTriggerSessionForSelfIdentity( - IBinder client, ModuleProperties moduleProperties) { - Identity identity = new Identity(); - identity.uid = Process.myUid(); - identity.pid = Process.myPid(); - identity.packageName = ActivityThread.currentOpPackageName(); - return Binder.withCleanCallingIdentity(() -> { - try (SafeCloseable ignored = IdentityContext.create(identity)) { - return new SoundTriggerSession( - mSoundTriggerInternal.attach(client, moduleProperties)); - } - }); } @Override @@ -1700,11 +1670,7 @@ public class VoiceInteractionManagerService extends SystemService { return null; } - /** - * Implementation of SoundTriggerSession. Does not implement {@link #asBinder()} as it's - * intended to be wrapped by an {@link IVoiceInteractionSoundTriggerSession.Stub} object. - */ - private class SoundTriggerSession implements IVoiceInteractionSoundTriggerSession { + private class SoundTriggerSession extends IVoiceInteractionSoundTriggerSession.Stub { final SoundTriggerInternal.Session mSession; private IHotwordRecognitionStatusCallback mSessionExternalCallback; private IRecognitionStatusCallback mSessionInternalCallback; @@ -1851,12 +1817,6 @@ public class VoiceInteractionManagerService extends SystemService { } @Override - public IBinder asBinder() { - throw new UnsupportedOperationException( - "This object isn't intended to be used as a Binder."); - } - - @Override public void detach() { mSession.detach(); } diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java index b003f59d5e81..331caa1bad7a 100644 --- a/telecomm/java/android/telecom/DisconnectCause.java +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -43,8 +43,8 @@ public final class DisconnectCause implements Parcelable { /** Disconnected because of a local user-initiated action, such as hanging up. */ public static final int LOCAL = TelecomProtoEnums.LOCAL; // = 2 /** - * Disconnected because of a remote user-initiated action, such as the other party hanging up - * up. + * Disconnected because the remote party hung up an ongoing call, or because an outgoing call + * was not answered by the remote party. */ public static final int REMOTE = TelecomProtoEnums.REMOTE; // = 3 /** Disconnected because it has been canceled. */ diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index fb46ff991d23..2021ac7c4cd5 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -278,6 +278,11 @@ public class SatelliteManager { */ public static final int SATELLITE_REQUEST_IN_PROGRESS = 21; + /** + * Satellite modem is currently busy due to which current request cannot be processed. + */ + public static final int SATELLITE_MODEM_BUSY = 22; + /** @hide */ @IntDef(prefix = {"SATELLITE_"}, value = { SATELLITE_ERROR_NONE, @@ -301,7 +306,8 @@ public class SatelliteManager { SATELLITE_NOT_REACHABLE, SATELLITE_NOT_AUTHORIZED, SATELLITE_NOT_SUPPORTED, - SATELLITE_REQUEST_IN_PROGRESS + SATELLITE_REQUEST_IN_PROGRESS, + SATELLITE_MODEM_BUSY }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteError {} diff --git a/tests/SilkFX/res/layout/gainmap_metadata.xml b/tests/SilkFX/res/layout/gainmap_metadata.xml index 0dabaca457f0..4cc3e0cbdb83 100644 --- a/tests/SilkFX/res/layout/gainmap_metadata.xml +++ b/tests/SilkFX/res/layout/gainmap_metadata.xml @@ -21,8 +21,8 @@ android:layout_height="wrap_content"> <LinearLayout - android:layout_width="350dp" - android:layout_height="300dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:padding="8dp" android:orientation="vertical" diff --git a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt index 6a8752abfde7..501b9d33871a 100644 --- a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt +++ b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt @@ -79,6 +79,16 @@ class UserUnlockRequestTest { .isEqualTo(oldCount + 1) } + @Test + fun reportUserMayRequestUnlock_differentUserId_doesNotPropagateToAgent() { + val oldCount = trustAgentRule.agent.onUserMayRequestUnlockCallCount + trustManager.reportUserMayRequestUnlock(userId + 1) + await() + + assertThat(trustAgentRule.agent.onUserMayRequestUnlockCallCount) + .isEqualTo(oldCount) + } + companion object { private const val TAG = "UserUnlockRequestTest" private fun await() = Thread.sleep(250) diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index 2ce2167c1bf3..1b1e93bd480a 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -209,6 +209,11 @@ class LinkCommand : public Command { AddOptionalFlag("--compile-sdk-version-name", "Version name to inject into the AndroidManifest.xml if none is present.", &options_.manifest_fixer_options.compile_sdk_version_codename); + AddOptionalSwitch( + "--no-compile-sdk-metadata", + "Suppresses output of compile SDK-related attributes in AndroidManifest.xml,\n" + "including android:compileSdkVersion and platformBuildVersion.", + &options_.manifest_fixer_options.no_compile_sdk_metadata); AddOptionalFlagList("--fingerprint-prefix", "Fingerprint prefix to add to install constraints.", &options_.manifest_fixer_options.fingerprint_prefixes); AddOptionalSwitch("--shared-lib", "Generates a shared Android runtime library.", diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 53f0abee1cf2..c4f6e70c0cc9 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -719,7 +719,7 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { root->InsertChild(0, std::move(uses_sdk)); } - if (options_.compile_sdk_version) { + if (!options_.no_compile_sdk_metadata && options_.compile_sdk_version) { xml::Attribute* attr = root->FindOrCreateAttribute(xml::kSchemaAndroid, "compileSdkVersion"); // Make sure we un-compile the value if it was set to something else. @@ -731,10 +731,9 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { // Make sure we un-compile the value if it was set to something else. attr->compiled_value = {}; attr->value = options_.compile_sdk_version.value(); - } - if (options_.compile_sdk_version_codename) { + if (!options_.no_compile_sdk_metadata && options_.compile_sdk_version_codename) { xml::Attribute* attr = root->FindOrCreateAttribute(xml::kSchemaAndroid, "compileSdkVersionCodename"); diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h index 175ab6f1cd7b..42938a4f8176 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -67,11 +67,12 @@ struct ManifestFixerOptions { std::optional<std::string> revision_code_default; // The version of the framework being compiled against to set for 'android:compileSdkVersion' in - // the <manifest> tag. + // the <manifest> tag. Not used if no_compile_sdk_metadata is set. std::optional<std::string> compile_sdk_version; // The version codename of the framework being compiled against to set for - // 'android:compileSdkVersionCodename' in the <manifest> tag. + // 'android:compileSdkVersionCodename' in the <manifest> tag. Not used if no_compile_sdk_metadata + // is set. std::optional<std::string> compile_sdk_version_codename; // The fingerprint prefixes to be added to the <install-constraints> tag. @@ -87,6 +88,9 @@ struct ManifestFixerOptions { // Whether to replace the manifest version with the the command line version bool replace_version = false; + + // Whether to suppress `android:compileSdkVersion*` and `platformBuildVersion*` attributes. + bool no_compile_sdk_metadata = false; }; // Verifies that the manifest is correctly formed and inserts defaults where specified with diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 1b8f05b957a7..6151a8e910d9 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -892,6 +892,35 @@ TEST_F(ManifestFixerTest, InsertCompileSdkVersions) { EXPECT_THAT(attr->value, StrEq("P")); } +TEST_F(ManifestFixerTest, DoNotInsertCompileSdkVersions) { + std::string input = R"(<manifest package="com.pkg" />)"; + ManifestFixerOptions options; + options.no_compile_sdk_metadata = true; + options.compile_sdk_version = {"28"}; + options.compile_sdk_version_codename = {"P"}; + + std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options); + ASSERT_THAT(manifest, NotNull()); + + // There should be a declaration of kSchemaAndroid, even when the input + // didn't have one. + EXPECT_EQ(manifest->root->namespace_decls.size(), 1); + EXPECT_EQ(manifest->root->namespace_decls[0].prefix, "android"); + EXPECT_EQ(manifest->root->namespace_decls[0].uri, xml::kSchemaAndroid); + + xml::Attribute* attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersion"); + ASSERT_THAT(attr, IsNull()); + + attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersionCodename"); + ASSERT_THAT(attr, IsNull()); + + attr = manifest->root->FindAttribute("", "platformBuildVersionCode"); + ASSERT_THAT(attr, IsNull()); + + attr = manifest->root->FindAttribute("", "platformBuildVersionName"); + ASSERT_THAT(attr, IsNull()); +} + TEST_F(ManifestFixerTest, OverrideCompileSdkVersions) { std::string input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" |