diff options
175 files changed, 4900 insertions, 1173 deletions
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java index 051dde01c64e..b732da29b754 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java @@ -127,6 +127,7 @@ public class UserLifecycleTests { private BroadcastWaiter mBroadcastWaiter; private UserSwitchWaiter mUserSwitchWaiter; private String mUserSwitchTimeoutMs; + private String mDisableUserSwitchingDialogAnimations; private final BenchmarkRunner mRunner = new BenchmarkRunner(); @Rule @@ -153,16 +154,17 @@ public class UserLifecycleTests { Log.w(TAG, "WARNING: Tests are being run from user " + mAm.getCurrentUser() + " rather than the system user"); } - mUserSwitchTimeoutMs = setSystemProperty("debug.usercontroller.user_switch_timeout_ms", - "100000"); - if (TextUtils.isEmpty(mUserSwitchTimeoutMs)) { - mUserSwitchTimeoutMs = "invalid"; - } + mUserSwitchTimeoutMs = setSystemProperty( + "debug.usercontroller.user_switch_timeout_ms", "100000"); + mDisableUserSwitchingDialogAnimations = setSystemProperty( + "debug.usercontroller.disable_user_switching_dialog_animations", "true"); } @After public void tearDown() throws Exception { setSystemProperty("debug.usercontroller.user_switch_timeout_ms", mUserSwitchTimeoutMs); + setSystemProperty("debug.usercontroller.disable_user_switching_dialog_animations", + mDisableUserSwitchingDialogAnimations); mBroadcastWaiter.close(); mUserSwitchWaiter.close(); for (int userId : mUsersToRemove) { @@ -1538,7 +1540,7 @@ public class UserLifecycleTests { private String setSystemProperty(String name, String value) throws Exception { final String oldValue = ShellHelper.runShellCommand("getprop " + name); assertEquals("", ShellHelper.runShellCommand("setprop " + name + " " + value)); - return oldValue; + return TextUtils.firstNotEmpty(oldValue, "invalid"); } private void waitForBroadcastIdle() { diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 3772960e8ac4..df1b66612ea2 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -4873,8 +4873,7 @@ public class AlarmManagerService extends SystemService { } } if (wakeupUids.size() > 0 && mBatteryStatsInternal != null) { - mBatteryStatsInternal.noteCpuWakingActivity( - BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM, nowELAPSED, + mBatteryStatsInternal.noteWakingAlarmBatch(nowELAPSED, wakeupUids.toArray()); } deliverAlarmsLocked(triggerList, nowELAPSED); diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 31c02b886686..fa99b59888f5 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -2085,7 +2085,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * * @param uri The URI whose file is to be opened. * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" - * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details. + * or "rwt". Please note the exact implementation of these may differ for each + * Provider implementation - for example, "w" may or may not truncate. * * @return Returns a new ParcelFileDescriptor which you can use to access * the file. @@ -2147,7 +2148,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * * @param uri The URI whose file is to be opened. * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" - * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details. + * or "rwt". Please note the exact implementation of these may differ for each + * Provider implementation - for example, "w" may or may not truncate. * @param signal A signal to cancel the operation in progress, or * {@code null} if none. For example, if you are downloading a * file from the network to service a "rw" mode request, you @@ -2208,7 +2210,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * * @param uri The URI whose file is to be opened. * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" - * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details. + * or "rwt". Please note the exact implementation of these may differ for each + * Provider implementation - for example, "w" may or may not truncate. * * @return Returns a new AssetFileDescriptor which you can use to access * the file. @@ -2262,7 +2265,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * * @param uri The URI whose file is to be opened. * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" - * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details. + * or "rwt". Please note the exact implementation of these may differ for each + * Provider implementation - for example, "w" may or may not truncate. * @param signal A signal to cancel the operation in progress, or * {@code null} if none. For example, if you are downloading a * file from the network to service a "rw" mode request, you @@ -2294,7 +2298,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * * @param uri The URI to be opened. * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" - * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details. + * or "rwt". Please note the exact implementation of these may differ for each + * Provider implementation - for example, "w" may or may not truncate. * * @return Returns a new ParcelFileDescriptor that can be used by the * client to access the file. diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index feca7a022934..b2cd7e90f291 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -1536,7 +1536,8 @@ public abstract class ContentResolver implements ContentInterface { /** * Synonym for {@link #openOutputStream(Uri, String) - * openOutputStream(uri, "w")}. + * openOutputStream(uri, "w")}. Please note the implementation of "w" is up to each + * Provider implementation and it may or may not truncate. * * @param uri The desired URI. * @return an OutputStream or {@code null} if the provider recently crashed. @@ -1562,7 +1563,8 @@ public abstract class ContentResolver implements ContentInterface { * * @param uri The desired URI. * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" - * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details. + * or "rwt". Please note the exact implementation of these may differ for each + * Provider implementation - for example, "w" may or may not truncate. * @return an OutputStream or {@code null} if the provider recently crashed. * @throws FileNotFoundException if the provided URI could not be opened. * @see #openAssetFileDescriptor(Uri, String) @@ -1619,7 +1621,8 @@ public abstract class ContentResolver implements ContentInterface { * * @param uri The desired URI to open. * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" - * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details. + * or "rwt". Please note the exact implementation of these may differ for each + * Provider implementation - for example, "w" may or may not truncate. * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the * provider recently crashed. You own this descriptor and are responsible for closing it * when done. @@ -1662,7 +1665,8 @@ public abstract class ContentResolver implements ContentInterface { * * @param uri The desired URI to open. * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" - * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details. + * or "rwt". Please note the exact implementation of these may differ for each + * Provider implementation - for example, "w" may or may not truncate. * @param cancellationSignal A signal to cancel the operation in progress, * or null if none. If the operation is canceled, then * {@link OperationCanceledException} will be thrown. @@ -1756,7 +1760,8 @@ public abstract class ContentResolver implements ContentInterface { * * @param uri The desired URI to open. * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" - * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details. + * or "rwt". Please note the exact implementation of these may differ for each + * Provider implementation - for example, "w" may or may not truncate. * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the * provider recently crashed. You own this descriptor and are responsible for closing it * when done. @@ -1810,7 +1815,8 @@ public abstract class ContentResolver implements ContentInterface { * * @param uri The desired URI to open. * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" - * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details. + * or "rwt". Please note "w" is write only and "wt" is write and truncate. + * See{@link ParcelFileDescriptor#parseMode} for more details. * @param cancellationSignal A signal to cancel the operation in progress, or null if * none. If the operation is canceled, then * {@link OperationCanceledException} will be thrown. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2b73afcd740f..c221d724c5a2 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -7613,7 +7613,7 @@ public abstract class Context { * the device association is changed by the system. * <p> * The callback can be called when an app is moved to a different device and the {@code Context} - * is not explicily associated with a specific device. + * is not explicitly associated with a specific device. * </p> * <p> When an application receives a device id update callback, this Context is guaranteed to * also have an updated display ID(if any) and {@link Configuration}. diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 3bc240c89e58..02212968cdb0 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -348,6 +348,13 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan return; } + if (hardwareAuthToken == null) { + callback.onEnrollmentError(FACE_ERROR_UNABLE_TO_PROCESS, + getErrorString(mContext, FACE_ERROR_UNABLE_TO_PROCESS, + 0 /* vendorCode */)); + return; + } + if (getEnrolledFaces(userId).size() >= mContext.getResources().getInteger(R.integer.config_faceMaxTemplatesPerUser)) { callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE, diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 84de1a6043ab..01977f6195ff 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -712,6 +712,13 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing return; } + if (hardwareAuthToken == null) { + callback.onEnrollmentError(FINGERPRINT_ERROR_UNABLE_TO_PROCESS, + getErrorString(mContext, FINGERPRINT_ERROR_UNABLE_TO_PROCESS, + 0 /* vendorCode */)); + return; + } + if (mService != null) { try { mEnrollmentCallback = callback; diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index a52e3d49148d..2c31e32f2ef8 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -213,6 +213,13 @@ public class GraphicsEnvironment { } /** + * Switch the system to use ANGLE as the default GLES driver. + */ + public void toggleAngleAsSystemDriver(boolean enabled) { + nativeToggleAngleAsSystemDriver(enabled); + } + + /** * Query to determine if the Game Mode has enabled ANGLE. */ private boolean isAngleEnabledByGameMode(Context context, String packageName) { @@ -992,6 +999,7 @@ public class GraphicsEnvironment { String appPackage, boolean angleIsSystemDriver, String legacyDriverName); private static native boolean getShouldUseAngle(String packageName); private static native boolean setInjectLayersPrSetDumpable(); + private static native void nativeToggleAngleAsSystemDriver(boolean enabled); /** * Hint for GraphicsEnvironment that an activity is launching on the process. diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 9d3d70d4a8f7..8d84e4413635 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -2738,7 +2738,12 @@ public abstract class WallpaperService extends Service { engineWrapper.destroy(); } mActiveEngines.clear(); - mBackgroundThread.quitSafely(); + 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 + // if here we don't take into consideration this scenario. + mBackgroundThread.quitSafely(); + } Trace.endSection(); } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index baefd853876b..60529c72afae 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -2188,7 +2188,7 @@ public final class Display { */ @NonNull public float[] getAlternativeRefreshRates() { - return mAlternativeRefreshRates; + return Arrays.copyOf(mAlternativeRefreshRates, mAlternativeRefreshRates.length); } /** @@ -2197,7 +2197,7 @@ public final class Display { @NonNull @HdrCapabilities.HdrType public int[] getSupportedHdrTypes() { - return mSupportedHdrTypes; + return Arrays.copyOf(mSupportedHdrTypes, mSupportedHdrTypes.length); } /** @@ -2497,8 +2497,10 @@ public final class Display { * @deprecated use {@link Display#getMode()} * and {@link Mode#getSupportedHdrTypes()} instead */ - public @HdrType int[] getSupportedHdrTypes() { - return mSupportedHdrTypes; + @Deprecated + @HdrType + public int[] getSupportedHdrTypes() { + return Arrays.copyOf(mSupportedHdrTypes, mSupportedHdrTypes.length); } /** * Returns the desired content max luminance data in cd/m2 for this display. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 2b29e7878a5d..6bd9538a9f65 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -885,6 +885,16 @@ public final class ViewRootImpl implements ViewParent, */ private SurfaceSyncGroup mActiveSurfaceSyncGroup; + + private final Object mPreviousSyncSafeguardLock = new Object(); + + /** + * Wraps the TransactionCommitted callback for the previous SSG so it can be added to the next + * SSG if started before previous has completed. + */ + @GuardedBy("mPreviousSyncSafeguardLock") + private SurfaceSyncGroup mPreviousSyncSafeguard; + private static final Object sSyncProgressLock = new Object(); // The count needs to be static since it's used to enable or disable RT animations which is // done at a global level per process. If any VRI syncs are in progress, we can't enable RT @@ -11329,6 +11339,61 @@ public final class ViewRootImpl implements ViewParent, }); } + /** + * This code will ensure that if multiple SurfaceSyncGroups are created for the same + * ViewRootImpl the SurfaceSyncGroups will maintain an order. The scenario that could occur + * is the following: + * <p> + * 1. SSG1 is created that includes the target VRI. There could be other VRIs in SSG1 + * 2. The target VRI draws its frame and marks its own active SSG as ready, but SSG1 is still + * waiting on other things in the SSG + * 3. Another SSG2 is created for the target VRI. The second frame renders and marks its own + * second SSG as complete. SSG2 has nothing else to wait on, so it will apply at this point, + * even though SSG1 has not finished. + * 4. Frame2 will get to SF first and Frame1 will later get to SF when SSG1 completes. + * <p> + * The code below ensures the SSGs that contains the VRI maintain an order. We create a new SSG + * that's a safeguard SSG. Its only job is to prevent the next active SSG from completing. + * The current active SSG for VRI will add a transaction committed callback and when that's + * invoked, it will mark the safeguard SSG as ready. If a new request to create a SSG comes + * in and the safeguard SSG is not null, it's added as part of the new active SSG. A new + * safeguard SSG is created to correspond to the new active SSG. This creates a chain to + * ensure the latter SSG always waits for the former SSG's transaction to get to SF. + */ + private void safeguardOverlappingSyncs(SurfaceSyncGroup activeSurfaceSyncGroup) { + SurfaceSyncGroup safeguardSsg = new SurfaceSyncGroup("VRI-Safeguard"); + // Always disable timeout on the safeguard sync + safeguardSsg.toggleTimeout(false /* enable */); + synchronized (mPreviousSyncSafeguardLock) { + if (mPreviousSyncSafeguard != null) { + activeSurfaceSyncGroup.add(mPreviousSyncSafeguard, null /* runnable */); + // Temporarily disable the timeout on the SSG that will contain the buffer. This + // is to ensure we don't timeout the active SSG before the previous one completes to + // ensure the order is maintained. The previous SSG has a timeout on its own SSG + // so it's guaranteed to complete. + activeSurfaceSyncGroup.toggleTimeout(false /* enable */); + mPreviousSyncSafeguard.addSyncCompleteCallback(mSimpleExecutor, () -> { + // Once we receive that the previous sync guard has been invoked, we can re-add + // the timeout on the active sync to ensure we eventually complete so it's not + // stuck permanently. + activeSurfaceSyncGroup.toggleTimeout(true /*enable */); + }); + } + mPreviousSyncSafeguard = safeguardSsg; + } + + Transaction t = new Transaction(); + t.addTransactionCommittedListener(mSimpleExecutor, () -> { + safeguardSsg.markSyncReady(); + synchronized (mPreviousSyncSafeguardLock) { + if (mPreviousSyncSafeguard == safeguardSsg) { + mPreviousSyncSafeguard = null; + } + } + }); + activeSurfaceSyncGroup.addTransaction(t); + } + @Override public SurfaceSyncGroup getOrCreateSurfaceSyncGroup() { boolean newSyncGroup = false; @@ -11355,6 +11420,7 @@ public final class ViewRootImpl implements ViewParent, mHandler.post(runnable); } }); + safeguardOverlappingSyncs(mActiveSurfaceSyncGroup); updateSyncInProgressCount(mActiveSurfaceSyncGroup); newSyncGroup = true; } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index ddcb431ee06d..5b6df1cb754e 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -467,7 +467,7 @@ public interface WindowManager extends ViewManager { * implementation. * @hide */ - int TRANSIT_FIRST_CUSTOM = 13; + int TRANSIT_FIRST_CUSTOM = 1000; /** * @hide diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index 13ac329815f5..fa0052cf664a 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -840,6 +840,7 @@ public final class AccessibilityWindowInfo implements Parcelable { mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; mTitle = null; mTransitionTime = 0; + mLocales = LocaleList.getEmptyLocaleList(); } /** diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java index b3d512488239..18405676dbd8 100644 --- a/core/java/android/window/SurfaceSyncGroup.java +++ b/core/java/android/window/SurfaceSyncGroup.java @@ -38,6 +38,7 @@ import android.view.SurfaceView; import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; @@ -63,7 +64,12 @@ public final class SurfaceSyncGroup { private static final int MAX_COUNT = 100; private static final AtomicInteger sCounter = new AtomicInteger(0); - private static final int TRANSACTION_READY_TIMEOUT = 1000 * Build.HW_TIMEOUT_MULTIPLIER; + + /** + * @hide + */ + @VisibleForTesting + public static final int TRANSACTION_READY_TIMEOUT = 1000 * Build.HW_TIMEOUT_MULTIPLIER; private static Supplier<Transaction> sTransactionFactory = Transaction::new; @@ -123,6 +129,14 @@ public final class SurfaceSyncGroup { @GuardedBy("mLock") private boolean mTimeoutAdded; + /** + * Disable the timeout for this SSG so it will never be set until there's an explicit call to + * add a timeout. + */ + @GuardedBy("mLock") + private boolean mTimeoutDisabled; + + private static boolean isLocalBinder(IBinder binder) { return !(binder instanceof BinderProxy); } @@ -223,6 +237,10 @@ public final class SurfaceSyncGroup { */ public void addSyncCompleteCallback(Executor executor, Runnable runnable) { synchronized (mLock) { + if (mFinished) { + executor.execute(runnable); + return; + } mSyncCompleteCallbacks.add(new Pair<>(executor, runnable)); } } @@ -768,6 +786,21 @@ public final class SurfaceSyncGroup { } } + /** + * @hide + */ + public void toggleTimeout(boolean enable) { + synchronized (mLock) { + mTimeoutDisabled = !enable; + if (mTimeoutAdded && !enable) { + mHandler.removeCallbacksAndMessages(this); + mTimeoutAdded = false; + } else if (!mTimeoutAdded && enable) { + addTimeout(); + } + } + } + private void addTimeout() { synchronized (sHandlerThreadLock) { if (sHandlerThread == null) { @@ -777,7 +810,7 @@ public final class SurfaceSyncGroup { } synchronized (mLock) { - if (mTimeoutAdded) { + if (mTimeoutAdded || mTimeoutDisabled) { // We only need one timeout for the entire SurfaceSyncGroup since we just want to // ensure it doesn't stay stuck forever. return; diff --git a/core/java/android/window/TaskConstants.java b/core/java/android/window/TaskConstants.java index 3a04198a3add..e18fd50dc38e 100644 --- a/core/java/android/window/TaskConstants.java +++ b/core/java/android/window/TaskConstants.java @@ -47,37 +47,31 @@ public class TaskConstants { -2 * TASK_CHILD_LAYER_REGION_SIZE; /** - * When a unresizable app is moved in the different configuration, a restart button appears - * allowing to adapt (~resize) app to the new configuration mocks. + * Compat UI components: reachability education, size compat restart + * button, letterbox education, restart dialog. * @hide */ - public static final int TASK_CHILD_LAYER_SIZE_COMPAT_RESTART_BUTTON = - TASK_CHILD_LAYER_REGION_SIZE; + public static final int TASK_CHILD_LAYER_COMPAT_UI = TASK_CHILD_LAYER_REGION_SIZE; - /** - * Shown the first time an app is opened in size compat mode in landscape. - * @hide - */ - public static final int TASK_CHILD_LAYER_LETTERBOX_EDUCATION = 2 * TASK_CHILD_LAYER_REGION_SIZE; /** * Captions, window frames and resize handlers around task windows. * @hide */ - public static final int TASK_CHILD_LAYER_WINDOW_DECORATIONS = 3 * TASK_CHILD_LAYER_REGION_SIZE; + public static final int TASK_CHILD_LAYER_WINDOW_DECORATIONS = 2 * TASK_CHILD_LAYER_REGION_SIZE; /** * Overlays the task when going into PIP w/ gesture navigation. * @hide */ public static final int TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY = - 4 * TASK_CHILD_LAYER_REGION_SIZE; + 3 * TASK_CHILD_LAYER_REGION_SIZE; /** * Allows other apps to add overlays on the task (i.e. game dashboard) * @hide */ - public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 5 * TASK_CHILD_LAYER_REGION_SIZE; + public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 4 * TASK_CHILD_LAYER_REGION_SIZE; /** * Z-orders of task child layers other than activities, task fragments and layers interleaved @@ -87,8 +81,7 @@ public class TaskConstants { @IntDef({ TASK_CHILD_LAYER_TASK_BACKGROUND, TASK_CHILD_LAYER_LETTERBOX_BACKGROUND, - TASK_CHILD_LAYER_SIZE_COMPAT_RESTART_BUTTON, - TASK_CHILD_LAYER_LETTERBOX_EDUCATION, + TASK_CHILD_LAYER_COMPAT_UI, TASK_CHILD_LAYER_WINDOW_DECORATIONS, TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY, TASK_CHILD_LAYER_TASK_OVERLAY diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index 55aa7117221e..4474d4cabacb 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -52,7 +52,7 @@ static JNIEnv* getenv(JavaVM* vm) { return env; } - struct { +struct { jmethodID onTransactionHang; } gTransactionHangCallback; @@ -72,12 +72,14 @@ public: } void onTransactionHang(const std::string& reason) { - if (mTransactionHangObject) { - JNIEnv* env = getenv(mVm); - ScopedLocalRef<jstring> jReason(env, env->NewStringUTF(reason.c_str())); - getenv(mVm)->CallVoidMethod(mTransactionHangObject, - gTransactionHangCallback.onTransactionHang, jReason.get()); + if (!mTransactionHangObject) { + return; } + JNIEnv* env = getenv(mVm); + ScopedLocalRef<jstring> jReason(env, env->NewStringUTF(reason.c_str())); + getenv(mVm)->CallVoidMethod(mTransactionHangObject, + gTransactionHangCallback.onTransactionHang, jReason.get()); + DieIfException(env, "Uncaught exception in TransactionHangCallback."); } private: diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index 78e2d3164993..d9152d61ed8a 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -122,6 +122,10 @@ void hintActivityLaunch_native(JNIEnv* env, jobject clazz) { android::GraphicsEnv::getInstance().hintActivityLaunch(); } +void nativeToggleAngleAsSystemDriver_native(JNIEnv* env, jobject clazz, jboolean enabled) { + android::GraphicsEnv::getInstance().nativeToggleAngleAsSystemDriver(enabled); +} + const JNINativeMethod g_methods[] = { {"isDebuggable", "()Z", reinterpret_cast<void*>(isDebuggable_native)}, {"setDriverPathAndSphalLibraries", "(Ljava/lang/String;Ljava/lang/String;)V", @@ -143,6 +147,8 @@ const JNINativeMethod g_methods[] = { {"setDebugLayersGLES", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayersGLES_native)}, {"hintActivityLaunch", "()V", reinterpret_cast<void*>(hintActivityLaunch_native)}, + {"nativeToggleAngleAsSystemDriver", "(Z)V", + reinterpret_cast<void*>(nativeToggleAngleAsSystemDriver_native)}, }; const char* const kGraphicsEnvironmentName = "android/os/GraphicsEnvironment"; diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index e42c6f107e6d..8e96ac136370 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -285,6 +285,7 @@ public: JNIEnv* env = getenv(); env->CallVoidMethod(mTransactionCommittedListenerObject, gTransactionCommittedListenerClassInfo.onTransactionCommitted); + DieIfException(env, "Uncaught exception in TransactionCommittedListener."); } static void transactionCallbackThunk(void* context, nsecs_t /*latchTime*/, @@ -325,6 +326,7 @@ public: binder::Status onWindowInfosReported() override { JNIEnv* env = getenv(); env->CallVoidMethod(mListener, gRunnableClassInfo.run); + DieIfException(env, "Uncaught exception in WindowInfosReportedListener."); return binder::Status::ok(); } @@ -356,6 +358,7 @@ public: env->CallVoidMethod(mTrustedPresentationCallback, gTrustedPresentationCallbackClassInfo.onTrustedPresentationChanged, inTrustedPresentationState); + DieIfException(env, "Uncaught exception in TrustedPresentationCallback."); } void addCallbackRef(const sp<SurfaceComposerClient::PresentationCallbackRAII>& callbackRef) { diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h index b85a42529fb6..210dc895d674 100644 --- a/core/jni/core_jni_helpers.h +++ b/core/jni/core_jni_helpers.h @@ -134,6 +134,15 @@ static inline JNIEnv* GetOrAttachJNIEnvironment(JavaVM* jvm, jint version = JNI_ return env; } +static inline void DieIfException(JNIEnv* env, const char* message) { + if (env->ExceptionCheck()) { + jnihelp::ExpandableString summary; + jnihelp::ExpandableStringInitialize(&summary); + jnihelp::GetStackTraceOrSummary(env, nullptr, &summary); + LOG_ALWAYS_FATAL("%s\n%s", message, summary.data); + } +} + } // namespace android #endif // CORE_JNI_HELPERS diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto index 25985ebc551a..c3cbc9102b27 100644 --- a/core/proto/android/server/windowmanagertransitiontrace.proto +++ b/core/proto/android/server/windowmanagertransitiontrace.proto @@ -55,12 +55,14 @@ message Transition { optional int64 finish_time_ns = 6; // consider aborted if not provided required int32 type = 7; repeated Target targets = 8; + optional int32 flags = 9; } message Target { required int32 mode = 1; required int32 layer_id = 2; optional int32 window_id = 3; // Not dumped in always on tracing + optional int32 flags = 4; } message TransitionState { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e216f88d3c4d..11fcd1e248db 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7629,6 +7629,13 @@ <permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE" android:protectionLevel="signature|module" /> + <!-- @hide Allows the settings app to access GPU service APIs". + <p>Not for use by third-party applications. + <p>Protection level: signature + --> + <permission android:name="android.permission.ACCESS_GPU_SERVICE" + android:protectionLevel="signature" /> + <!-- @hide Allows an application to get type of any provider uri. <p>Not for use by third-party applications. <p>Protection level: signature diff --git a/core/res/res/drawable/loading_spinner.xml b/core/res/res/drawable/loading_spinner.xml new file mode 100644 index 000000000000..49603d857a1a --- /dev/null +++ b/core/res/res/drawable/loading_spinner.xml @@ -0,0 +1,55 @@ +<!-- 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. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:drawable"> + <vector android:height="230dp" android:width="230dp" android:viewportHeight="230" + android:viewportWidth="230"> + <group android:name="_R_G"> + <group android:name="_R_G_L_0_G" android:translateX="100.621" + android:translateY="102.621"> + <path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#ffffff" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="8" android:strokeAlpha="1" android:trimPathStart="0" + android:trimPathEnd="0" android:trimPathOffset="0" + android:pathData=" M14.38 -93.62 C72.88,-93.62 120.38,-46.12 120.38,12.38 C120.38,70.88 72.88,118.38 14.38,118.38 C-44.12,118.38 -91.62,70.88 -91.62,12.38 C-91.62,-46.12 -44.12,-93.62 14.38,-93.62c "/> + </group> + </group> + <group android:name="time_group"/> + </vector> + </aapt:attr> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="trimPathEnd" android:duration="350" + android:startOffset="0" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.4,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" android:duration="517" + android:startOffset="0" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"/> + </set> + </aapt:attr> + </target> +</animated-vector>
\ No newline at end of file diff --git a/core/res/res/layout/user_switching_dialog.xml b/core/res/res/layout/user_switching_dialog.xml index 2e041f5f2be2..496179aefc91 100644 --- a/core/res/res/layout/user_switching_dialog.xml +++ b/core/res/res/layout/user_switching_dialog.xml @@ -14,17 +14,48 @@ See the License for the specific language governing permissions and limitations under the License. --> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/content" + android:background="?attr/colorBackground" + android:layout_width="match_parent" + android:layout_height="match_parent"> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/message" - style="?attr/textAppearanceListItem" - android:background="?attr/colorSurface" + <LinearLayout android:layout_width="wrap_content" - android:layout_height="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" android:gravity="center" - android:drawablePadding="12dp" - android:drawableTint="?attr/textColorPrimary" - android:paddingStart="?attr/dialogPreferredPadding" - android:paddingEnd="?attr/dialogPreferredPadding" - android:paddingTop="24dp" - android:paddingBottom="24dp" /> + android:orientation="vertical" + android:paddingBottom="77dp"> + + <RelativeLayout + android:layout_width="242dp" + android:layout_height="242dp" + android:layout_gravity="center"> + + <ImageView + android:id="@+id/icon" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="26dp" /> + + <ImageView + android:id="@+id/progress_circular" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="6dp" + android:src="@drawable/loading_spinner" /> + + </RelativeLayout> + + <TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/message" + style="?attr/textAppearanceListItem" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="20sp" + android:textAlignment="center" + android:drawableTint="?attr/textColorPrimary" /> + + </LinearLayout> +</FrameLayout> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5544701d9325..c5f7ea6501ff 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1917,7 +1917,8 @@ <string name="config_defaultNetworkRecommendationProviderPackage" translatable="false"></string> <!-- The package name of the default search selector app. Must be granted the POST_NOTIFICATIONS - permission. + permission, and notifications from this app must be given the notification flag + FLAG_NO_DISMISS if the notification requests FLAG_ONGOING_EVENT. --> <string name="config_defaultSearchSelectorPackageName" translatable="false"></string> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 3ea15924e96d..34b4c5177852 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -91,8 +91,8 @@ android_test { ":BstatsTestApp", ":BinderDeathRecipientHelperApp1", ":BinderDeathRecipientHelperApp2", + ":com.android.cts.helpers.aosp", ], - required: ["com.android.cts.helpers.aosp"], } // Rules to copy all the test apks to the intermediate raw resource directory diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 4f91e7a3545a..fb0f3d419002 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -71,6 +71,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.content.ReferrerIntent; import org.junit.After; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -95,7 +96,8 @@ public class ActivityThreadTest { // few sequence numbers the framework used to launch the test activity. private static final int BASE_SEQ = 10000; - private final ActivityTestRule<TestActivity> mActivityTestRule = + @Rule + public final ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */, false /* launchActivity */); diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java index 1075d44b4017..07dec5d9e222 100644 --- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java +++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java @@ -17,6 +17,7 @@ package android.hardware.face; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS; import static com.google.common.truth.Truth.assertThat; @@ -178,6 +179,18 @@ public class FaceManagerTest { verify(mEnrollmentCallback).onEnrollmentError(eq(FACE_ERROR_HW_UNAVAILABLE), anyString()); } + @Test + public void enrollment_errorWhenHardwareAuthTokenIsNull() throws RemoteException { + initializeProperties(); + mFaceManager.enroll(USER_ID, null, + new CancellationSignal(), mEnrollmentCallback, null /* disabledFeatures */); + + verify(mEnrollmentCallback).onEnrollmentError(eq(FACE_ERROR_UNABLE_TO_PROCESS), + anyString()); + verify(mService, never()).enroll(eq(USER_ID), any(), any(), + any(), anyString(), any(), any(), anyBoolean()); + } + private void initializeProperties() throws RemoteException { verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture()); diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java index 5058065710be..625e2e3723a7 100644 --- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java +++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java @@ -17,12 +17,14 @@ package android.hardware.fingerprint; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -70,6 +72,8 @@ public class FingerprintManagerTest { private IFingerprintService mService; @Mock private FingerprintManager.AuthenticationCallback mAuthCallback; + @Mock + private FingerprintManager.EnrollmentCallback mEnrollCallback; @Captor private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mCaptor; @@ -149,4 +153,17 @@ public class FingerprintManagerTest { verify(mAuthCallback).onAuthenticationError(eq(FINGERPRINT_ERROR_HW_UNAVAILABLE), any()); } + + @Test + public void enrollment_errorWhenHardwareAuthTokenIsNull() throws RemoteException { + verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture()); + + mCaptor.getValue().onAllAuthenticatorsRegistered(mProps); + mFingerprintManager.enroll(null, new CancellationSignal(), USER_ID, + mEnrollCallback, FingerprintManager.ENROLL_ENROLL); + + verify(mEnrollCallback).onEnrollmentError(eq(FINGERPRINT_ERROR_UNABLE_TO_PROCESS), + anyString()); + verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt()); + } } diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java index 4a12bb374cb2..5e617fd59946 100644 --- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java +++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java @@ -460,6 +460,36 @@ public class DisplayTest { assertArrayEquals(sortedHdrTypes, displayMode.getSupportedHdrTypes()); } + @Test + public void testGetSupportedHdrTypesReturnsCopy() { + int[] hdrTypes = new int[]{1, 2, 3}; + Display.Mode displayMode = new Display.Mode(0, 0, 0, 0, new float[0], hdrTypes); + + int[] hdrTypesCopy = displayMode.getSupportedHdrTypes(); + hdrTypesCopy[0] = 0; + assertArrayEquals(hdrTypes, displayMode.getSupportedHdrTypes()); + } + + @Test + public void testGetAlternativeRefreshRatesReturnsCopy() { + float[] alternativeRates = new float[]{1.0f, 2.0f}; + Display.Mode displayMode = new Display.Mode(0, 0, 0, 0, alternativeRates, new int[0]); + + float[] alternativeRatesCopy = displayMode.getAlternativeRefreshRates(); + alternativeRatesCopy[0] = 0.0f; + assertArrayEquals(alternativeRates, displayMode.getAlternativeRefreshRates(), 0.0f); + } + + @Test + public void testHdrCapabilitiesGetSupportedHdrTypesReturnsCopy() { + int[] hdrTypes = new int[]{1, 2, 3}; + Display.HdrCapabilities hdrCapabilities = new Display.HdrCapabilities(hdrTypes, 0, 0, 0); + + int[] hdrTypesCopy = hdrCapabilities.getSupportedHdrTypes(); + hdrTypesCopy[0] = 0; + assertArrayEquals(hdrTypes, hdrCapabilities.getSupportedHdrTypes()); + } + // Given rotated display dimensions, calculate the letterboxed app bounds. private static Rect buildAppBounds(int displayWidth, int displayHeight) { final int midWidth = displayWidth / 2; diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 7434cb02cc7c..549ac5858d1d 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -3805,6 +3805,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, + "1463355909": { + "message": "Queueing legacy sync-set: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "1469310004": { "message": " SKIP: common mode mismatch. was %s", "level": "VERBOSE", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 1b20f67e42ab..f8f8897e41d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1068,6 +1068,7 @@ public class BubbleStackView extends FrameLayout // We need to be Z ordered on top in order for alpha animations to work. mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true); mExpandedBubble.getExpandedView().setAnimating(true); + mExpandedViewContainer.setVisibility(VISIBLE); } } @@ -2922,14 +2923,15 @@ public class BubbleStackView extends FrameLayout final float targetX = isLtr ? mTempRect.left - margin : mTempRect.right + margin - mManageMenu.getWidth(); - final float targetY = mTempRect.bottom - mManageMenu.getHeight(); + final float menuHeight = getVisibleManageMenuHeight(); + final float targetY = mTempRect.bottom - menuHeight; final float xOffsetForAnimation = (isLtr ? 1 : -1) * mManageMenu.getWidth() / 4f; if (show) { mManageMenu.setScaleX(0.5f); mManageMenu.setScaleY(0.5f); mManageMenu.setTranslationX(targetX - xOffsetForAnimation); - mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4f); + mManageMenu.setTranslationY(targetY + menuHeight / 4f); mManageMenu.setAlpha(0f); PhysicsAnimator.getInstance(mManageMenu) @@ -2955,7 +2957,7 @@ public class BubbleStackView extends FrameLayout .spring(DynamicAnimation.SCALE_X, 0.5f) .spring(DynamicAnimation.SCALE_Y, 0.5f) .spring(DynamicAnimation.TRANSLATION_X, targetX - xOffsetForAnimation) - .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4f) + .spring(DynamicAnimation.TRANSLATION_Y, targetY + menuHeight / 4f) .withEndActions(() -> { mManageMenu.setVisibility(View.INVISIBLE); if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { @@ -3115,7 +3117,7 @@ public class BubbleStackView extends FrameLayout mAnimatingOutBubbleBuffer.getColorSpace()); mAnimatingOutSurfaceView.setAlpha(1f); - mExpandedViewContainer.setVisibility(View.GONE); + mExpandedViewContainer.setVisibility(View.INVISIBLE); mSurfaceSynchronizer.syncSurfaceAndRun(() -> { post(() -> { @@ -3145,9 +3147,6 @@ public class BubbleStackView extends FrameLayout int[] paddings = mPositioner.getExpandedViewContainerPadding( mStackAnimationController.isStackOnLeftSide(), isOverflowExpanded); mExpandedViewContainer.setPadding(paddings[0], paddings[1], paddings[2], paddings[3]); - if (mIsExpansionAnimating) { - mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); - } if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { PointF p = mPositioner.getExpandedBubbleXY(getBubbleIndex(mExpandedBubble), getState()); @@ -3272,6 +3271,24 @@ public class BubbleStackView extends FrameLayout } /** + * Menu height calculated for animation + * It takes into account view visibility to get the correct total height + */ + private float getVisibleManageMenuHeight() { + float menuHeight = 0; + + for (int i = 0; i < mManageMenu.getChildCount(); i++) { + View subview = mManageMenu.getChildAt(i); + + if (subview.getVisibility() == VISIBLE) { + menuHeight += subview.getHeight(); + } + } + + return menuHeight; + } + + /** * @return the normalized x-axis position of the bubble stack rounded to 4 decimal places. */ public float getNormalizedXPosition() { 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 170c0ee91b40..659229228a57 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 @@ -20,6 +20,7 @@ import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; 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; @@ -46,11 +47,6 @@ import java.util.function.Consumer; */ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { - /** - * The Compat UI should be below the Letterbox Education. - */ - private static final int Z_ORDER = LetterboxEduWindowManager.Z_ORDER - 1; - private final CompatUICallback mCallback; private final CompatUIConfiguration mCompatUIConfiguration; @@ -92,7 +88,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { @Override protected int getZOrder() { - return Z_ORDER; + return TASK_CHILD_LAYER_COMPAT_UI + 1; } @Override 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 0c21c8ccd686..959c50d5c640 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 @@ -17,6 +17,7 @@ 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; @@ -46,12 +47,6 @@ import java.util.function.Consumer; */ class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { - /** - * The Letterbox Education should be the topmost child of the Task in case there can be more - * than one child. - */ - public static final int Z_ORDER = Integer.MAX_VALUE; - private final DialogAnimationController<LetterboxEduDialogLayout> mAnimationController; private final Transitions mTransitions; @@ -118,7 +113,7 @@ class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { @Override protected int getZOrder() { - return Z_ORDER; + return TASK_CHILD_LAYER_COMPAT_UI + 2; } @Override 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 b6e396d12512..a18ab9154e01 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 @@ -18,6 +18,7 @@ package com.android.wm.shell.compatui; 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; @@ -41,11 +42,6 @@ import com.android.wm.shell.common.SyncTransactionQueue; */ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { - /** - * The Compat UI should be below the Letterbox Education. - */ - private static final int Z_ORDER = LetterboxEduWindowManager.Z_ORDER - 1; - // The time to wait before hiding the education private static final long DISAPPEAR_DELAY_MS = 4000L; @@ -102,7 +98,7 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { @Override protected int getZOrder() { - return Z_ORDER; + return TASK_CHILD_LAYER_COMPAT_UI + 1; } @Override 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 aab123a843ea..51e5141a28af 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 @@ -17,6 +17,7 @@ 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; @@ -47,12 +48,6 @@ import java.util.function.Consumer; */ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { - /** - * The restart dialog should be the topmost child of the Task in case there can be more - * than one child. - */ - private static final int Z_ORDER = Integer.MAX_VALUE; - private final DialogAnimationController<RestartDialogLayout> mAnimationController; private final Transitions mTransitions; @@ -112,7 +107,7 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { @Override protected int getZOrder() { - return Z_ORDER; + return TASK_CHILD_LAYER_COMPAT_UI + 2; } @Override @@ -170,10 +165,10 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { final Rect taskBounds = getTaskBounds(); final Rect taskStableBounds = getTaskStableBounds(); - - marginParams.topMargin = taskStableBounds.top - taskBounds.top + mDialogVerticalMargin; - marginParams.bottomMargin = - taskBounds.bottom - taskStableBounds.bottom + mDialogVerticalMargin; + // only update margins based on taskbar insets + marginParams.topMargin = mDialogVerticalMargin; + marginParams.bottomMargin = taskBounds.bottom - taskStableBounds.bottom + + mDialogVerticalMargin; dialogContainer.setLayoutParams(marginParams); } 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 b6216b340b38..566c130c7573 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 @@ -1690,8 +1690,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // Similar to auto-enter-pip transition, we use content overlay when there is no // source rect hint to enter PiP use bounds animation. if (sourceHintRect == null) { + // We use content overlay when there is no source rect hint to enter PiP use bounds + // animation. + // TODO(b/272819817): cleanup the null-check and extra logging. + final boolean hasTopActivityInfo = mTaskInfo.topActivityInfo != null; + if (!hasTopActivityInfo) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "%s: TaskInfo.topActivityInfo is null", TAG); + } if (SystemProperties.getBoolean( - "persist.wm.debug.enable_pip_app_icon_overlay", true)) { + "persist.wm.debug.enable_pip_app_icon_overlay", true) + && hasTopActivityInfo) { animator.setAppIconContentOverlay( mContext, currentBounds, mTaskInfo.topActivityInfo, mPipBoundsState.getLauncherState().getAppIconSizePx()); 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 bcc37baa5b00..0f4645c0fdab 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 @@ -122,14 +122,14 @@ public class TransitionAnimationHelper { ? R.styleable.WindowAnimation_taskToFrontEnterAnimation : R.styleable.WindowAnimation_taskToFrontExitAnimation; } else if (type == TRANSIT_CLOSE) { - if (isTask) { + if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) { + translucent = true; + } + if (isTask && !translucent) { animAttr = enter ? R.styleable.WindowAnimation_taskCloseEnterAnimation : R.styleable.WindowAnimation_taskCloseExitAnimation; } else { - if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) { - translucent = true; - } animAttr = enter ? R.styleable.WindowAnimation_activityCloseEnterAnimation : R.styleable.WindowAnimation_activityCloseExitAnimation; diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java index 7ac84460463e..f4caad727407 100644 --- a/media/java/android/media/ImageUtils.java +++ b/media/java/android/media/ImageUtils.java @@ -20,6 +20,7 @@ import android.graphics.ImageFormat; import android.graphics.PixelFormat; import android.hardware.HardwareBuffer; import android.media.Image.Plane; +import android.util.Log; import android.util.Size; import libcore.io.Memory; @@ -30,6 +31,7 @@ import java.nio.ByteBuffer; * Package private utility class for hosting commonly used Image related methods. */ class ImageUtils { + private static final String IMAGEUTILS_LOG_TAG = "ImageUtils"; /** * Only a subset of the formats defined in @@ -266,11 +268,15 @@ class ImageUtils { break; case PixelFormat.RGBA_8888: case PixelFormat.RGBX_8888: + case PixelFormat.RGBA_1010102: estimatedBytePerPixel = 4.0; break; default: - throw new UnsupportedOperationException( - String.format("Invalid format specified %d", format)); + if (Log.isLoggable(IMAGEUTILS_LOG_TAG, Log.VERBOSE)) { + Log.v(IMAGEUTILS_LOG_TAG, "getEstimatedNativeAllocBytes() uses default" + + "estimated native allocation size."); + } + estimatedBytePerPixel = 1.0; } return (int)(width * height * estimatedBytePerPixel * numImages); @@ -295,6 +301,7 @@ class ImageUtils { } case PixelFormat.RGB_565: case PixelFormat.RGBA_8888: + case PixelFormat.RGBA_1010102: case PixelFormat.RGBX_8888: case PixelFormat.RGB_888: case ImageFormat.JPEG: @@ -312,8 +319,11 @@ class ImageUtils { case ImageFormat.PRIVATE: return new Size(0, 0); default: - throw new UnsupportedOperationException( - String.format("Invalid image format %d", image.getFormat())); + if (Log.isLoggable(IMAGEUTILS_LOG_TAG, Log.VERBOSE)) { + Log.v(IMAGEUTILS_LOG_TAG, "getEffectivePlaneSizeForImage() uses" + + "image's width and height for plane size."); + } + return new Size(image.getWidth(), image.getHeight()); } } diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml index d470d4c7b3f6..aae30dfe6223 100644 --- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml +++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml @@ -16,13 +16,15 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - style="@style/ScrollViewStyle"> + style="@style/ScrollViewStyle" + android:importantForAccessibility="no"> <LinearLayout android:id="@+id/activity_confirmation" android:layout_width="match_parent" android:layout_height="wrap_content" - android:baselineAligned="false"> + android:baselineAligned="false" + android:importantForAccessibility="no"> <LinearLayout android:id="@+id/association_confirmation" style="@style/ContainerLayout"> @@ -153,7 +155,8 @@ <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_weight="1"> + android:layout_weight="1" + android:importantForAccessibility="noHideDescendants"> <ProgressBar android:id="@+id/spinner_single_device" diff --git a/packages/CompanionDeviceManager/res/layout/vendor_header.xml b/packages/CompanionDeviceManager/res/layout/vendor_header.xml index 16a87e589b8e..77c79b384276 100644 --- a/packages/CompanionDeviceManager/res/layout/vendor_header.xml +++ b/packages/CompanionDeviceManager/res/layout/vendor_header.xml @@ -33,7 +33,7 @@ android:layout_width="48dp" android:layout_height="48dp" android:padding="12dp" - android:contentDescription="@string/vendor_icon_description" /> + android:importantForAccessibility="no" /> <TextView android:id="@+id/vendor_header_name" diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index ebfb86d4d6ba..74072e9d4ec3 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -113,6 +113,18 @@ <!-- Back button for the helper consent dialog [CHAR LIMIT=30] --> <string name="consent_back">Back</string> + <!-- Action when permission list view is expanded CHAR LIMIT=30] --> + <string name="permission_expanded">Expanded</string> + + <!-- Expand action permission list CHAR LIMIT=30] --> + <string name="permission_expand">Expand</string> + + <!-- Action when permission list view is collapsed CHAR LIMIT=30] --> + <string name="permission_collapsed">Collapsed</string> + + <!-- Collapse action permission list CHAR LIMIT=30] --> + <string name="permission_collapse">Collapse</string> + <!-- ================== System data transfer ==================== --> <!-- Title of the permission sync confirmation dialog. [CHAR LIMIT=NONE] --> <string name="permission_sync_confirmation_title">Give apps on <strong><xliff:g id="companion_device_name" example="Galaxy Watch 5">%1$s</xliff:g></strong> the same permissions as on <strong><xliff:g id="primary_device_name" example="Pixel 6">%2$s</xliff:g></strong>?</string> @@ -120,11 +132,8 @@ <!-- Text of the permission sync explanation in the confirmation dialog. [CHAR LIMIT=NONE] --> <string name="permission_sync_summary">This may include <strong>Microphone</strong>, <strong>Camera</strong>, and <strong>Location access</strong>, and other sensitive permissions on <strong><xliff:g id="companion_device_name" example="My Watch">%1$s</xliff:g></strong>. <br/><br/>You can change these permissions any time in your Settings on <strong><xliff:g id="companion_device_name" example="My Watch">%1$s</xliff:g></strong>.</string> - <!--Description for vendor icon [CHAR LIMIT=30]--> - <string name="vendor_icon_description">App Icon</string> - <!--Description for information icon [CHAR LIMIT=30]--> - <string name="vendor_header_button_description">More Information Button</string> + <string name="vendor_header_button_description">More Information</string> <!-- ================= Permissions ================= --> diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml index b167377eabf7..e85190be0e1e 100644 --- a/packages/CompanionDeviceManager/res/values/styles.xml +++ b/packages/CompanionDeviceManager/res/values/styles.xml @@ -36,7 +36,7 @@ </style> <style name="DescriptionTitle" - parent="@android:style/TextAppearance.DeviceDefault.Medium"> + parent="@android:style/TextAppearance.DeviceDefault"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">wrap_content</item> <item name="android:gravity">center</item> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java index d2fd78012193..b86ef649331a 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java @@ -27,6 +27,7 @@ import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; @@ -121,6 +122,10 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V if (viewHolder.mExpandButton.getTag() == null) { viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more); } + + setAccessibility(view, viewType, + AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand); + // Add expand buttons if the permissions are more than PERMISSION_SIZE in this list also // make the summary invisible by default. if (mPermissions.size() > PERMISSION_SIZE) { @@ -132,10 +137,18 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_less); viewHolder.mPermissionSummary.setVisibility(View.VISIBLE); viewHolder.mExpandButton.setTag(R.drawable.btn_expand_less); + view.setContentDescription(mContext.getString(R.string.permission_expanded)); + setAccessibility(view, viewType, + AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_collapse); + viewHolder.mPermissionSummary.setFocusable(true); } else { viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_more); viewHolder.mPermissionSummary.setVisibility(View.GONE); viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more); + view.setContentDescription(mContext.getString(R.string.permission_collapsed)); + setAccessibility(view, viewType, + AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expanded); + viewHolder.mPermissionSummary.setFocusable(false); } }); } else { @@ -187,6 +200,18 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V } } + private void setAccessibility(View view, int viewType, int action, int resourceId) { + final String actionString = mContext.getString(resourceId); + final String permission = mContext.getString(sTitleMap.get(viewType)); + view.setAccessibilityDelegate(new View.AccessibilityDelegate() { + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.addAction(new AccessibilityNodeInfo.AccessibilityAction(action, + actionString + permission)); + } + }); + } + void setPermissionType(List<Integer> permissions) { mPermissions = permissions; } diff --git a/packages/SettingsLib/res/drawable/dialog_btn_filled.xml b/packages/SettingsLib/res/drawable/dialog_btn_filled.xml new file mode 100644 index 000000000000..14cb1de9fa2d --- /dev/null +++ b/packages/SettingsLib/res/drawable/dialog_btn_filled.xml @@ -0,0 +1,39 @@ +<?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. + --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:insetTop="@dimen/dialog_button_vertical_inset" + android:insetBottom="@dimen/dialog_button_vertical_inset"> + <ripple android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="@android:color/white"/> + <corners android:radius="?android:attr/buttonCornerRadius"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="?android:attr/buttonCornerRadius"/> + <solid android:color="?androidprv:attr/colorAccentPrimary"/> + <padding android:left="@dimen/dialog_button_horizontal_padding" + android:top="@dimen/dialog_button_vertical_padding" + android:right="@dimen/dialog_button_horizontal_padding" + android:bottom="@dimen/dialog_button_vertical_padding"/> + </shape> + </item> + </ripple> +</inset> diff --git a/packages/SettingsLib/res/drawable/dialog_btn_outline.xml b/packages/SettingsLib/res/drawable/dialog_btn_outline.xml new file mode 100644 index 000000000000..1e7775980243 --- /dev/null +++ b/packages/SettingsLib/res/drawable/dialog_btn_outline.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. + --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:insetTop="@dimen/dialog_button_vertical_inset" + android:insetBottom="@dimen/dialog_button_vertical_inset"> + <ripple android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="@android:color/white"/> + <corners android:radius="?android:attr/buttonCornerRadius"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="?android:attr/buttonCornerRadius"/> + <solid android:color="@android:color/transparent"/> + <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant" + android:width="1dp" + /> + <padding android:left="@dimen/dialog_button_horizontal_padding" + android:top="@dimen/dialog_button_vertical_padding" + android:right="@dimen/dialog_button_horizontal_padding" + android:bottom="@dimen/dialog_button_vertical_padding"/> + </shape> + </item> + </ripple> +</inset> diff --git a/packages/SettingsLib/res/drawable/ic_admin_panel_settings.xml b/packages/SettingsLib/res/drawable/ic_admin_panel_settings.xml new file mode 100644 index 000000000000..3ea8e9e67d4e --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_admin_panel_settings.xml @@ -0,0 +1,25 @@ +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M17,17Q17.625,17 18.062,16.562Q18.5,16.125 18.5,15.5Q18.5,14.875 18.062,14.438Q17.625,14 17,14Q16.375,14 15.938,14.438Q15.5,14.875 15.5,15.5Q15.5,16.125 15.938,16.562Q16.375,17 17,17ZM17,20Q17.775,20 18.425,19.637Q19.075,19.275 19.475,18.675Q18.925,18.35 18.3,18.175Q17.675,18 17,18Q16.325,18 15.7,18.175Q15.075,18.35 14.525,18.675Q14.925,19.275 15.575,19.637Q16.225,20 17,20ZM12,22Q8.525,21.125 6.263,18.012Q4,14.9 4,11.1V5L12,2L20,5V10.675Q19.525,10.475 19.025,10.312Q18.525,10.15 18,10.075V6.4L12,4.15L6,6.4V11.1Q6,12.275 6.312,13.45Q6.625,14.625 7.188,15.688Q7.75,16.75 8.55,17.65Q9.35,18.55 10.325,19.15Q10.6,19.95 11.05,20.675Q11.5,21.4 12.075,21.975Q12.05,21.975 12.038,21.988Q12.025,22 12,22ZM17,22Q14.925,22 13.463,20.538Q12,19.075 12,17Q12,14.925 13.463,13.462Q14.925,12 17,12Q19.075,12 20.538,13.462Q22,14.925 22,17Q22,19.075 20.538,20.538Q19.075,22 17,22ZM12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Z"/> +</vector> diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml new file mode 100644 index 000000000000..9081ca5cc1bb --- /dev/null +++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml @@ -0,0 +1,99 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:padding="@dimen/grant_admin_dialog_padding" + android:paddingBottom="0dp"> + <ImageView + android:id="@+id/dialog_with_icon_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription=""/> + <TextView + android:id="@+id/dialog_with_icon_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + style="@style/DialogWithIconTitle" + android:text="@string/user_grant_admin_title" + android:textDirection="locale"/> + <TextView + android:id="@+id/dialog_with_icon_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="10dp" + android:gravity="center" + style="@style/TextAppearanceSmall" + android:text="" + android:textDirection="locale"/> + <LinearLayout + android:id="@+id/custom_layout" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingBottom="0dp"> + </LinearLayout> + <LinearLayout + android:id="@+id/button_panel" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:paddingBottom="0dp"> + <Button + android:id="@+id/button_cancel" + style="@style/DialogButtonNegative" + android:layout_width="wrap_content" + android:buttonCornerRadius="28dp" + android:layout_height="wrap_content" + android:visibility="gone"/> + <Space + android:layout_width="0dp" + android:layout_height="1dp" + android:layout_weight="1" > + </Space> + <Button + android:id="@+id/button_back" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/DialogButtonNegative" + android:buttonCornerRadius="40dp" + android:clickable="true" + android:focusable="true" + android:text="Back" + android:visibility="gone" + /> + <Space + android:layout_width="0dp" + android:layout_height="1dp" + android:layout_weight="0.05" + > + </Space> + <Button + android:id="@+id/button_ok" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/DialogButtonPositive" + android:clickable="true" + android:focusable="true" + android:visibility="gone" + /> + </LinearLayout> +</LinearLayout> diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index dbfd1c27fce8..e9aded0838d9 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -112,4 +112,13 @@ <!-- Size of grant admin privileges dialog padding --> <dimen name="grant_admin_dialog_padding">16dp</dimen> + + <dimen name="dialog_button_horizontal_padding">16dp</dimen> + <dimen name="dialog_button_vertical_padding">8dp</dimen> + <!-- The button will be 48dp tall, but the background needs to be 36dp tall --> + <dimen name="dialog_button_vertical_inset">6dp</dimen> + <dimen name="dialog_top_padding">24dp</dimen> + <dimen name="dialog_bottom_padding">18dp</dimen> + <dimen name="dialog_side_padding">24dp</dimen> + <dimen name="dialog_button_bar_top_padding">32dp</dimen> </resources> diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml index cc60382b48f9..2584be725747 100644 --- a/packages/SettingsLib/res/values/styles.xml +++ b/packages/SettingsLib/res/values/styles.xml @@ -83,4 +83,39 @@ <item name="android:textDirection">locale</item> <item name="android:ellipsize">end</item> </style> + + <style name="DialogWithIconTitle" parent="@android:TextAppearance.DeviceDefault.Headline"> + <item name="android:textSize">@dimen/broadcast_dialog_title_text_size</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textDirection">locale</item> + <item name="android:ellipsize">end</item> + </style> + + <style name="DialogButtonPositive" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item name="android:buttonCornerRadius">0dp</item> + <item name="android:background">@drawable/dialog_btn_filled</item> + <item name="android:textColor">?androidprv:attr/textColorOnAccent</item> + <item name="android:textSize">14sp</item> + <item name="android:lineHeight">20sp</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> + <item name="android:stateListAnimator">@null</item> + <item name="android:minWidth">0dp</item> + </style> + + <style name="DialogButtonNegative"> + <item name="android:buttonCornerRadius">28dp</item> + <item name="android:background">@drawable/dialog_btn_outline</item> + <item name="android:buttonCornerRadius">28dp</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textSize">14sp</item> + <item name="android:lineHeight">20sp</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> + <item name="android:stateListAnimator">@null</item> + <item name="android:minWidth">0dp</item> + </style> + + <style name="TextStyleMessage"> + <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> + <item name="android:textSize">16dp</item> + </style> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java new file mode 100644 index 000000000000..de488144be6c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java @@ -0,0 +1,277 @@ +/* + * 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.settingslib.utils; +import android.annotation.IntDef; +import android.annotation.StringRes; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.settingslib.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This class is used to create custom dialog with icon, title, message and custom view that are + * horizontally centered. + */ +public class CustomDialogHelper { + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ICON, TITLE, MESSAGE, LAYOUT, BACK_BUTTON, NEGATIVE_BUTTON, POSITIVE_BUTTON}) + public @interface LayoutComponent {} + + @Retention(RetentionPolicy.SOURCE) + @IntDef({BACK_BUTTON, NEGATIVE_BUTTON, POSITIVE_BUTTON}) + public @interface LayoutButton {} + + public static final int ICON = 0; + public static final int TITLE = 1; + public static final int MESSAGE = 2; + public static final int LAYOUT = 3; + public static final int BACK_BUTTON = 4; + public static final int NEGATIVE_BUTTON = 5; + public static final int POSITIVE_BUTTON = 6; + private View mDialogContent; + private Dialog mDialog; + private Context mContext; + private LayoutInflater mLayoutInflater; + private ImageView mDialogIcon; + private TextView mDialogTitle; + private TextView mDialogMessage; + private LinearLayout mCustomLayout; + private Button mPositiveButton; + private Button mNegativeButton; + private Button mBackButton; + + public CustomDialogHelper(Context context) { + mContext = context; + mLayoutInflater = LayoutInflater.from(context); + mDialogContent = mLayoutInflater.inflate(R.layout.dialog_with_icon, null); + mDialogIcon = mDialogContent.findViewById(R.id.dialog_with_icon_icon); + mDialogTitle = mDialogContent.findViewById(R.id.dialog_with_icon_title); + mDialogMessage = mDialogContent.findViewById(R.id.dialog_with_icon_message); + mCustomLayout = mDialogContent.findViewById(R.id.custom_layout); + mPositiveButton = mDialogContent.findViewById(R.id.button_ok); + mNegativeButton = mDialogContent.findViewById(R.id.button_cancel); + mBackButton = mDialogContent.findViewById(R.id.button_back); + createDialog(); + } + + /** + * Creates dialog with content defined in constructor. + */ + private void createDialog() { + mDialog = new AlertDialog.Builder(mContext) + .setView(mDialogContent) + .setCancelable(true) + .create(); + mDialog.getWindow() + .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } + + /** + * Sets title and listener for positive button. + */ + public CustomDialogHelper setPositiveButton(@StringRes int resid, + View.OnClickListener onClickListener) { + setButton(POSITIVE_BUTTON, resid, onClickListener); + return this; + } + + /** + * Sets positive button text. + */ + public CustomDialogHelper setPositiveButtonText(@StringRes int resid) { + mPositiveButton.setText(resid); + return this; + } + + /** + * Sets title and listener for negative button. + */ + public CustomDialogHelper setNegativeButton(@StringRes int resid, + View.OnClickListener onClickListener) { + setButton(NEGATIVE_BUTTON, resid, onClickListener); + return this; + } + + /** + * Sets negative button text. + */ + public CustomDialogHelper setNegativeButtonText(@StringRes int resid) { + mNegativeButton.setText(resid); + return this; + } + + /** + * Sets title and listener for back button. + */ + public CustomDialogHelper setBackButton(@StringRes int resid, + View.OnClickListener onClickListener) { + setButton(BACK_BUTTON, resid, onClickListener); + return this; + } + + /** + * Sets title for back button. + */ + public CustomDialogHelper setBackButtonText(@StringRes int resid) { + mBackButton.setText(resid); + return this; + } + + private void setButton(@LayoutButton int whichButton, @StringRes int resid, + View.OnClickListener listener) { + switch (whichButton) { + case POSITIVE_BUTTON : + mPositiveButton.setText(resid); + mPositiveButton.setVisibility(View.VISIBLE); + mPositiveButton.setOnClickListener(listener); + break; + case NEGATIVE_BUTTON: + mNegativeButton.setText(resid); + mNegativeButton.setVisibility(View.VISIBLE); + mNegativeButton.setOnClickListener(listener); + break; + case BACK_BUTTON: + mBackButton.setText(resid); + mBackButton.setVisibility(View.VISIBLE); + mBackButton.setOnClickListener(listener); + break; + default: + break; + } + } + + + /** + * Modifies state of button. + * //TODO: modify method to allow setting state for any button. + */ + public CustomDialogHelper setButtonEnabled(boolean enabled) { + mPositiveButton.setEnabled(enabled); + return this; + } + + /** + * Sets title of the dialog. + */ + public CustomDialogHelper setTitle(@StringRes int resid) { + mDialogTitle.setText(resid); + return this; + } + + /** + * Sets message of the dialog. + */ + public CustomDialogHelper setMessage(@StringRes int resid) { + mDialogMessage.setText(resid); + return this; + } + + /** + * Sets icon of the dialog. + */ + public CustomDialogHelper setIcon(Drawable icon) { + mDialogIcon.setImageDrawable(icon); + return this; + } + + /** + * Removes all views that were previously added to the custom layout part. + */ + public CustomDialogHelper clearCustomLayout() { + mCustomLayout.removeAllViews(); + return this; + } + + /** + * Hides custom layout. + */ + public void hideCustomLayout() { + mCustomLayout.setVisibility(View.GONE); + } + + /** + * Shows custom layout. + */ + public void showCustomLayout() { + mCustomLayout.setVisibility(View.VISIBLE); + } + + /** + * Adds view to custom layout. + */ + public CustomDialogHelper addCustomView(View view) { + mCustomLayout.addView(view); + return this; + } + + /** + * Returns dialog. + */ + public Dialog getDialog() { + return mDialog; + } + + /** + * Sets visibility of layout component. + * @param element part of the layout visibility of which is being changed. + * @param isVisible true if visibility is set to View.VISIBLE + * @return this + */ + public CustomDialogHelper setVisibility(@LayoutComponent int element, boolean isVisible) { + int visibility; + if (isVisible) { + visibility = View.VISIBLE; + } else { + visibility = View.GONE; + } + switch (element) { + case ICON: + mDialogIcon.setVisibility(visibility); + break; + case TITLE: + mDialogTitle.setVisibility(visibility); + break; + case MESSAGE: + mDialogMessage.setVisibility(visibility); + break; + case BACK_BUTTON: + mBackButton.setVisibility(visibility); + break; + case NEGATIVE_BUTTON: + mNegativeButton.setVisibility(visibility); + break; + case POSITIVE_BUTTON: + mPositiveButton.setVisibility(visibility); + break; + default: + break; + } + return this; + } +} diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index a00f401756f7..24de487945db 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -380,6 +380,9 @@ <service android:name="SystemUIService" android:exported="true" /> + <service android:name=".wallet.controller.WalletContextualLocationsService" + android:exported="true" + /> <!-- Service for dumping extremely verbose content during a bug report --> <service android:name=".dump.SystemUIAuxiliaryDumpService" diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt index f0a82113c3a3..83e44b69812b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt @@ -18,8 +18,10 @@ package com.android.systemui.animation import android.graphics.fonts.Font import android.graphics.fonts.FontVariationAxis +import android.util.LruCache import android.util.MathUtils import android.util.MathUtils.abs +import androidx.annotation.VisibleForTesting import java.lang.Float.max import java.lang.Float.min @@ -34,6 +36,10 @@ private const val FONT_ITALIC_MIN = 0f private const val FONT_ITALIC_ANIMATION_STEP = 0.1f private const val FONT_ITALIC_DEFAULT_VALUE = 0f +// Benchmarked via Perfetto, difference between 10 and 50 entries is about 0.3ms in +// frame draw time on a Pixel 6. +@VisibleForTesting const val FONT_CACHE_MAX_ENTRIES = 10 + /** Provide interpolation of two fonts by adjusting font variation settings. */ class FontInterpolator { @@ -81,8 +87,8 @@ class FontInterpolator { // Font interpolator has two level caches: one for input and one for font with different // variation settings. No synchronization is needed since FontInterpolator is not designed to be // thread-safe and can be used only on UI thread. - private val interpCache = hashMapOf<InterpKey, Font>() - private val verFontCache = hashMapOf<VarFontKey, Font>() + private val interpCache = LruCache<InterpKey, Font>(FONT_CACHE_MAX_ENTRIES) + private val verFontCache = LruCache<VarFontKey, Font>(FONT_CACHE_MAX_ENTRIES) // Mutable keys for recycling. private val tmpInterpKey = InterpKey(null, null, 0f) @@ -152,7 +158,7 @@ class FontInterpolator { tmpVarFontKey.set(start, newAxes) val axesCachedFont = verFontCache[tmpVarFontKey] if (axesCachedFont != null) { - interpCache[InterpKey(start, end, progress)] = axesCachedFont + interpCache.put(InterpKey(start, end, progress), axesCachedFont) return axesCachedFont } @@ -160,8 +166,8 @@ class FontInterpolator { // Font.Builder#build won't throw IOException since creating fonts from existing fonts will // not do any IO work. val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build() - interpCache[InterpKey(start, end, progress)] = newFont - verFontCache[VarFontKey(start, newAxes)] = newFont + interpCache.put(InterpKey(start, end, progress), newFont) + verFontCache.put(VarFontKey(start, newAxes), newFont) return newFont } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index 9e9929e79d47..3ee97be360f0 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -24,8 +24,10 @@ import android.graphics.Canvas import android.graphics.Typeface import android.graphics.fonts.Font import android.text.Layout +import android.util.LruCache private const val DEFAULT_ANIMATION_DURATION: Long = 300 +private const val TYPEFACE_CACHE_MAX_ENTRIES = 5 typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit /** @@ -114,7 +116,7 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { private val fontVariationUtils = FontVariationUtils() - private val typefaceCache = HashMap<String, Typeface?>() + private val typefaceCache = LruCache<String, Typeface>(TYPEFACE_CACHE_MAX_ENTRIES) fun updateLayout(layout: Layout) { textInterpolator.layout = layout @@ -218,12 +220,12 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { } if (!fvar.isNullOrBlank()) { - textInterpolator.targetPaint.typeface = - typefaceCache.getOrElse(fvar) { - textInterpolator.targetPaint.fontVariationSettings = fvar + textInterpolator.targetPaint.typeface = typefaceCache.get(fvar) ?: run { + textInterpolator.targetPaint.fontVariationSettings = fvar + textInterpolator.targetPaint.typeface?.also { typefaceCache.put(fvar, textInterpolator.targetPaint.typeface) - textInterpolator.targetPaint.typeface } + } } if (color != null) { diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt index 387b67d231cd..520c8882b428 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt @@ -40,7 +40,8 @@ class SystemUIIssueRegistry : IssueRegistry() { SoftwareBitmapDetector.ISSUE, NonInjectedServiceDetector.ISSUE, StaticSettingsProviderDetector.ISSUE, - DemotingTestWithoutBugDetector.ISSUE + DemotingTestWithoutBugDetector.ISSUE, + TestFunctionNameViolationDetector.ISSUE, ) override val api: Int diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/TestFunctionNameViolationDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/TestFunctionNameViolationDetector.kt new file mode 100644 index 000000000000..d91c7e53a7aa --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/TestFunctionNameViolationDetector.kt @@ -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.internal.systemui.lint + +import com.android.tools.lint.detector.api.AnnotationInfo +import com.android.tools.lint.detector.api.AnnotationUsageInfo +import com.android.tools.lint.detector.api.AnnotationUsageType +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import org.jetbrains.uast.UElement +import org.jetbrains.uast.getParentOfType +import org.jetbrains.uast.kotlin.KotlinUAnnotation +import org.jetbrains.uast.kotlin.KotlinUMethod + +/** + * Detects test function naming violations regarding use of the backtick-wrapped space-allowed + * feature of Kotlin functions. + */ +class TestFunctionNameViolationDetector : Detector(), SourceCodeScanner { + + override fun applicableAnnotations(): List<String> = listOf(ANNOTATION) + override fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean = true + + @Suppress("UnstableApiUsage") + override fun visitAnnotationUsage( + context: JavaContext, + element: UElement, + annotationInfo: AnnotationInfo, + usageInfo: AnnotationUsageInfo, + ) { + (element as? KotlinUAnnotation)?.getParentOfType(KotlinUMethod::class.java)?.let { method -> + if (method.name.contains(" ")) { + context.report( + issue = ISSUE, + scope = method.nameIdentifier, + location = context.getLocation(method.nameIdentifier), + message = + "Spaces are not allowed in test names. Use pascalCase_withUnderScores" + + " instead.", + ) + } + } + } + + companion object { + private const val ANNOTATION = "org.junit.Test" + + @JvmStatic + val ISSUE = + Issue.create( + id = "TestFunctionNameViolation", + briefDescription = "Spaces not allowed in test function names.", + explanation = + """ + We don't allow test function names because it leads to issues with our test + harness system (for example, see b/277739595). Please use + pascalCase_withUnderScores instead. + """, + category = Category.TESTING, + priority = 8, + severity = Severity.FATAL, + implementation = + Implementation( + TestFunctionNameViolationDetector::class.java, + Scope.JAVA_FILE_SCOPE, + ), + ) + } +} diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt new file mode 100644 index 000000000000..db73154c02d9 --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt @@ -0,0 +1,103 @@ +/* + * 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.internal.systemui.lint + +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +@Suppress("UnstableApiUsage") +class TestFunctionNameViolationDetectorTest : SystemUILintDetectorTest() { + override fun getDetector(): Detector { + return TestFunctionNameViolationDetector() + } + + override fun getIssues(): List<Issue> { + return listOf( + TestFunctionNameViolationDetector.ISSUE, + ) + } + + @Test + fun violations() { + lint() + .files( + kotlin( + """ + package test.pkg.name + + import org.junit.Test + + class MyTest { + @Test + fun `illegal test name - violation should be detected`() { + // some test code here. + } + + @Test + fun legitimateTestName_doesNotViolate() { + // some test code here. + } + + fun helperFunction_doesNotViolate() { + // some code. + } + + fun `helper function - does not violate`() { + // some code. + } + } + """ + .trimIndent() + ), + testAnnotationStub, + ) + .issues( + TestFunctionNameViolationDetector.ISSUE, + ) + .run() + .expectWarningCount(0) + .expect( + """ + src/test/pkg/name/MyTest.kt:7: Error: Spaces are not allowed in test names. Use pascalCase_withUnderScores instead. [TestFunctionNameViolation] + fun `illegal test name - violation should be detected`() { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + ) + } + + companion object { + private val testAnnotationStub: TestFile = + kotlin( + """ + package org.junit + + import java.lang.annotation.ElementType + import java.lang.annotation.Retention + import java.lang.annotation.RetentionPolicy + import java.lang.annotation.Target + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + annotation class Test + """ + ) + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index 6ca7f12e842b..3fda83d7b6f4 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -263,6 +263,13 @@ class DefaultClockController( view.animateDoze(dozeState.isActive, !hasJumped) } } + + override fun onPickerCarouselSwiping(swipingFraction: Float, previewRatio: Float) { + // TODO(b/278936436): refactor this part when we change recomputePadding + // when on the side, swipingFraction = 0, translationY should offset + // the top margin change in recomputePadding to make clock be centered + view.translationY = 0.5f * view.bottom * (1 - swipingFraction) + } } inner class LargeClockAnimations( diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 64aa629b02f0..331307a0d9f4 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -44,6 +44,8 @@ <LinearLayout android:id="@+id/status_bar_contents" android:layout_width="match_parent" android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false" android:paddingStart="@dimen/status_bar_padding_start" android:paddingEnd="@dimen/status_bar_padding_end" android:paddingTop="@dimen/status_bar_padding_top" diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 10c08bc3e8b3..9573913e5e2f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1832,7 +1832,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED .equals(action)) { mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED, - getSendingUserId())); + getSendingUserId(), 0)); } else if (ACTION_USER_UNLOCKED.equals(action)) { mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_UNLOCKED, getSendingUserId(), 0)); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index e0b9f01bf662..d0ac2968ae8e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -23,14 +23,17 @@ import android.content.Context import android.graphics.Point import android.hardware.biometrics.BiometricFingerprintConstants import android.hardware.biometrics.BiometricSourceType +import android.util.DisplayMetrics import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.logging.KeyguardLogger import com.android.settingslib.Utils import com.android.settingslib.udfps.UdfpsOverlayParams +import com.android.systemui.CoreStartable import com.android.systemui.R import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.WakefulnessLifecycle @@ -39,12 +42,11 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LiftReveal import com.android.systemui.statusbar.LightRevealEffect +import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.phone.BiometricUnlockController -import com.android.systemui.statusbar.phone.CentralSurfaces -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.ViewController @@ -60,9 +62,8 @@ import javax.inject.Provider * * The ripple uses the accent color of the current theme. */ -@CentralSurfacesScope +@SysUISingleton class AuthRippleController @Inject constructor( - private val centralSurfaces: CentralSurfaces, private val sysuiContext: Context, private val authController: AuthController, private val configurationController: ConfigurationController, @@ -73,12 +74,15 @@ class AuthRippleController @Inject constructor( private val notificationShadeWindowController: NotificationShadeWindowController, private val udfpsControllerProvider: Provider<UdfpsController>, private val statusBarStateController: StatusBarStateController, + private val displayMetrics: DisplayMetrics, private val featureFlags: FeatureFlags, private val logger: KeyguardLogger, private val biometricUnlockController: BiometricUnlockController, + private val lightRevealScrim: LightRevealScrim, rippleView: AuthRippleView? ) : ViewController<AuthRippleView>(rippleView), + CoreStartable, KeyguardStateController.Callback, WakefulnessLifecycle.Observer { @@ -92,6 +96,10 @@ class AuthRippleController @Inject constructor( private var udfpsController: UdfpsController? = null private var udfpsRadius: Float = -1f + override fun start() { + init() + } + @VisibleForTesting public override fun onViewAttached() { authController.addCallback(authControllerCallback) @@ -153,8 +161,8 @@ class AuthRippleController @Inject constructor( it.y, 0, Math.max( - Math.max(it.x, centralSurfaces.displayWidth.toInt() - it.x), - Math.max(it.y, centralSurfaces.displayHeight.toInt() - it.y) + Math.max(it.x, displayMetrics.widthPixels - it.x), + Math.max(it.y, displayMetrics.heightPixels - it.y) ) ) logger.showingUnlockRippleAt(it.x, it.y, "FP sensor radius: $udfpsRadius") @@ -168,8 +176,8 @@ class AuthRippleController @Inject constructor( it.y, 0, Math.max( - Math.max(it.x, centralSurfaces.displayWidth.toInt() - it.x), - Math.max(it.y, centralSurfaces.displayHeight.toInt() - it.y) + Math.max(it.x, displayMetrics.widthPixels - it.x), + Math.max(it.y, displayMetrics.heightPixels - it.y) ) ) logger.showingUnlockRippleAt(it.x, it.y, "Face unlock ripple") @@ -184,11 +192,10 @@ class AuthRippleController @Inject constructor( // This code path is not used if the KeyguardTransitionRepository is managing the light // reveal scrim. if (!featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) { - val lightRevealScrim = centralSurfaces.lightRevealScrim if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) { circleReveal?.let { - lightRevealScrim?.revealAmount = 0f - lightRevealScrim?.revealEffect = it + lightRevealScrim.revealAmount = 0f + lightRevealScrim.revealEffect = it startLightRevealScrimOnKeyguardFadingAway = true } } @@ -208,8 +215,7 @@ class AuthRippleController @Inject constructor( } if (keyguardStateController.isKeyguardFadingAway) { - val lightRevealScrim = centralSurfaces.lightRevealScrim - if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) { + if (startLightRevealScrimOnKeyguardFadingAway) { lightRevealScrimAnimator?.cancel() lightRevealScrimAnimator = ValueAnimator.ofFloat(.1f, 1f).apply { interpolator = Interpolators.LINEAR_OUT_SLOW_IN diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index cee829465047..dff2c0e91f1f 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -288,7 +288,7 @@ public abstract class SystemUIModule { INotificationManager notificationManager, IDreamManager dreamManager, NotificationVisibilityProvider visibilityProvider, - NotificationInterruptStateProvider interruptionStateProvider, + VisualInterruptionDecisionProvider visualInterruptionDecisionProvider, ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager, CommonNotifCollection notifCollection, @@ -306,7 +306,7 @@ public abstract class SystemUIModule { notificationManager, dreamManager, visibilityProvider, - interruptionStateProvider, + visualInterruptionDecisionProvider, zenModeController, notifUserManager, notifCollection, diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index aaf4358494f0..05153b6932b6 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -87,7 +87,9 @@ object Flags { releasedFlag(270682168, "animated_notification_shade_insets") // TODO(b/268005230): Tracking Bug - @JvmField val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim") + @JvmField + val SENSITIVE_REVEAL_ANIM = + unreleasedFlag(268005230, "sensitive_reveal_anim", teamfood = true) // 200 - keyguard/lockscreen // ** Flag retired ** diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt index b86083abad21..1f1329111ce7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt @@ -106,7 +106,7 @@ constructor( } private fun isPhysicalFullKeyboard(deviceId: Int): Boolean { - val device = inputManager.getInputDevice(deviceId) + val device = inputManager.getInputDevice(deviceId) ?: return false return !device.isVirtual && device.isFullKeyboard } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 9ab2e9922531..2d1b7ae610c0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -791,7 +791,7 @@ class KeyguardUnlockAnimationController @Inject constructor( // Translate up from the bottom. surfaceBehindMatrix.setTranslate( - surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(), + surfaceBehindRemoteAnimationTarget.localBounds.left.toFloat(), surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) ) diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index 3770b2885b18..96e97565d585 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -60,6 +60,7 @@ private const val MIN_DURATION_CANCELLED_ANIMATION = 200L private const val MIN_DURATION_COMMITTED_ANIMATION = 80L private const val MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION = 120L private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L +private const val MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION = 80L private const val MIN_DURATION_FLING_ANIMATION = 160L private const val MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING = 100L @@ -135,7 +136,8 @@ class BackPanelController internal constructor( private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback private var previousXTranslationOnActiveOffset = 0f private var previousXTranslation = 0f - private var totalTouchDelta = 0f + private var totalTouchDeltaActive = 0f + private var totalTouchDeltaInactive = 0f private var touchDeltaStartX = 0f private var velocityTracker: VelocityTracker? = null set(value) { @@ -154,7 +156,7 @@ class BackPanelController internal constructor( private var gestureEntryTime = 0L private var gestureInactiveTime = 0L - private var gestureActiveTime = 0L + private var gesturePastActiveThresholdWhileInactiveTime = 0L private val elapsedTimeSinceInactive get() = SystemClock.uptimeMillis() - gestureInactiveTime @@ -250,7 +252,7 @@ class BackPanelController internal constructor( private fun updateConfiguration() { params.update(resources) mView.updateArrowPaint(params.arrowThickness) - minFlingDistance = ViewConfiguration.get(context).scaledTouchSlop * 3 + minFlingDistance = viewConfiguration.scaledTouchSlop * 3 } private val configurationListener = object : ConfigurationController.ConfigurationListener { @@ -403,30 +405,46 @@ class BackPanelController internal constructor( } GestureState.ACTIVE -> { val isPastDynamicDeactivationThreshold = - totalTouchDelta <= params.deactivationSwipeTriggerThreshold + totalTouchDeltaActive <= params.deactivationTriggerThreshold val isMinDurationElapsed = - elapsedTimeSinceEntry > MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION + elapsedTimeSinceEntry > MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION if (isMinDurationElapsed && (!isWithinYActivationThreshold || - isPastDynamicDeactivationThreshold) + isPastDynamicDeactivationThreshold) ) { updateArrowState(GestureState.INACTIVE) } } GestureState.INACTIVE -> { val isPastStaticThreshold = - xTranslation > params.staticTriggerThreshold - val isPastDynamicReactivationThreshold = totalTouchDelta > 0 && - abs(totalTouchDelta) >= - params.reactivationTriggerThreshold - - if (isPastStaticThreshold && + xTranslation > params.staticTriggerThreshold + val isPastDynamicReactivationThreshold = + totalTouchDeltaInactive >= params.reactivationTriggerThreshold + val isPastAllThresholds = isPastStaticThreshold && isPastDynamicReactivationThreshold && isWithinYActivationThreshold - ) { + val isPastAllThresholdsForFirstTime = isPastAllThresholds && + gesturePastActiveThresholdWhileInactiveTime == 0L + + gesturePastActiveThresholdWhileInactiveTime = when { + isPastAllThresholdsForFirstTime -> SystemClock.uptimeMillis() + isPastAllThresholds -> gesturePastActiveThresholdWhileInactiveTime + else -> 0L + } + + val elapsedTimePastAllThresholds = + SystemClock.uptimeMillis() - gesturePastActiveThresholdWhileInactiveTime + + val isPastMinimumInactiveToActiveDuration = + elapsedTimePastAllThresholds > MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION + + if (isPastAllThresholds && isPastMinimumInactiveToActiveDuration) { + // The minimum duration adds the 'edge stickiness' + // factor before pulling it off edge updateArrowState(GestureState.ACTIVE) } } + else -> {} } } @@ -451,19 +469,25 @@ class BackPanelController internal constructor( previousXTranslation = xTranslation if (abs(xDelta) > 0) { - val range = - params.run { deactivationSwipeTriggerThreshold..reactivationTriggerThreshold } - val isTouchInContinuousDirection = - sign(xDelta) == sign(totalTouchDelta) || totalTouchDelta in range + val isInSameDirection = sign(xDelta) == sign(totalTouchDeltaActive) + val isInDynamicRange = totalTouchDeltaActive in params.dynamicTriggerThresholdRange + val isTouchInContinuousDirection = isInSameDirection || isInDynamicRange if (isTouchInContinuousDirection) { // Direction has NOT changed, so keep counting the delta - totalTouchDelta += xDelta + totalTouchDeltaActive += xDelta } else { // Direction has changed, so reset the delta - totalTouchDelta = xDelta + totalTouchDeltaActive = xDelta touchDeltaStartX = x } + + // Add a slop to to prevent small jitters when arrow is at edge in + // emitting small values that cause the arrow to poke out slightly + val minimumDelta = -viewConfiguration.scaledTouchSlop.toFloat() + totalTouchDeltaInactive = totalTouchDeltaInactive + .plus(xDelta) + .coerceAtLeast(minimumDelta) } updateArrowStateOnMove(yTranslation, xTranslation) @@ -471,7 +495,7 @@ class BackPanelController internal constructor( val gestureProgress = when (currentState) { GestureState.ACTIVE -> fullScreenProgress(xTranslation) GestureState.ENTRY -> staticThresholdProgress(xTranslation) - GestureState.INACTIVE -> reactivationThresholdProgress(totalTouchDelta) + GestureState.INACTIVE -> reactivationThresholdProgress(totalTouchDeltaInactive) else -> null } @@ -529,8 +553,7 @@ class BackPanelController internal constructor( * the arrow is fully stretched (between 0.0 - 1.0f) */ private fun fullScreenProgress(xTranslation: Float): Float { - val progress = abs((xTranslation - previousXTranslationOnActiveOffset) / - (fullyStretchedThreshold - previousXTranslationOnActiveOffset)) + val progress = (xTranslation - previousXTranslationOnActiveOffset) / fullyStretchedThreshold return MathUtils.saturate(progress) } @@ -581,13 +604,15 @@ class BackPanelController internal constructor( } private var previousPreThresholdWidthInterpolator = params.entryWidthTowardsEdgeInterpolator - fun preThresholdWidthStretchAmount(progress: Float): Float { + private fun preThresholdWidthStretchAmount(progress: Float): Float { val interpolator = run { - val isPastSlop = abs(totalTouchDelta) > ViewConfiguration.get(context).scaledTouchSlop + val isPastSlop = totalTouchDeltaInactive > viewConfiguration.scaledTouchSlop if (isPastSlop) { - if (totalTouchDelta > 0) { + if (totalTouchDeltaInactive > 0) { params.entryWidthInterpolator - } else params.entryWidthTowardsEdgeInterpolator + } else { + params.entryWidthTowardsEdgeInterpolator + } } else { previousPreThresholdWidthInterpolator }.also { previousPreThresholdWidthInterpolator = it } @@ -643,7 +668,7 @@ class BackPanelController internal constructor( xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1) } ?: 0f val isPastFlingVelocityThreshold = - flingVelocity > ViewConfiguration.get(context).scaledMinimumFlingVelocity + flingVelocity > viewConfiguration.scaledMinimumFlingVelocity return flingDistance > minFlingDistance && isPastFlingVelocityThreshold } @@ -861,7 +886,6 @@ class BackPanelController internal constructor( } GestureState.ACTIVE -> { previousXTranslationOnActiveOffset = previousXTranslation - gestureActiveTime = SystemClock.uptimeMillis() updateRestingArrowDimens() @@ -870,21 +894,24 @@ class BackPanelController internal constructor( vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) } - val startingVelocity = convertVelocityToSpringStartingVelocity( - valueOnFastVelocity = 0f, - valueOnSlowVelocity = if (previousState == GestureState.ENTRY) 2f else 4.5f - ) + val minimumPop = 2f + val maximumPop = 4.5f when (previousState) { - GestureState.ENTRY, - GestureState.INACTIVE -> { + GestureState.ENTRY -> { + val startingVelocity = convertVelocityToSpringStartingVelocity( + valueOnFastVelocity = minimumPop, + valueOnSlowVelocity = maximumPop, + fastVelocityBound = 1f, + slowVelocityBound = 0.5f, + ) mView.popOffEdge(startingVelocity) } - GestureState.COMMITTED -> { - // if previous state was committed then this activation - // was due to a quick second swipe. Don't pop the arrow this time + GestureState.INACTIVE -> { + mView.popOffEdge(maximumPop) } - else -> { } + + else -> {} } } @@ -896,7 +923,7 @@ class BackPanelController internal constructor( // but because we can also independently enter this state // if touch Y >> touch X, we force it to deactivationSwipeTriggerThreshold // so that gesture progress in this state is consistent regardless of entry - totalTouchDelta = params.deactivationSwipeTriggerThreshold + totalTouchDeltaInactive = params.deactivationTriggerThreshold val startingVelocity = convertVelocityToSpringStartingVelocity( valueOnFastVelocity = -1.05f, @@ -944,10 +971,12 @@ class BackPanelController internal constructor( private fun convertVelocityToSpringStartingVelocity( valueOnFastVelocity: Float, valueOnSlowVelocity: Float, + fastVelocityBound: Float = 3f, + slowVelocityBound: Float = 0f, ): Float { val factor = velocityTracker?.run { computeCurrentVelocity(PX_PER_MS) - MathUtils.smoothStep(0f, 3f, abs(xVelocity)) + MathUtils.smoothStep(slowVelocityBound, fastVelocityBound, abs(xVelocity)) } ?: valueOnFastVelocity return MathUtils.lerp(valueOnFastVelocity, valueOnSlowVelocity, 1 - factor) @@ -982,7 +1011,7 @@ class BackPanelController internal constructor( "$currentState", "startX=$startX", "startY=$startY", - "xDelta=${"%.1f".format(totalTouchDelta)}", + "xDelta=${"%.1f".format(totalTouchDeltaActive)}", "xTranslation=${"%.1f".format(previousXTranslation)}", "pre=${"%.0f".format(staticThresholdProgress(previousXTranslation) * 100)}%", "post=${"%.0f".format(fullScreenProgress(previousXTranslation) * 100)}%" @@ -1023,7 +1052,7 @@ class BackPanelController internal constructor( } drawVerticalLine(x = params.staticTriggerThreshold, color = Color.BLUE) - drawVerticalLine(x = params.deactivationSwipeTriggerThreshold, color = Color.BLUE) + drawVerticalLine(x = params.deactivationTriggerThreshold, color = Color.BLUE) drawVerticalLine(x = startX, color = Color.GREEN) drawVerticalLine(x = previousXTranslation, color = Color.DKGRAY) } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt index c9d8c8495dcc..876c74a9f3e3 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt @@ -72,9 +72,11 @@ data class EdgePanelParams(private var resources: Resources) { private set var reactivationTriggerThreshold: Float = 0f private set - var deactivationSwipeTriggerThreshold: Float = 0f + var deactivationTriggerThreshold: Float = 0f get() = -field private set + lateinit var dynamicTriggerThresholdRange: ClosedRange<Float> + private set var swipeProgressThreshold: Float = 0f private set @@ -122,8 +124,10 @@ data class EdgePanelParams(private var resources: Resources) { staticTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold) reactivationTriggerThreshold = getDimen(R.dimen.navigation_edge_action_reactivation_drag_threshold) - deactivationSwipeTriggerThreshold = + deactivationTriggerThreshold = getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold) + dynamicTriggerThresholdRange = + reactivationTriggerThreshold..deactivationTriggerThreshold swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold) entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f) @@ -136,7 +140,6 @@ data class EdgePanelParams(private var resources: Resources) { edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f) heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f) - val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.76f) val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f) val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f) val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f) @@ -150,7 +153,7 @@ data class EdgePanelParams(private var resources: Resources) { horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin), scale = getDimenFloat(R.dimen.navigation_edge_entry_scale), scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width), - horizontalTranslationSpring = entryActiveHorizontalTranslationSpring, + horizontalTranslationSpring = createSpring(500f, 0.76f), verticalTranslationSpring = createSpring(30000f, 1f), scaleSpring = createSpring(120f, 0.8f), arrowDimens = ArrowDimens( @@ -202,7 +205,7 @@ data class EdgePanelParams(private var resources: Resources) { activeIndicator = BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin), scale = getDimenFloat(R.dimen.navigation_edge_active_scale), - horizontalTranslationSpring = entryActiveHorizontalTranslationSpring, + horizontalTranslationSpring = createSpring(1000f, 0.7f), scaleSpring = createSpring(450f, 0.39f), scalePivotX = getDimen(R.dimen.navigation_edge_active_background_width), arrowDimens = ArrowDimens( @@ -222,8 +225,8 @@ data class EdgePanelParams(private var resources: Resources) { farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners), widthSpring = createSpring(850f, 0.75f), heightSpring = createSpring(10000f, 1f), - edgeCornerRadiusSpring = createSpring(600f, 0.36f), - farCornerRadiusSpring = createSpring(2500f, 0.855f), + edgeCornerRadiusSpring = createSpring(2600f, 0.855f), + farCornerRadiusSpring = createSpring(1200f, 0.30f), ) ) @@ -250,10 +253,10 @@ data class EdgePanelParams(private var resources: Resources) { getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners), farCornerRadius = getDimen(R.dimen.navigation_edge_pre_threshold_far_corners), - widthSpring = createSpring(250f, 0.65f), + widthSpring = createSpring(400f, 0.65f), heightSpring = createSpring(1500f, 0.45f), - farCornerRadiusSpring = createSpring(200f, 1f), - edgeCornerRadiusSpring = createSpring(150f, 0.5f), + farCornerRadiusSpring = createSpring(300f, 1f), + edgeCornerRadiusSpring = createSpring(250f, 0.5f), ) ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index 0641eec154bb..a3b901b675ed 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -44,6 +44,7 @@ import android.widget.ImageView import android.widget.TextView import androidx.annotation.GuardedBy import androidx.annotation.VisibleForTesting +import androidx.annotation.WorkerThread import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -201,7 +202,7 @@ class FgsManagerControllerImpl @Inject constructor( @GuardedBy("lock") private val appListAdapter: AppListAdapter = AppListAdapter() - @GuardedBy("lock") + /* Only mutate on the background thread */ private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap() private val userTrackerCallback = object : UserTracker.Callback { @@ -374,11 +375,6 @@ class FgsManagerControllerImpl @Inject constructor( override fun showDialog(expandable: Expandable?) { synchronized(lock) { if (dialog == null) { - - runningTaskIdentifiers.keys.forEach { - it.updateUiControl() - } - val dialog = SystemUIDialog(context) dialog.setTitle(R.string.fgs_manager_dialog_title) dialog.setMessage(R.string.fgs_manager_dialog_message) @@ -421,33 +417,53 @@ class FgsManagerControllerImpl @Inject constructor( } } - backgroundExecutor.execute { - synchronized(lock) { - updateAppItemsLocked() - } - } + updateAppItemsLocked(refreshUiControls = true) } } } @GuardedBy("lock") - private fun updateAppItemsLocked() { + private fun updateAppItemsLocked(refreshUiControls: Boolean = false) { if (dialog == null) { - runningApps.clear() + backgroundExecutor.execute { + clearRunningApps() + } return } - val addedPackages = runningTaskIdentifiers.keys.filter { - currentProfileIds.contains(it.userId) && + val packagesToStartTime = runningTaskIdentifiers.mapValues { it.value.startTime } + val profileIds = currentProfileIds.toSet() + backgroundExecutor.execute { + updateAppItems(packagesToStartTime, profileIds, refreshUiControls) + } + } + + /** + * Must be called on the background thread. + */ + @WorkerThread + private fun updateAppItems( + packages: Map<UserPackage, Long>, + profileIds: Set<Int>, + refreshUiControls: Boolean = true + ) { + if (refreshUiControls) { + packages.forEach { (pkg, _) -> + pkg.updateUiControl() + } + } + + val addedPackages = packages.keys.filter { + profileIds.contains(it.userId) && it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true } - val removedPackages = runningApps.keys.filter { !runningTaskIdentifiers.containsKey(it) } + val removedPackages = runningApps.keys.filter { it !in packages } addedPackages.forEach { val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId) runningApps[it] = RunningApp( it.userId, it.packageName, - runningTaskIdentifiers[it]!!.startTime, it.uiControl, + packages[it]!!, it.uiControl, packageManager.getApplicationLabel(ai), packageManager.getUserBadgedIcon( packageManager.getApplicationIcon(ai), UserHandle.of(it.userId) @@ -472,6 +488,14 @@ class FgsManagerControllerImpl @Inject constructor( } } + /** + * Must be called on the background thread. + */ + @WorkerThread + private fun clearRunningApps() { + runningApps.clear() + } + private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) { logEvent(stopped = true, packageName, userId, timeStarted) val userPackageKey = UserPackage(userId, packageName) diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 3711a2f39b7b..fbf134db15f1 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -103,7 +103,7 @@ open class UserTrackerImpl internal constructor( @GuardedBy("callbacks") private val callbacks: MutableList<DataItem> = ArrayList() - fun initialize(startingUser: Int) { + open fun initialize(startingUser: Int) { if (initialized) { return } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index b0b9ab240988..4c6673cca473 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -17,15 +17,28 @@ package com.android.systemui.shade import android.view.LayoutInflater +import com.android.systemui.CoreStartable import com.android.systemui.R +import com.android.systemui.biometrics.AuthRippleController +import com.android.systemui.biometrics.AuthRippleView import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import dagger.Binds import dagger.Module import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap /** Module for classes related to the notification shade. */ @Module abstract class ShadeModule { + + @Binds + @IntoMap + @ClassKey(AuthRippleController::class) + abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable + companion object { @Provides @SysUISingleton @@ -59,5 +72,22 @@ abstract class ShadeModule { ): NotificationPanelView { return notificationShadeWindowView.findViewById(R.id.notification_panel) } + + @Provides + @SysUISingleton + fun providesLightRevealScrim( + notificationShadeWindowView: NotificationShadeWindowView, + ): LightRevealScrim { + return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim) + } + + // TODO(b/277762009): Only allow this view's controller to inject the view. See above. + @Provides + @SysUISingleton + fun providesAuthRippleView( + notificationShadeWindowView: NotificationShadeWindowView, + ): AuthRippleView? { + return notificationShadeWindowView.findViewById(R.id.auth_ripple) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt index 6deef2e11828..76ff97ddb61b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt @@ -383,11 +383,11 @@ class RoundableState( } fun debugString() = buildString { - append("TargetView: ${targetView.hashCode()} ") - append("Top: $topRoundness ") - append(topRoundnessMap.map { "${it.key} ${it.value}" }) - append(" Bottom: $bottomRoundness ") - append(bottomRoundnessMap.map { "${it.key} ${it.value}" }) + append("Roundable { ") + append("top: { value: $topRoundness, requests: $topRoundnessMap}") + append(", ") + append("bottom: { value: $bottomRoundness, requests: $bottomRoundnessMap}") + append("}") } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index 0529c94ed59a..23b5241b79ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -38,8 +38,7 @@ import com.android.systemui.statusbar.notification.collection.provider.LaunchFul import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.dagger.IncomingHeader import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider import com.android.systemui.statusbar.notification.logKey import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP import com.android.systemui.statusbar.policy.HeadsUpManager @@ -69,12 +68,12 @@ class HeadsUpCoordinator @Inject constructor( private val mSystemClock: SystemClock, private val mHeadsUpManager: HeadsUpManager, private val mHeadsUpViewBinder: HeadsUpViewBinder, - private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider, + private val mVisualInterruptionDecisionProvider: VisualInterruptionDecisionProvider, private val mRemoteInputManager: NotificationRemoteInputManager, private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider, private val mFlags: NotifPipelineFlags, @IncomingHeader private val mIncomingHeaderController: NodeController, - @Main private val mExecutor: DelayableExecutor, + @Main private val mExecutor: DelayableExecutor ) : Coordinator { private val mEntriesBindingUntil = ArrayMap<String, Long>() private val mEntriesUpdateTimes = ArrayMap<String, Long>() @@ -388,18 +387,21 @@ class HeadsUpCoordinator @Inject constructor( override fun onEntryAdded(entry: NotificationEntry) { // First check whether this notification should launch a full screen intent, and // launch it if needed. - val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry) - mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision) - if (fsiDecision.shouldLaunch) { + val fsiDecision = + mVisualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry) + mVisualInterruptionDecisionProvider.logFullScreenIntentDecision(fsiDecision) + if (fsiDecision.shouldInterrupt) { mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry) - } else if (fsiDecision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) { + } else if (fsiDecision.wouldInterruptWithoutDnd) { // If DND was the only reason this entry was suppressed, note it for potential // reconsideration on later ranking updates. addForFSIReconsideration(entry, mSystemClock.currentTimeMillis()) } - // shouldHeadsUp includes check for whether this notification should be filtered - val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry) + // makeAndLogHeadsUpDecision includes check for whether this notification should be + // filtered + val shouldHeadsUpEver = + mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt mPostedEntries[entry.key] = PostedEntry( entry, wasAdded = true, @@ -420,7 +422,8 @@ class HeadsUpCoordinator @Inject constructor( * up again. */ override fun onEntryUpdated(entry: NotificationEntry) { - val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry) + val shouldHeadsUpEver = + mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt val shouldHeadsUpAgain = shouldHunAgain(entry) val isAlerting = mHeadsUpManager.isAlerting(entry.key) val isBinding = isEntryBinding(entry) @@ -510,26 +513,26 @@ class HeadsUpCoordinator @Inject constructor( // If any of these entries are no longer suppressed, launch the FSI now. if (isCandidateForFSIReconsideration(entry)) { val decision = - mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry) - if (decision.shouldLaunch) { + mVisualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision( + entry + ) + if (decision.shouldInterrupt) { // Log both the launch of the full screen and also that this was via a // ranking update, and finally revoke candidacy for FSI reconsideration - mLogger.logEntryUpdatedToFullScreen(entry.key, decision.name) - mNotificationInterruptStateProvider.logFullScreenIntentDecision( - entry, decision) + mLogger.logEntryUpdatedToFullScreen(entry.key, decision.logReason) + mVisualInterruptionDecisionProvider.logFullScreenIntentDecision(decision) mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry) mFSIUpdateCandidates.remove(entry.key) // if we launch the FSI then this is no longer a candidate for HUN continue - } else if (decision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) { + } else if (decision.wouldInterruptWithoutDnd) { // decision has not changed; no need to log } else { // some other condition is now blocking FSI; log that and revoke candidacy // for FSI reconsideration - mLogger.logEntryDisqualifiedFromFullScreen(entry.key, decision.name) - mNotificationInterruptStateProvider.logFullScreenIntentDecision( - entry, decision) + mLogger.logEntryDisqualifiedFromFullScreen(entry.key, decision.logReason) + mVisualInterruptionDecisionProvider.logFullScreenIntentDecision(decision) mFSIUpdateCandidates.remove(entry.key) } } @@ -539,13 +542,18 @@ class HeadsUpCoordinator @Inject constructor( // state // - if it is present in PostedEntries and the previous state of shouldHeadsUp // differs from the updated one - val shouldHeadsUpEver = mNotificationInterruptStateProvider.checkHeadsUp(entry, - /* log= */ false) + val decision = + mVisualInterruptionDecisionProvider.makeUnloggedHeadsUpDecision(entry) + val shouldHeadsUpEver = decision.shouldInterrupt val postedShouldHeadsUpEver = mPostedEntries[entry.key]?.shouldHeadsUpEver ?: false val shouldUpdateEntry = postedShouldHeadsUpEver != shouldHeadsUpEver if (shouldUpdateEntry) { - mLogger.logEntryUpdatedByRanking(entry.key, shouldHeadsUpEver) + mLogger.logEntryUpdatedByRanking( + entry.key, + shouldHeadsUpEver, + decision.logReason + ) onEntryUpdated(entry) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt index e9365594fad7..32c3c6665b6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt @@ -61,12 +61,13 @@ class HeadsUpCoordinatorLogger constructor( }) } - fun logEntryUpdatedByRanking(key: String, shouldHun: Boolean) { + fun logEntryUpdatedByRanking(key: String, shouldHun: Boolean, reason: String) { buffer.log(TAG, LogLevel.DEBUG, { str1 = key bool1 = shouldHun + str2 = reason }, { - "updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1" + "updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1 because $str2" }) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt index f2216fce6fef..ebba4b1aa265 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt @@ -36,6 +36,8 @@ class NotificationInterruptStateProviderWrapper( SHOULD_INTERRUPT(shouldInterrupt = true), SHOULD_NOT_INTERRUPT(shouldInterrupt = false); + override val logReason = "unknown" + companion object { fun of(booleanDecision: Boolean) = if (booleanDecision) SHOULD_INTERRUPT else SHOULD_NOT_INTERRUPT @@ -49,6 +51,7 @@ class NotificationInterruptStateProviderWrapper( ) : FullScreenIntentDecision { override val shouldInterrupt = originalDecision.shouldLaunch override val wouldInterruptWithoutDnd = originalDecision == NO_FSI_SUPPRESSED_ONLY_BY_DND + override val logReason = originalDecision.name } override fun addSuppressor(suppressor: NotificationInterruptSuppressor) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt index c0f4fcda56bb..8024016fd3fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt @@ -32,9 +32,12 @@ interface VisualInterruptionDecisionProvider { * full-screen intent decisions. * * @property[shouldInterrupt] whether a visual interruption should be triggered + * @property[logReason] a log-friendly string explaining the reason for the decision; should be + * used *only* for logging, not decision-making */ interface Decision { val shouldInterrupt: Boolean + val logReason: String } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 10cdae3b967c..e468a59d4eb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -3649,16 +3649,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } else { pw.println("no viewState!!!"); } - pw.println("Roundness: " + getRoundableState().debugString()); + pw.println(getRoundableState().debugString()); int transientViewCount = mChildrenContainer == null ? 0 : mChildrenContainer.getTransientViewCount(); if (mIsSummaryWithChildren || transientViewCount > 0) { - pw.println(); - pw.print("ChildrenContainer"); - pw.print(" visibility: " + mChildrenContainer.getVisibility()); - pw.print(", alpha: " + mChildrenContainer.getAlpha()); - pw.print(", translationY: " + mChildrenContainer.getTranslationY()); + pw.println(mChildrenContainer.debugString()); pw.println(); List<ExpandableNotificationRow> notificationChildren = getAttachedChildren(); pw.print("Children: " + notificationChildren.size() + " {"); @@ -3726,12 +3722,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - public String toString() { - String roundableStateDebug = "RoundableState = " + getRoundableState().debugString(); - return "ExpandableNotificationRow:" + hashCode() + " { " + roundableStateDebug + " }"; - } - - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (mUseRoundnessSourceTypes) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 197caa2d5645..9aa50e989ff1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -352,7 +352,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); super.dump(pw, args); DumpUtilsKt.withIncreasedIndent(pw, () -> { - pw.println("Roundness: " + getRoundableState().debugString()); + pw.println(getRoundableState().debugString()); if (DUMP_VERBOSE) { pw.println("mCustomOutline: " + mCustomOutline + " mOutlineRect: " + mOutlineRect); pw.println("mOutlineAlpha: " + mOutlineAlpha); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index 49f17b664a20..6bbeebfdb431 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -17,8 +17,6 @@ package com.android.systemui.statusbar.notification.row; import android.annotation.ColorInt; -import android.annotation.DrawableRes; -import android.annotation.StringRes; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -50,8 +48,8 @@ public class FooterView extends StackScrollerDecorView { // Footer label private TextView mSeenNotifsFooterTextView; - private @StringRes int mSeenNotifsFilteredText; - private int mUnlockIconSize; + private String mSeenNotifsFilteredText; + private Drawable mSeenNotifsFilteredIcon; public FooterView(Context context, AttributeSet attrs) { super(context, attrs); @@ -87,30 +85,12 @@ public class FooterView extends StackScrollerDecorView { mManageButton = findViewById(R.id.manage_text); mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer); updateResources(); - updateText(); + updateContent(); updateColors(); } - public void setFooterLabelTextAndIcon(@StringRes int text, @DrawableRes int icon) { - mSeenNotifsFilteredText = text; - if (mSeenNotifsFilteredText != 0) { - mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText); - } else { - mSeenNotifsFooterTextView.setText(null); - } - Drawable drawable; - if (icon == 0) { - drawable = null; - } else { - drawable = getResources().getDrawable(icon); - drawable.setBounds(0, 0, mUnlockIconSize, mUnlockIconSize); - } - mSeenNotifsFooterTextView.setCompoundDrawablesRelative(drawable, null, null, null); - updateFooterVisibilityMode(); - } - - private void updateFooterVisibilityMode() { - if (mSeenNotifsFilteredText != 0) { + public void setFooterLabelVisible(boolean isVisible) { + if (isVisible) { mManageButton.setVisibility(View.GONE); mClearAllButton.setVisibility(View.GONE); mSeenNotifsFooterTextView.setVisibility(View.VISIBLE); @@ -141,10 +121,10 @@ public class FooterView extends StackScrollerDecorView { return; } mShowHistory = showHistory; - updateText(); + updateContent(); } - private void updateText() { + private void updateContent() { if (mShowHistory) { mManageButton.setText(mManageNotificationHistoryText); mManageButton.setContentDescription(mManageNotificationHistoryText); @@ -152,6 +132,9 @@ public class FooterView extends StackScrollerDecorView { mManageButton.setText(mManageNotificationText); mManageButton.setContentDescription(mManageNotificationText); } + mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText); + mSeenNotifsFooterTextView + .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null); } public boolean isHistoryShown() { @@ -166,7 +149,7 @@ public class FooterView extends StackScrollerDecorView { mClearAllButton.setContentDescription( mContext.getString(R.string.accessibility_clear_all)); updateResources(); - updateText(); + updateContent(); } /** @@ -190,8 +173,11 @@ public class FooterView extends StackScrollerDecorView { mManageNotificationText = getContext().getString(R.string.manage_notifications_text); mManageNotificationHistoryText = getContext() .getString(R.string.manage_notifications_history_text); - mUnlockIconSize = getResources() + int unlockIconSize = getResources() .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size); + mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text); + mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed); + mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 40f55bd3726c..160a2309bfcc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -1546,9 +1546,11 @@ public class NotificationChildrenContainer extends ViewGroup mUseRoundnessSourceTypes = enabled; } - @Override - public String toString() { - String roundableStateDebug = "RoundableState = " + getRoundableState().debugString(); - return "NotificationChildrenContainer:" + hashCode() + " { " + roundableStateDebug + " }"; + public String debugString() { + return TAG + " { " + + "visibility: " + getVisibility() + + ", alpha: " + getAlpha() + + ", translationY: " + getTranslationY() + + ", roundableState: " + getRoundableState().debugString() + "}"; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 587783d87ba5..5c322d71fd59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -4746,13 +4746,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mFooterView.setVisible(visible, animate); mFooterView.setSecondaryVisible(showDismissView, animate); mFooterView.showHistory(showHistory); - if (mHasFilteredOutSeenNotifications) { - mFooterView.setFooterLabelTextAndIcon( - R.string.unlock_to_see_notif_text, - R.drawable.ic_friction_lock_closed); - } else { - mFooterView.setFooterLabelTextAndIcon(0, 0); - } + mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications); } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index e66a8b16173f..5654772695ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -452,7 +452,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { protected PhoneStatusBarView mStatusBarView; private PhoneStatusBarViewController mPhoneStatusBarViewController; private PhoneStatusBarTransitions mStatusBarTransitions; - private AuthRippleController mAuthRippleController; + private final AuthRippleController mAuthRippleController; @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING; protected final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarInitializer mStatusBarInitializer; @@ -460,7 +460,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @VisibleForTesting DozeServiceHost mDozeServiceHost; - private LightRevealScrim mLightRevealScrim; + private final LightRevealScrim mLightRevealScrim; private PowerButtonReveal mPowerButtonReveal; private boolean mWakeUpComingFromTouch; @@ -768,6 +768,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { ScrimController scrimController, Lazy<LockscreenWallpaper> lockscreenWallpaperLazy, Lazy<BiometricUnlockController> biometricUnlockControllerLazy, + AuthRippleController authRippleController, DozeServiceHost dozeServiceHost, PowerManager powerManager, ScreenPinningRequest screenPinningRequest, @@ -810,6 +811,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { IDreamManager dreamManager, Lazy<CameraLauncher> cameraLauncherLazy, Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy, + LightRevealScrim lightRevealScrim, AlternateBouncerInteractor alternateBouncerInteractor, UserTracker userTracker, Provider<FingerprintManager> fingerprintManager @@ -866,6 +868,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScreenPinningRequest = screenPinningRequest; mDozeScrimController = dozeScrimController; mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; + mAuthRippleController = authRippleController; mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy; mVolumeComponent = volumeComponent; mCommandQueue = commandQueue; @@ -932,6 +935,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { wiredChargingRippleController.registerCallbacks(); mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy; + mLightRevealScrim = lightRevealScrim; // Based on teamfood flag, turn predictive back dispatch on at runtime. if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) { @@ -1326,8 +1330,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }); mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront); - mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); - if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) { LightRevealScrimViewBinder.bind( mLightRevealScrim, mLightRevealScrimViewModelLazy.get()); @@ -1667,8 +1669,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mPresenter = mCentralSurfacesComponent.getNotificationPresenter(); mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter(); mNotificationShelfController = mCentralSurfacesComponent.getNotificationShelfController(); - mAuthRippleController = mCentralSurfacesComponent.getAuthRippleController(); - mAuthRippleController.init(); mHeadsUpManager.addListener(mCentralSurfacesComponent.getStatusBarHeadsUpChangeListener()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt index fbe374c32952..c0269b8d6fca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt @@ -23,10 +23,10 @@ import android.view.WindowInsetsController.Appearance import android.view.WindowInsetsController.Behavior import com.android.internal.statusbar.LetterboxDetails import com.android.internal.view.AppearanceRegion +import com.android.systemui.Dumpable +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.SysuiStatusBarStateController -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope import java.io.PrintWriter import javax.inject.Inject @@ -37,7 +37,7 @@ import javax.inject.Inject * It is responsible for modifying any attributes if necessary, and then notifying the other * downstream listeners. */ -@CentralSurfacesScope +@SysUISingleton class SystemBarAttributesListener @Inject internal constructor( @@ -45,18 +45,14 @@ internal constructor( private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator, private val statusBarStateController: SysuiStatusBarStateController, private val lightBarController: LightBarController, - private val dumpManager: DumpManager, -) : CentralSurfacesComponent.Startable, StatusBarBoundsProvider.BoundsChangeListener { + dumpManager: DumpManager, +) : Dumpable, StatusBarBoundsProvider.BoundsChangeListener { private var lastLetterboxAppearance: LetterboxAppearance? = null private var lastSystemBarAttributesParams: SystemBarAttributesParams? = null - override fun start() { - dumpManager.registerDumpable(javaClass.simpleName, this::dump) - } - - override fun stop() { - dumpManager.unregisterDumpable(javaClass.simpleName) + init { + dumpManager.registerCriticalDumpable(this) } override fun onStatusBarBoundsChanged() { @@ -128,7 +124,7 @@ internal constructor( private fun shouldUseLetterboxAppearance(letterboxDetails: Array<LetterboxDetails>) = letterboxDetails.isNotEmpty() - private fun dump(printWriter: PrintWriter, strings: Array<String>) { + override fun dump(printWriter: PrintWriter, strings: Array<String>) { printWriter.println("lastSystemBarAttributesParams: $lastSystemBarAttributesParams") printWriter.println("lastLetterboxAppearance: $lastLetterboxAppearance") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java index d80e1d3c9f11..b16d16a01df8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java @@ -122,11 +122,6 @@ public interface CentralSurfacesComponent { LockIconViewController getLockIconViewController(); /** - * Creates an AuthRippleViewController. Must be init after creation. - */ - AuthRippleController getAuthRippleController(); - - /** * Creates a StatusBarHeadsUpChangeListener. */ StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java index f72e74b77aea..7ded90f7cf25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java @@ -16,11 +16,7 @@ package com.android.systemui.statusbar.phone.dagger; -import com.android.systemui.statusbar.phone.SystemBarAttributesListener; - -import dagger.Binds; import dagger.Module; -import dagger.multibindings.IntoSet; import dagger.multibindings.Multibinds; import java.util.Set; @@ -29,9 +25,4 @@ import java.util.Set; interface CentralSurfacesStartableModule { @Multibinds Set<CentralSurfacesComponent.Startable> multibindStartables(); - - @Binds - @IntoSet - CentralSurfacesComponent.Startable sysBarAttrsListener( - SystemBarAttributesListener systemBarAttributesListener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index ef86162aecf5..1a943e79a79b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -29,7 +29,6 @@ import com.android.keyguard.LockIconView; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.battery.BatteryMeterViewController; -import com.android.systemui.biometrics.AuthRippleView; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; @@ -150,15 +149,6 @@ public abstract class StatusBarViewModule { /** */ @Provides - @CentralSurfacesComponent.CentralSurfacesScope - @Nullable - public static AuthRippleView getAuthRippleView( - NotificationShadeWindowView notificationShadeWindowView) { - return notificationShadeWindowView.findViewById(R.id.auth_ripple); - } - - /** */ - @Provides @Named(SHADE_HEADER) @CentralSurfacesComponent.CentralSurfacesScope public static MotionLayout getLargeScreenShadeHeaderBarView( diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt new file mode 100644 index 000000000000..1c17fc34a34a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt @@ -0,0 +1,93 @@ +package com.android.systemui.wallet.controller + +import android.content.Intent +import android.os.IBinder +import android.util.Log +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LifecycleService +import androidx.lifecycle.lifecycleScope +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * Serves as an intermediary between QuickAccessWalletService and ContextualCardManager (in PCC). + * When QuickAccessWalletService has a list of store locations, WalletContextualLocationsService + * will send them to ContextualCardManager. When the user enters a store location, this Service + * class will be notified, and WalletContextualSuggestionsController will be updated. + */ +class WalletContextualLocationsService +@Inject +constructor( + private val controller: WalletContextualSuggestionsController, + private val featureFlags: FeatureFlags, +) : LifecycleService() { + private var listener: IWalletCardsUpdatedListener? = null + private var scope: CoroutineScope = this.lifecycleScope + + @VisibleForTesting + constructor( + controller: WalletContextualSuggestionsController, + featureFlags: FeatureFlags, + scope: CoroutineScope, + ) : this(controller, featureFlags) { + this.scope = scope + } + + override fun onBind(intent: Intent): IBinder { + super.onBind(intent) + scope.launch { + controller.allWalletCards.collect { cards -> + val cardsSize = cards.size + Log.i(TAG, "Number of cards registered $cardsSize") + listener?.registerNewWalletCards(cards) + } + } + return binder + } + + override fun onDestroy() { + super.onDestroy() + listener = null + } + + @VisibleForTesting + fun addWalletCardsUpdatedListenerInternal(listener: IWalletCardsUpdatedListener) { + if (!featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) { + return + } + this.listener = listener // Currently, only one listener at a time is supported + // Sends WalletCard objects from QuickAccessWalletService to the listener + val cards = controller.allWalletCards.value + if (!cards.isEmpty()) { + val cardsSize = cards.size + Log.i(TAG, "Number of cards registered $cardsSize") + listener.registerNewWalletCards(cards) + } + } + + @VisibleForTesting + fun onWalletContextualLocationsStateUpdatedInternal(storeLocations: List<String>) { + if (!featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) { + return + } + Log.i(TAG, "Entered store $storeLocations") + controller.setSuggestionCardIds(storeLocations.toSet()) + } + + private val binder: IWalletContextualLocationsService.Stub + = object : IWalletContextualLocationsService.Stub() { + override fun addWalletCardsUpdatedListener(listener: IWalletCardsUpdatedListener) { + addWalletCardsUpdatedListenerInternal(listener) + } + override fun onWalletContextualLocationsStateUpdated(storeLocations: List<String>) { + onWalletContextualLocationsStateUpdatedInternal(storeLocations) + } + } + + companion object { + private const val TAG = "WalletContextualLocationsService" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt index 518f5a774d7f..b3ad9b0c6a37 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emptyFlow @@ -57,7 +58,8 @@ constructor( ) { private val cardsReceivedCallbacks: MutableSet<(List<WalletCard>) -> Unit> = mutableSetOf() - private val allWalletCards: Flow<List<WalletCard>> = + /** All potential cards. */ + val allWalletCards: StateFlow<List<WalletCard>> = if (featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) { // TODO(b/237409756) determine if we should debounce this so we don't call the service // too frequently. Also check if the list actually changed before calling callbacks. @@ -107,12 +109,13 @@ constructor( emptyList() ) } else { - emptyFlow() + MutableStateFlow<List<WalletCard>>(emptyList()).asStateFlow() } private val _suggestionCardIds: MutableStateFlow<Set<String>> = MutableStateFlow(emptySet()) private val contextualSuggestionsCardIds: Flow<Set<String>> = _suggestionCardIds.asStateFlow() + /** Contextually-relevant cards. */ val contextualSuggestionCards: Flow<List<WalletCard>> = combine(allWalletCards, contextualSuggestionsCardIds) { cards, ids -> val ret = diff --git a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java index 9429d8991090..efba3e5d9c34 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java @@ -35,6 +35,8 @@ import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; import dagger.multibindings.StringKey; +import android.app.Service; +import com.android.systemui.wallet.controller.WalletContextualLocationsService; /** * Module for injecting classes in Wallet. @@ -42,6 +44,12 @@ import dagger.multibindings.StringKey; @Module public abstract class WalletModule { + @Binds + @IntoMap + @ClassKey(WalletContextualLocationsService.class) + abstract Service bindWalletContextualLocationsService( + WalletContextualLocationsService service); + /** */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index a4b093d1832a..a5365fbc3d5d 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -66,7 +66,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ZenModeController; @@ -100,7 +100,7 @@ public class BubblesManager { private final INotificationManager mNotificationManager; private final IDreamManager mDreamManager; private final NotificationVisibilityProvider mVisibilityProvider; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; + private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider; private final NotificationLockscreenUserManager mNotifUserManager; private final CommonNotifCollection mCommonNotifCollection; private final NotifPipeline mNotifPipeline; @@ -126,7 +126,7 @@ public class BubblesManager { INotificationManager notificationManager, IDreamManager dreamManager, NotificationVisibilityProvider visibilityProvider, - NotificationInterruptStateProvider interruptionStateProvider, + VisualInterruptionDecisionProvider visualInterruptionDecisionProvider, ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager, CommonNotifCollection notifCollection, @@ -145,7 +145,7 @@ public class BubblesManager { notificationManager, dreamManager, visibilityProvider, - interruptionStateProvider, + visualInterruptionDecisionProvider, zenModeController, notifUserManager, notifCollection, @@ -169,7 +169,7 @@ public class BubblesManager { INotificationManager notificationManager, IDreamManager dreamManager, NotificationVisibilityProvider visibilityProvider, - NotificationInterruptStateProvider interruptionStateProvider, + VisualInterruptionDecisionProvider visualInterruptionDecisionProvider, ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager, CommonNotifCollection notifCollection, @@ -185,7 +185,7 @@ public class BubblesManager { mNotificationManager = notificationManager; mDreamManager = dreamManager; mVisibilityProvider = visibilityProvider; - mNotificationInterruptStateProvider = interruptionStateProvider; + mVisualInterruptionDecisionProvider = visualInterruptionDecisionProvider; mNotifUserManager = notifUserManager; mCommonNotifCollection = notifCollection; mNotifPipeline = notifPipeline; @@ -272,7 +272,7 @@ public class BubblesManager { for (NotificationEntry entry : activeEntries) { if (mNotifUserManager.isCurrentProfile(entry.getSbn().getUserId()) && savedBubbleKeys.contains(entry.getKey()) - && mNotificationInterruptStateProvider.shouldBubbleUp(entry) + && shouldBubbleUp(entry) && entry.isBubble()) { result.add(notifToBubbleEntry(entry)); } @@ -416,16 +416,13 @@ public class BubblesManager { } void onEntryAdded(NotificationEntry entry) { - if (mNotificationInterruptStateProvider.shouldBubbleUp(entry) - && entry.isBubble()) { + if (shouldBubbleUp(entry) && entry.isBubble()) { mBubbles.onEntryAdded(notifToBubbleEntry(entry)); } } void onEntryUpdated(NotificationEntry entry, boolean fromSystem) { - boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry); - mBubbles.onEntryUpdated(notifToBubbleEntry(entry), - shouldBubble, fromSystem); + mBubbles.onEntryUpdated(notifToBubbleEntry(entry), shouldBubbleUp(entry), fromSystem); } void onEntryRemoved(NotificationEntry entry) { @@ -438,12 +435,8 @@ public class BubblesManager { for (int i = 0; i < orderedKeys.length; i++) { String key = orderedKeys[i]; final NotificationEntry entry = mCommonNotifCollection.getEntry(key); - BubbleEntry bubbleEntry = entry != null - ? notifToBubbleEntry(entry) - : null; - boolean shouldBubbleUp = entry != null - ? mNotificationInterruptStateProvider.shouldBubbleUp(entry) - : false; + BubbleEntry bubbleEntry = entry != null ? notifToBubbleEntry(entry) : null; + boolean shouldBubbleUp = entry != null ? shouldBubbleUp(entry) : false; pendingOrActiveNotif.put(key, new Pair<>(bubbleEntry, shouldBubbleUp)); } mBubbles.onRankingUpdated(rankingMap, pendingOrActiveNotif); @@ -637,6 +630,10 @@ public class BubblesManager { } } + private boolean shouldBubbleUp(NotificationEntry e) { + return mVisualInterruptionDecisionProvider.makeAndLogBubbleDecision(e).getShouldInterrupt(); + } + /** * Callback for when the BubbleController wants to interact with the notification pipeline to: * - Remove a previously bubbled notification diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt index 8a5c5b58d058..57a355f4e127 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt @@ -106,4 +106,29 @@ class FontInterpolatorTest : SysuiTestCase() { val reversedFont = interp.lerp(endFont, startFont, 0.5f) assertThat(resultFont).isSameInstanceAs(reversedFont) } + + @Test + fun testCacheMaxSize() { + val interp = FontInterpolator() + + val startFont = Font.Builder(sFont) + .setFontVariationSettings("'wght' 100") + .build() + val endFont = Font.Builder(sFont) + .setFontVariationSettings("'wght' 1") + .build() + val resultFont = interp.lerp(startFont, endFont, 0.5f) + for (i in 0..FONT_CACHE_MAX_ENTRIES + 1) { + val f1 = Font.Builder(sFont) + .setFontVariationSettings("'wght' ${i * 100}") + .build() + val f2 = Font.Builder(sFont) + .setFontVariationSettings("'wght' $i") + .build() + interp.lerp(f1, f2, 0.5f) + } + + val cachedFont = interp.lerp(startFont, endFont, 0.5f) + assertThat(resultFont).isNotSameInstanceAs(cachedFont) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 7d9ccb642e47..6b5679a2a92a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -21,6 +21,7 @@ import android.hardware.biometrics.BiometricSourceType import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import android.util.DisplayMetrics import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.keyguard.KeyguardUpdateMonitor @@ -35,7 +36,6 @@ import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.phone.BiometricUnlockController -import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.leak.RotationUtils @@ -66,7 +66,6 @@ class AuthRippleControllerTest : SysuiTestCase() { private lateinit var staticMockSession: MockitoSession private lateinit var controller: AuthRippleController - @Mock private lateinit var mCentralSurfaces: CentralSurfaces @Mock private lateinit var rippleView: AuthRippleView @Mock private lateinit var commandRegistry: CommandRegistry @Mock private lateinit var configurationController: ConfigurationController @@ -92,6 +91,8 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal + private val displayMetrics = DisplayMetrics() + @Captor private lateinit var biometricUnlockListener: ArgumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener> @@ -109,7 +110,6 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(udfpsControllerProvider.get()).thenReturn(udfpsController) controller = AuthRippleController( - mCentralSurfaces, context, authController, configurationController, @@ -120,13 +120,14 @@ class AuthRippleControllerTest : SysuiTestCase() { notificationShadeWindowController, udfpsControllerProvider, statusBarStateController, + displayMetrics, featureFlags, KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)), biometricUnlockController, + lightRevealScrim, rippleView, ) controller.init() - `when`(mCentralSurfaces.lightRevealScrim).thenReturn(lightRevealScrim) } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt index f6ff4b214035..6f9dedf9dda8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt @@ -96,6 +96,16 @@ class KeyboardRepositoryTest : SysuiTestCase() { } @Test + fun emitsDisconnected_whenDeviceWithIdDoesNotExist() = + testScope.runTest { + val deviceListener = captureDeviceListener() + val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + + deviceListener.onInputDeviceAdded(NULL_DEVICE_ID) + assertThat(isKeyboardConnected).isFalse() + } + + @Test fun emitsDisconnected_whenKeyboardDisconnects() = testScope.runTest { val deviceListener = captureDeviceListener() @@ -172,6 +182,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { private const val VIRTUAL_FULL_KEYBOARD_ID = 2 private const val PHYSICAL_NOT_FULL_KEYBOARD_ID = 3 private const val ANOTHER_PHYSICAL_FULL_KEYBOARD_ID = 4 + private const val NULL_DEVICE_ID = 5 private val INPUT_DEVICES_MAP: Map<Int, InputDevice> = mapOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt index 8e32f81b193f..d9428f897f0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt @@ -124,7 +124,7 @@ class BackPanelControllerTest : SysuiTestCase() { continueTouch(START_X + touchSlop.toFloat() + 1) continueTouch( START_X + touchSlop + triggerThreshold - - mBackPanelController.params.deactivationSwipeTriggerThreshold + mBackPanelController.params.deactivationTriggerThreshold ) clearInvocations(backCallback) Thread.sleep(MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index 67128ff5624a..283efe263f04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -38,8 +38,10 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback import com.android.systemui.statusbar.phone.NotificationGroupTestHelper import com.android.systemui.statusbar.policy.HeadsUpManager @@ -52,6 +54,7 @@ import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.time.FakeSystemClock import java.util.ArrayList import java.util.function.Consumer +import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -86,7 +89,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { private val logger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true) private val headsUpManager: HeadsUpManager = mock() private val headsUpViewBinder: HeadsUpViewBinder = mock() - private val notificationInterruptStateProvider: NotificationInterruptStateProvider = mock() + private val visualInterruptionDecisionProvider: VisualInterruptionDecisionProvider = mock() private val remoteInputManager: NotificationRemoteInputManager = mock() private val endLifetimeExtension: OnEndLifetimeExtensionCallback = mock() private val headerController: NodeController = mock() @@ -114,7 +117,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { systemClock, headsUpManager, headsUpViewBinder, - notificationInterruptStateProvider, + visualInterruptionDecisionProvider, remoteInputManager, launchFullScreenIntentProvider, flags, @@ -168,8 +171,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { groupChild2 = helper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250) groupChild3 = helper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150) + // Set the default HUN decision + setDefaultShouldHeadsUp(false) + // Set the default FSI decision - setShouldFullScreen(any(), FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) + setDefaultShouldFullScreen(FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) } @Test @@ -1006,31 +1012,59 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(entry) } + private fun setDefaultShouldHeadsUp(should: Boolean) { + whenever(visualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(any())) + .thenReturn(DecisionImpl.of(should)) + whenever(visualInterruptionDecisionProvider.makeUnloggedHeadsUpDecision(any())) + .thenReturn(DecisionImpl.of(should)) + } + private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) { - whenever(notificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should) - whenever(notificationInterruptStateProvider.checkHeadsUp(eq(entry), any())) - .thenReturn(should) + whenever(visualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry)) + .thenReturn(DecisionImpl.of(should)) + whenever(visualInterruptionDecisionProvider.makeUnloggedHeadsUpDecision(entry)) + .thenReturn(DecisionImpl.of(should)) } - private fun setShouldFullScreen(entry: NotificationEntry, decision: FullScreenIntentDecision) { - whenever(notificationInterruptStateProvider.getFullScreenIntentDecision(entry)) - .thenReturn(decision) + private fun setDefaultShouldFullScreen( + originalDecision: FullScreenIntentDecision + ) { + val provider = visualInterruptionDecisionProvider + whenever(provider.makeUnloggedFullScreenIntentDecision(any())).thenAnswer { + val entry: NotificationEntry = it.getArgument(0) + FullScreenIntentDecisionImpl(entry, originalDecision) + } + } + + private fun setShouldFullScreen( + entry: NotificationEntry, + originalDecision: FullScreenIntentDecision + ) { + whenever( + visualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry) + ).thenAnswer { + FullScreenIntentDecisionImpl(entry, originalDecision) + } } private fun verifyLoggedFullScreenIntentDecision( entry: NotificationEntry, - decision: FullScreenIntentDecision + originalDecision: FullScreenIntentDecision ) { - verify(notificationInterruptStateProvider).logFullScreenIntentDecision(entry, decision) + val decision = withArgCaptor { + verify(visualInterruptionDecisionProvider).logFullScreenIntentDecision(capture()) + } + check(decision is FullScreenIntentDecisionImpl) + assertEquals(entry, decision.originalEntry) + assertEquals(originalDecision, decision.originalDecision) } private fun verifyNoFullScreenIntentDecisionLogged() { - verify(notificationInterruptStateProvider, never()) - .logFullScreenIntentDecision(any(), any()) + verify(visualInterruptionDecisionProvider, never()).logFullScreenIntentDecision(any()) } private fun clearInterruptionProviderInvocations() { - clearInvocations(notificationInterruptStateProvider) + clearInvocations(visualInterruptionDecisionProvider) } private fun finishBind(entry: NotificationEntry) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java index 819a75bffc8e..90cb7341377d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java @@ -24,12 +24,12 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; +import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; @@ -39,7 +39,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(AndroidTestingRunner.class) public class FooterViewTest extends SysuiTestCase { FooterView mView; @@ -102,14 +102,21 @@ public class FooterViewTest extends SysuiTestCase { } @Test - public void testSetFooterLabelTextAndIcon() { - mView.setFooterLabelTextAndIcon( - R.string.unlock_to_see_notif_text, - R.drawable.ic_friction_lock_closed); + public void testSetFooterLabelVisible() { + mView.setFooterLabelVisible(true); assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE); assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.GONE); assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility()) .isEqualTo(View.VISIBLE); } + + @Test + public void testSetFooterLabelInvisible() { + mView.setFooterLabelVisible(false); + assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility()) + .isEqualTo(View.GONE); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index fbbb921f96a0..420c7ae3d2ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -255,6 +255,34 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test + public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + initController(/* viewIsAttached= */ true); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ false, + /* areNotificationsHiddenInShade= */ false); + } + + @Test + public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + initController(/* viewIsAttached= */ true); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(false); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ true, + /* areNotificationsHiddenInShade= */ false); + } + + @Test public void testOnUserChange_verifySensitiveProfile() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); initController(/* viewIsAttached= */ true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 219e6a995680..c83769d84d2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -101,6 +101,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.assist.AssistManager; +import com.android.systemui.biometrics.AuthRippleController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.charging.WiredChargingRippleController; import com.android.systemui.classifier.FalsingCollectorFake; @@ -138,6 +139,7 @@ import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.LightRevealScrim; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -231,10 +233,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private IStatusBarService mBarService; @Mock private IDreamManager mDreamManager; @Mock private LightRevealScrimViewModel mLightRevealScrimViewModel; + @Mock private LightRevealScrim mLightRevealScrim; @Mock private ScrimController mScrimController; @Mock private DozeScrimController mDozeScrimController; @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; @Mock private BiometricUnlockController mBiometricUnlockController; + @Mock private AuthRippleController mAuthRippleController; @Mock private NotificationListener mNotificationListener; @Mock private KeyguardViewMediator mKeyguardViewMediator; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @@ -345,6 +349,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true); // For the Shade to animate during the Back gesture, we must enable the animation flag. mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true); + mFeatureFlags.set(Flags.LIGHT_REVEAL_MIGRATION, true); IThermalService thermalService = mock(IThermalService.class); mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService, @@ -497,6 +502,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mScrimController, mLockscreenWallpaperLazy, mBiometricUnlockControllerLazy, + mAuthRippleController, mDozeServiceHost, mPowerManager, mScreenPinningRequest, mDozeScrimController, @@ -538,6 +544,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mDreamManager, mCameraLauncherLazy, () -> mLightRevealScrimViewModel, + mLightRevealScrim, mAlternateBouncerInteractor, mUserTracker, () -> mFingerprintManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt new file mode 100644 index 000000000000..af1d7881195e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt @@ -0,0 +1,128 @@ +package com.android.systemui.wallet.controller + +import android.app.PendingIntent +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.Icon +import android.os.Looper +import android.service.quickaccesswallet.WalletCard +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.anySet +import org.mockito.Mockito.doNothing +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +@kotlinx.coroutines.ExperimentalCoroutinesApi +class WalletContextualLocationsServiceTest : SysuiTestCase() { + @Mock private lateinit var controller: WalletContextualSuggestionsController + private var featureFlags = FakeFeatureFlags() + private lateinit var underTest: WalletContextualLocationsService + private lateinit var testScope: TestScope + private var listenerRegisteredCount: Int = 0 + private val listener: IWalletCardsUpdatedListener.Stub = object : IWalletCardsUpdatedListener.Stub() { + override fun registerNewWalletCards(cards: List<WalletCard?>) { + listenerRegisteredCount++ + } + } + + @Before + @kotlinx.coroutines.ExperimentalCoroutinesApi + fun setUp() { + MockitoAnnotations.initMocks(this) + doReturn(fakeWalletCards).whenever(controller).allWalletCards + doNothing().whenever(controller).setSuggestionCardIds(anySet()) + + if (Looper.myLooper() == null) Looper.prepare() + + testScope = TestScope() + featureFlags.set(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS, true) + listenerRegisteredCount = 0 + + underTest = WalletContextualLocationsService(controller, featureFlags, testScope.backgroundScope) + } + + @Test + @kotlinx.coroutines.ExperimentalCoroutinesApi + fun addListener() = testScope.runTest { + underTest.addWalletCardsUpdatedListenerInternal(listener) + assertThat(listenerRegisteredCount).isEqualTo(1) + } + + @Test + @kotlinx.coroutines.ExperimentalCoroutinesApi + fun addStoreLocations() = testScope.runTest { + underTest.onWalletContextualLocationsStateUpdatedInternal(ArrayList<String>()) + verify(controller, times(1)).setSuggestionCardIds(anySet()) + } + + @Test + @kotlinx.coroutines.ExperimentalCoroutinesApi + fun updateListenerAndLocationsState() = testScope.runTest { + // binds to the service and adds a listener + val underTestStub = getInterface + underTestStub.addWalletCardsUpdatedListener(listener) + assertThat(listenerRegisteredCount).isEqualTo(1) + + // sends a list of card IDs to the controller + underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>()) + verify(controller, times(1)).setSuggestionCardIds(anySet()) + + // adds another listener + fakeWalletCards.update{ updatedFakeWalletCards } + runCurrent() + assertThat(listenerRegisteredCount).isEqualTo(2) + + // sends another list of card IDs to the controller + underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>()) + verify(controller, times(2)).setSuggestionCardIds(anySet()) + } + + private val fakeWalletCards: MutableStateFlow<List<WalletCard>> + get() { + val intent = Intent(getContext(), WalletContextualLocationsService::class.java) + val pi: PendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE) + val icon: Icon = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888)) + val walletCards: ArrayList<WalletCard> = ArrayList<WalletCard>() + walletCards.add(WalletCard.Builder("card1", icon, "card", pi).build()) + walletCards.add(WalletCard.Builder("card2", icon, "card", pi).build()) + return MutableStateFlow<List<WalletCard>>(walletCards) + } + + private val updatedFakeWalletCards: List<WalletCard> + get() { + val intent = Intent(getContext(), WalletContextualLocationsService::class.java) + val pi: PendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE) + val icon: Icon = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888)) + val walletCards: ArrayList<WalletCard> = ArrayList<WalletCard>() + walletCards.add(WalletCard.Builder("card3", icon, "card", pi).build()) + return walletCards + } + + private val getInterface: IWalletContextualLocationsService + get() { + val intent = Intent() + return IWalletContextualLocationsService.Stub.asInterface(underTest.onBind(intent)) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index bc3a5b7975a7..1510ee89dcd9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -115,6 +115,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.DozeParameters; @@ -398,7 +399,7 @@ public class BubblesTest extends SysuiTestCase { mock(INotificationManager.class), mIDreamManager, mVisibilityProvider, - interruptionStateProvider, + new NotificationInterruptStateProviderWrapper(interruptionStateProvider), mZenModeController, mLockscreenUserManager, mCommonNotifCollection, diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java index 16d2e6b47a54..93531ddea005 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java @@ -16,76 +16,31 @@ package com.android.server.accessibility.magnification; -import android.annotation.NonNull; import android.provider.DeviceConfig; -import com.android.internal.annotations.VisibleForTesting; - -import java.util.concurrent.Executor; - /** * Encapsulates the feature flags for always on magnification. {@see DeviceConfig} * * @hide */ -public class AlwaysOnMagnificationFeatureFlag { +public class AlwaysOnMagnificationFeatureFlag extends MagnificationFeatureFlagBase { private static final String NAMESPACE = DeviceConfig.NAMESPACE_WINDOW_MANAGER; private static final String FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION = "AlwaysOnMagnifier__enable_always_on_magnifier"; - private AlwaysOnMagnificationFeatureFlag() {} - - /** Returns true if the feature flag is enabled for always on magnification */ - public static boolean isAlwaysOnMagnificationEnabled() { - return DeviceConfig.getBoolean( - NAMESPACE, - FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION, - /* defaultValue= */ false); + @Override + String getNamespace() { + return NAMESPACE; } - /** Sets the feature flag. Only used for testing; requires shell permissions. */ - @VisibleForTesting - public static boolean setAlwaysOnMagnificationEnabled(boolean isEnabled) { - return DeviceConfig.setProperty( - NAMESPACE, - FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION, - Boolean.toString(isEnabled), - /* makeDefault= */ false); - } - - /** - * Adds a listener for when the feature flag changes. - * - * <p>{@see DeviceConfig#addOnPropertiesChangedListener( - * String, Executor, DeviceConfig.OnPropertiesChangedListener)} - */ - @NonNull - public static DeviceConfig.OnPropertiesChangedListener addOnChangedListener( - @NonNull Executor executor, @NonNull Runnable listener) { - DeviceConfig.OnPropertiesChangedListener onChangedListener = - properties -> { - if (properties.getKeyset().contains( - FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION)) { - listener.run(); - } - }; - DeviceConfig.addOnPropertiesChangedListener( - NAMESPACE, - executor, - onChangedListener); - - return onChangedListener; + @Override + String getFeatureName() { + return FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION; } - /** - * Remove a listener for when the feature flag changes. - * - * <p>{@see DeviceConfig#addOnPropertiesChangedListener(String, Executor, - * DeviceConfig.OnPropertiesChangedListener)} - */ - public static void removeOnChangedListener( - @NonNull DeviceConfig.OnPropertiesChangedListener onChangedListener) { - DeviceConfig.removeOnPropertiesChangedListener(onChangedListener); + @Override + boolean getDefaultValue() { + return false; } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index ed8a35f45176..fbc7b3cbc63b 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -38,7 +38,6 @@ import android.graphics.Region; import android.hardware.display.DisplayManagerInternal; import android.os.Handler; import android.os.Message; -import android.provider.DeviceConfig; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.MathUtils; @@ -57,6 +56,7 @@ import com.android.internal.R; import com.android.internal.accessibility.common.MagnificationConstants; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService; @@ -110,6 +110,7 @@ public class FullScreenMagnificationController implements private boolean mAlwaysOnMagnificationEnabled = false; private final DisplayManagerInternal mDisplayManagerInternal; + private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag; @NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier; /** @@ -177,9 +178,7 @@ public class FullScreenMagnificationController implements mDisplayId, mMagnificationRegion); mMagnificationRegion.getBounds(mMagnificationBounds); - if (mMagnificationThumbnail == null) { - mMagnificationThumbnail = mThumbnailSupplier.get(); - } + createThumbnailIfSupported(); return true; } @@ -207,7 +206,7 @@ public class FullScreenMagnificationController implements mRegistered = false; unregisterCallbackLocked(mDisplayId, delete); - destroyThumbNail(); + destroyThumbnail(); } mUnregisterPending = false; } @@ -345,7 +344,7 @@ public class FullScreenMagnificationController implements mMagnificationRegion.set(magnified); mMagnificationRegion.getBounds(mMagnificationBounds); - refreshThumbNail(getScale(), getCenterX(), getCenterY()); + refreshThumbnail(getScale(), getCenterX(), getCenterY()); // It's possible that our magnification spec is invalid with the new bounds. // Adjust the current spec's offsets if necessary. @@ -405,9 +404,9 @@ public class FullScreenMagnificationController implements } if (isActivated()) { - updateThumbNail(scale, centerX, centerY); + updateThumbnail(scale, centerX, centerY); } else { - hideThumbNail(); + hideThumbnail(); } } @@ -538,7 +537,7 @@ public class FullScreenMagnificationController implements mIdOfLastServiceToMagnify = INVALID_SERVICE_ID; sendSpecToAnimation(spec, animationCallback); - hideThumbNail(); + hideThumbnail(); return changed; } @@ -596,16 +595,16 @@ public class FullScreenMagnificationController implements } @GuardedBy("mLock") - void updateThumbNail(float scale, float centerX, float centerY) { + void updateThumbnail(float scale, float centerX, float centerY) { if (mMagnificationThumbnail != null) { - mMagnificationThumbnail.updateThumbNail(scale, centerX, centerY); + mMagnificationThumbnail.updateThumbnail(scale, centerX, centerY); } } @GuardedBy("mLock") - void refreshThumbNail(float scale, float centerX, float centerY) { + void refreshThumbnail(float scale, float centerX, float centerY) { if (mMagnificationThumbnail != null) { - mMagnificationThumbnail.setThumbNailBounds( + mMagnificationThumbnail.setThumbnailBounds( mMagnificationBounds, scale, centerX, @@ -615,20 +614,38 @@ public class FullScreenMagnificationController implements } @GuardedBy("mLock") - void hideThumbNail() { + void hideThumbnail() { if (mMagnificationThumbnail != null) { - mMagnificationThumbnail.hideThumbNail(); + mMagnificationThumbnail.hideThumbnail(); + } + } + + @GuardedBy("mLock") + void createThumbnailIfSupported() { + if (mMagnificationThumbnail == null) { + mMagnificationThumbnail = mThumbnailSupplier.get(); + // We call refreshThumbnail when the thumbnail is just created to set current + // magnification bounds to thumbnail. It to prevent the thumbnail size has not yet + // updated properly and thus shows with huge size. (b/276314641) + refreshThumbnail(getScale(), getCenterX(), getCenterY()); } } @GuardedBy("mLock") - void destroyThumbNail() { + void destroyThumbnail() { if (mMagnificationThumbnail != null) { - hideThumbNail(); + hideThumbnail(); mMagnificationThumbnail = null; } } + void onThumbnailFeatureFlagChanged() { + synchronized (mLock) { + destroyThumbnail(); + createThumbnailIfSupported(); + } + } + /** * Updates the current magnification spec. * @@ -768,20 +785,7 @@ public class FullScreenMagnificationController implements lock, magnificationInfoChangedCallback, scaleProvider, - () -> { - if (DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_ACCESSIBILITY, - "enable_magnifier_thumbnail", - /* defaultValue= */ false)) { - return new MagnificationThumbnail( - context, - context.getSystemService(WindowManager.class), - new Handler(context.getMainLooper()) - ); - } - - return null; - }); + /* thumbnailSupplier= */ null); } /** Constructor for tests */ @@ -791,7 +795,7 @@ public class FullScreenMagnificationController implements @NonNull Object lock, @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback, @NonNull MagnificationScaleProvider scaleProvider, - @NonNull Supplier<MagnificationThumbnail> thumbnailSupplier) { + Supplier<MagnificationThumbnail> thumbnailSupplier) { mControllerCtx = ctx; mLock = lock; mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId(); @@ -799,7 +803,41 @@ public class FullScreenMagnificationController implements addInfoChangedCallback(magnificationInfoChangedCallback); mScaleProvider = scaleProvider; mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); - mThumbnailSupplier = thumbnailSupplier; + mMagnificationThumbnailFeatureFlag = new MagnificationThumbnailFeatureFlag(); + mMagnificationThumbnailFeatureFlag.addOnChangedListener( + ConcurrentUtils.DIRECT_EXECUTOR, this::onMagnificationThumbnailFeatureFlagChanged); + if (thumbnailSupplier != null) { + mThumbnailSupplier = thumbnailSupplier; + } else { + mThumbnailSupplier = () -> { + if (mMagnificationThumbnailFeatureFlag.isFeatureFlagEnabled()) { + return new MagnificationThumbnail( + ctx.getContext(), + ctx.getContext().getSystemService(WindowManager.class), + new Handler(ctx.getContext().getMainLooper()) + ); + } + return null; + }; + } + } + + private void onMagnificationThumbnailFeatureFlagChanged() { + synchronized (mLock) { + for (int i = 0; i < mDisplays.size(); i++) { + onMagnificationThumbnailFeatureFlagChanged(mDisplays.keyAt(i)); + } + } + } + + private void onMagnificationThumbnailFeatureFlagChanged(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return; + } + display.onThumbnailFeatureFlagChanged(); + } } /** diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index c1c47f53ab7e..7ee72dfa30fd 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -93,6 +93,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb private final SparseArray<DisableMagnificationCallback> mMagnificationEndRunnableSparseArray = new SparseArray(); + private final AlwaysOnMagnificationFeatureFlag mAlwaysOnMagnificationFeatureFlag; private final MagnificationScaleProvider mScaleProvider; private FullScreenMagnificationController mFullScreenMagnificationController; private WindowMagnificationManager mWindowMagnificationMgr; @@ -151,7 +152,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb mSupportWindowMagnification = context.getPackageManager().hasSystemFeature( FEATURE_WINDOW_MAGNIFICATION); - AlwaysOnMagnificationFeatureFlag.addOnChangedListener( + mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(); + mAlwaysOnMagnificationFeatureFlag.addOnChangedListener( ConcurrentUtils.DIRECT_EXECUTOR, mAms::updateAlwaysOnMagnification); } @@ -710,7 +712,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb } public boolean isAlwaysOnMagnificationFeatureFlagEnabled() { - return AlwaysOnMagnificationFeatureFlag.isAlwaysOnMagnificationEnabled(); + return mAlwaysOnMagnificationFeatureFlag.isFeatureFlagEnabled(); } private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked( diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationFeatureFlagBase.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationFeatureFlagBase.java new file mode 100644 index 000000000000..2965887d8eb3 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationFeatureFlagBase.java @@ -0,0 +1,115 @@ +/* + * 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.accessibility.magnification; + +import android.annotation.NonNull; +import android.os.Binder; +import android.provider.DeviceConfig; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Abstract base class to encapsulates the feature flags for magnification features. + * {@see DeviceConfig} + * + * @hide + */ +abstract class MagnificationFeatureFlagBase { + + abstract String getNamespace(); + abstract String getFeatureName(); + abstract boolean getDefaultValue(); + + private void clearCallingIdentifyAndTryCatch(Runnable tryBlock, Runnable catchBlock) { + try { + Binder.withCleanCallingIdentity(() -> tryBlock.run()); + } catch (Throwable throwable) { + catchBlock.run(); + } + } + + /** Returns true iff the feature flag is readable and enabled */ + public boolean isFeatureFlagEnabled() { + AtomicBoolean isEnabled = new AtomicBoolean(getDefaultValue()); + + clearCallingIdentifyAndTryCatch( + () -> isEnabled.set(DeviceConfig.getBoolean( + getNamespace(), + getFeatureName(), + getDefaultValue())), + () -> isEnabled.set(getDefaultValue())); + + return isEnabled.get(); + } + + /** Sets the feature flag. Only used for testing; requires shell permissions. */ + @VisibleForTesting + public boolean setFeatureFlagEnabled(boolean isEnabled) { + AtomicBoolean success = new AtomicBoolean(getDefaultValue()); + + clearCallingIdentifyAndTryCatch( + () -> success.set(DeviceConfig.setProperty( + getNamespace(), + getFeatureName(), + Boolean.toString(isEnabled), + /* makeDefault= */ false)), + () -> success.set(getDefaultValue())); + + return success.get(); + } + + /** + * Adds a listener for when the feature flag changes. + * + * <p>{@see DeviceConfig#addOnPropertiesChangedListener( + * String, Executor, DeviceConfig.OnPropertiesChangedListener)} + */ + @NonNull + public DeviceConfig.OnPropertiesChangedListener addOnChangedListener( + @NonNull Executor executor, @NonNull Runnable listener) { + DeviceConfig.OnPropertiesChangedListener onChangedListener = + properties -> { + if (properties.getKeyset().contains( + getFeatureName())) { + listener.run(); + } + }; + + clearCallingIdentifyAndTryCatch( + () -> DeviceConfig.addOnPropertiesChangedListener( + getNamespace(), + executor, + onChangedListener), + () -> {}); + + return onChangedListener; + } + + /** + * Remove a listener for when the feature flag changes. + * + * <p>{@see DeviceConfig#addOnPropertiesChangedListener(String, Executor, + * DeviceConfig.OnPropertiesChangedListener)} + */ + public void removeOnChangedListener( + @NonNull DeviceConfig.OnPropertiesChangedListener onChangedListener) { + DeviceConfig.removeOnPropertiesChangedListener(onChangedListener); + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java index 5a783f47ccdc..03fa93d8a3bc 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java @@ -58,7 +58,9 @@ public class MagnificationThumbnail { @VisibleForTesting public final FrameLayout mThumbnailLayout; - private final View mThumbNailView; + private final View mThumbnailView; + private int mThumbnailWidth; + private int mThumbnailHeight; private final WindowManager.LayoutParams mBackgroundParams; private boolean mVisible = false; @@ -66,7 +68,7 @@ public class MagnificationThumbnail { private static final float ASPECT_RATIO = 14f; private static final float BG_ASPECT_RATIO = ASPECT_RATIO / 2f; - private ObjectAnimator mThumbNailAnimator; + private ObjectAnimator mThumbnailAnimator; private boolean mIsFadingIn; /** @@ -79,9 +81,11 @@ public class MagnificationThumbnail { mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); mThumbnailLayout = (FrameLayout) LayoutInflater.from(mContext) .inflate(R.layout.thumbnail_background_view, /* root: */ null); - mThumbNailView = + mThumbnailView = mThumbnailLayout.findViewById(R.id.accessibility_magnification_thumbnail_view); mBackgroundParams = createLayoutParams(); + mThumbnailWidth = 0; + mThumbnailHeight = 0; } /** @@ -90,35 +94,35 @@ public class MagnificationThumbnail { * @param currentBounds the current magnification bounds */ @AnyThread - public void setThumbNailBounds(Rect currentBounds, float scale, float centerX, float centerY) { + public void setThumbnailBounds(Rect currentBounds, float scale, float centerX, float centerY) { if (DEBUG) { - Log.d(LOG_TAG, "setThumbNailBounds " + currentBounds); + Log.d(LOG_TAG, "setThumbnailBounds " + currentBounds); } mHandler.post(() -> { mWindowBounds = currentBounds; setBackgroundBounds(); if (mVisible) { - updateThumbNailMainThread(scale, centerX, centerY); + updateThumbnailMainThread(scale, centerX, centerY); } }); } private void setBackgroundBounds() { Point magnificationBoundary = getMagnificationThumbnailPadding(mContext); - final int thumbNailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO); - final int thumbNailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO); + mThumbnailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO); + mThumbnailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO); int initX = magnificationBoundary.x; int initY = magnificationBoundary.y; - mBackgroundParams.width = thumbNailWidth; - mBackgroundParams.height = thumbNailHeight; + mBackgroundParams.width = mThumbnailWidth; + mBackgroundParams.height = mThumbnailHeight; mBackgroundParams.x = initX; mBackgroundParams.y = initY; } @MainThread - private void showThumbNail() { + private void showThumbnail() { if (DEBUG) { - Log.d(LOG_TAG, "showThumbNail " + mVisible); + Log.d(LOG_TAG, "showThumbnail " + mVisible); } animateThumbnail(true); } @@ -127,14 +131,14 @@ public class MagnificationThumbnail { * Hides thumbnail and removes the view from the window when finished animating. */ @AnyThread - public void hideThumbNail() { - mHandler.post(this::hideThumbNailMainThread); + public void hideThumbnail() { + mHandler.post(this::hideThumbnailMainThread); } @MainThread - private void hideThumbNailMainThread() { + private void hideThumbnailMainThread() { if (DEBUG) { - Log.d(LOG_TAG, "hideThumbNail " + mVisible); + Log.d(LOG_TAG, "hideThumbnail " + mVisible); } if (mVisible) { animateThumbnail(false); @@ -155,14 +159,14 @@ public class MagnificationThumbnail { + " fadeIn: " + fadeIn + " mVisible: " + mVisible + " isFadingIn: " + mIsFadingIn - + " isRunning: " + mThumbNailAnimator + + " isRunning: " + mThumbnailAnimator ); } // Reset countdown to hide automatically - mHandler.removeCallbacks(this::hideThumbNailMainThread); + mHandler.removeCallbacks(this::hideThumbnailMainThread); if (fadeIn) { - mHandler.postDelayed(this::hideThumbNailMainThread, LINGER_DURATION_MS); + mHandler.postDelayed(this::hideThumbnailMainThread, LINGER_DURATION_MS); } if (fadeIn == mIsFadingIn) { @@ -175,18 +179,18 @@ public class MagnificationThumbnail { mVisible = true; } - if (mThumbNailAnimator != null) { - mThumbNailAnimator.cancel(); + if (mThumbnailAnimator != null) { + mThumbnailAnimator.cancel(); } - mThumbNailAnimator = ObjectAnimator.ofFloat( + mThumbnailAnimator = ObjectAnimator.ofFloat( mThumbnailLayout, "alpha", fadeIn ? 1f : 0f ); - mThumbNailAnimator.setDuration( + mThumbnailAnimator.setDuration( fadeIn ? FADE_IN_ANIMATION_DURATION_MS : FADE_OUT_ANIMATION_DURATION_MS ); - mThumbNailAnimator.addListener(new Animator.AnimatorListener() { + mThumbnailAnimator.addListener(new Animator.AnimatorListener() { private boolean mIsCancelled; @Override @@ -231,7 +235,7 @@ public class MagnificationThumbnail { } }); - mThumbNailAnimator.start(); + mThumbnailAnimator.start(); } /** @@ -246,38 +250,48 @@ public class MagnificationThumbnail { * of the viewport, or {@link Float#NaN} to leave unchanged */ @AnyThread - public void updateThumbNail(float scale, float centerX, float centerY) { - mHandler.post(() -> updateThumbNailMainThread(scale, centerX, centerY)); + public void updateThumbnail(float scale, float centerX, float centerY) { + mHandler.post(() -> updateThumbnailMainThread(scale, centerX, centerY)); } @MainThread - private void updateThumbNailMainThread(float scale, float centerX, float centerY) { + private void updateThumbnailMainThread(float scale, float centerX, float centerY) { // Restart the fadeout countdown (or show if it's hidden) - showThumbNail(); + showThumbnail(); - var scaleDown = Float.isNaN(scale) ? mThumbNailView.getScaleX() : 1f / scale; + var scaleDown = Float.isNaN(scale) ? mThumbnailView.getScaleX() : 1f / scale; if (!Float.isNaN(scale)) { - mThumbNailView.setScaleX(scaleDown); - mThumbNailView.setScaleY(scaleDown); + mThumbnailView.setScaleX(scaleDown); + mThumbnailView.setScaleY(scaleDown); + } + float thumbnailWidth; + float thumbnailHeight; + if (mThumbnailView.getWidth() == 0 || mThumbnailView.getHeight() == 0) { + // if the thumbnail view size is not updated correctly, we just use the cached values. + thumbnailWidth = mThumbnailWidth; + thumbnailHeight = mThumbnailHeight; + } else { + thumbnailWidth = mThumbnailView.getWidth(); + thumbnailHeight = mThumbnailView.getHeight(); } if (!Float.isNaN(centerX)) { - var padding = mThumbNailView.getPaddingTop(); + var padding = mThumbnailView.getPaddingTop(); var ratio = 1f / BG_ASPECT_RATIO; - var centerXScaled = centerX * ratio - (mThumbNailView.getWidth() / 2f + padding); - var centerYScaled = centerY * ratio - (mThumbNailView.getHeight() / 2f + padding); + var centerXScaled = centerX * ratio - (thumbnailWidth / 2f + padding); + var centerYScaled = centerY * ratio - (thumbnailHeight / 2f + padding); if (DEBUG) { Log.d( LOG_TAG, - "updateThumbNail centerXScaled : " + centerXScaled + "updateThumbnail centerXScaled : " + centerXScaled + " centerYScaled : " + centerYScaled - + " getTranslationX : " + mThumbNailView.getTranslationX() + + " getTranslationX : " + mThumbnailView.getTranslationX() + " ratio : " + ratio ); } - mThumbNailView.setTranslationX(centerXScaled); - mThumbNailView.setTranslationY(centerYScaled); + mThumbnailView.setTranslationX(centerXScaled); + mThumbnailView.setTranslationY(centerYScaled); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java new file mode 100644 index 000000000000..519f31b86f78 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java @@ -0,0 +1,46 @@ +/* + * 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.accessibility.magnification; + +import android.provider.DeviceConfig; + +/** + * Encapsulates the feature flags for magnification thumbnail. {@see DeviceConfig} + * + * @hide + */ +public class MagnificationThumbnailFeatureFlag extends MagnificationFeatureFlagBase { + + private static final String NAMESPACE = DeviceConfig.NAMESPACE_ACCESSIBILITY; + private static final String FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL = + "enable_magnifier_thumbnail"; + + @Override + String getNamespace() { + return NAMESPACE; + } + + @Override + String getFeatureName() { + return FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL; + } + + @Override + boolean getDefaultValue() { + return false; + } +} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 3bd4547dd3e6..b2e8ffcd8fca 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -4277,7 +4277,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState getUiForShowing().showFillUi(filledId, response, filterText, mService.getServicePackageName(), mComponentName, - targetLabel, targetIcon, this, id, mCompatMode); + targetLabel, targetIcon, this, userId, id, mCompatMode); synchronized (mLock) { mPresentationStatsEventLogger.maybeSetCountShown( diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index 829161037832..a6318186f2d5 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -23,6 +23,7 @@ import static com.android.server.autofill.Helper.sVerbose; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -196,17 +197,19 @@ public final class AutoFillUI { * @param serviceLabel label of autofill service * @param serviceIcon icon of autofill service * @param callback identifier for the caller + * @param userId the user associated wit the session * @param sessionId id of the autofill session * @param compatMode whether the app is being autofilled in compatibility mode. */ public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response, @Nullable String filterText, @Nullable String servicePackageName, @NonNull ComponentName componentName, @NonNull CharSequence serviceLabel, - @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback, int sessionId, - boolean compatMode) { + @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback, + @UserIdInt int userId, int sessionId, boolean compatMode) { if (sDebug) { final int size = filterText == null ? 0 : filterText.length(); - Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars"); + Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars, userId=" + + userId); } final LogMaker log = Helper .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, componentName, servicePackageName, @@ -221,7 +224,7 @@ public final class AutoFillUI { return; } hideAllUiThread(callback); - mFillUi = new FillUi(mContext, response, focusedId, + mFillUi = new FillUi(mContext, userId, response, focusedId, filterText, mOverlayControl, serviceLabel, serviceIcon, mUiModeMgr.isNightMode(), new FillUi.Callback() { diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 76f4505bbb4a..30d2fe40158b 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -22,12 +22,15 @@ import static com.android.server.autofill.Helper.sVerbose; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.content.Context; import android.content.IntentSender; import android.content.pm.PackageManager; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.hardware.display.DisplayManager; +import android.os.UserManager; import android.service.autofill.Dataset; import android.service.autofill.Dataset.DatasetFieldFilter; import android.service.autofill.FillResponse; @@ -36,6 +39,7 @@ import android.util.PluralsMessageFormatter; import android.util.Slog; import android.util.TypedValue; import android.view.ContextThemeWrapper; +import android.view.Display; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -57,9 +61,12 @@ import android.widget.RemoteViews; import android.widget.TextView; import com.android.internal.R; +import com.android.server.LocalServices; import com.android.server.UiThread; import com.android.server.autofill.AutofillManagerService; import com.android.server.autofill.Helper; +import com.android.server.pm.UserManagerInternal; +import com.android.server.utils.Slogf; import java.io.PrintWriter; import java.util.ArrayList; @@ -133,13 +140,29 @@ final class FillUi { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); } - FillUi(@NonNull Context context, @NonNull FillResponse response, + FillUi(@NonNull Context context, @UserIdInt int userId, @NonNull FillResponse response, @NonNull AutofillId focusedViewId, @Nullable String filterText, @NonNull OverlayControl overlayControl, @NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon, boolean nightMode, @NonNull Callback callback) { if (sVerbose) Slog.v(TAG, "nightMode: " + nightMode); mThemeId = nightMode ? THEME_ID_DARK : THEME_ID_LIGHT; mCallback = callback; + + if (UserManager.isVisibleBackgroundUsersEnabled()) { + UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); + int displayId = umi.getMainDisplayAssignedToUser(userId); + if (sDebug) { + Slogf.d(TAG, "Creating context for display %d for user %d", displayId, userId); + } + Display display = context.getSystemService(DisplayManager.class).getDisplay(displayId); + if (display != null) { + context = context.createDisplayContext(display); + } else { + Slogf.d(TAG, "Could not get display with id %d (which is associated with user %d; " + + "FillUi operations will probably fail", displayId, userId); + } + } + mFullScreen = isFullScreen(context); mContext = new ContextThemeWrapper(context, mThemeId); @@ -774,6 +797,7 @@ final class FillUi { pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth); pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight); pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed); + pw.print(prefix); pw.print("mContext: "); pw.println(mContext); pw.print(prefix); pw.print("theme id: "); pw.print(mThemeId); switch (mThemeId) { case THEME_ID_DARK: diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index efff11226683..d8fbd08a0e70 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -604,25 +604,21 @@ public class CompanionDeviceManagerService extends SystemService { } @Override - @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports") public void addOnTransportsChangedListener(IOnTransportsChangedListener listener) { mTransportManager.addListener(listener); } @Override - @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports") public void removeOnTransportsChangedListener(IOnTransportsChangedListener listener) { mTransportManager.removeListener(listener); } @Override - @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports") public void sendMessage(int messageType, byte[] data, int[] associationIds) { mTransportManager.sendMessage(messageType, data, associationIds); } @Override - @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports") public void addOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener) { mTransportManager.addListener(messageType, listener); diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java new file mode 100644 index 000000000000..1e4bb9a504ba --- /dev/null +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java @@ -0,0 +1,188 @@ +/* + * 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.companion.datatransfer.contextsync; + +import android.annotation.NonNull; +import android.companion.ContextSyncMessage; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** A read-only snapshot of an {@link ContextSyncMessage}. */ +class CallMetadataSyncData { + + final Map<Long, CallMetadataSyncData.Call> mCalls = new HashMap<>(); + final List<CallMetadataSyncData.Call> mRequests = new ArrayList<>(); + + public void addCall(CallMetadataSyncData.Call call) { + mCalls.put(call.getId(), call); + } + + public boolean hasCall(long id) { + return mCalls.containsKey(id); + } + + public Collection<CallMetadataSyncData.Call> getCalls() { + return mCalls.values(); + } + + public void addRequest(CallMetadataSyncData.Call call) { + mRequests.add(call); + } + + public List<CallMetadataSyncData.Call> getRequests() { + return mRequests; + } + + public static class Call implements Parcelable { + private long mId; + private String mCallerId; + private byte[] mAppIcon; + private String mAppName; + private String mAppIdentifier; + private int mStatus; + private final Set<Integer> mControls = new HashSet<>(); + + public static Call fromParcel(Parcel parcel) { + final Call call = new Call(); + call.setId(parcel.readLong()); + call.setCallerId(parcel.readString()); + call.setAppIcon(parcel.readBlob()); + call.setAppName(parcel.readString()); + call.setAppIdentifier(parcel.readString()); + call.setStatus(parcel.readInt()); + final int numberOfControls = parcel.readInt(); + for (int i = 0; i < numberOfControls; i++) { + call.addControl(parcel.readInt()); + } + return call; + } + + @Override + public void writeToParcel(Parcel parcel, int parcelableFlags) { + parcel.writeLong(mId); + parcel.writeString(mCallerId); + parcel.writeBlob(mAppIcon); + parcel.writeString(mAppName); + parcel.writeString(mAppIdentifier); + parcel.writeInt(mStatus); + parcel.writeInt(mControls.size()); + for (int control : mControls) { + parcel.writeInt(control); + } + } + + void setId(long id) { + mId = id; + } + + void setCallerId(String callerId) { + mCallerId = callerId; + } + + void setAppIcon(byte[] appIcon) { + mAppIcon = appIcon; + } + + void setAppName(String appName) { + mAppName = appName; + } + + void setAppIdentifier(String appIdentifier) { + mAppIdentifier = appIdentifier; + } + + void setStatus(int status) { + mStatus = status; + } + + void addControl(int control) { + mControls.add(control); + } + + long getId() { + return mId; + } + + String getCallerId() { + return mCallerId; + } + + byte[] getAppIcon() { + return mAppIcon; + } + + String getAppName() { + return mAppName; + } + + String getAppIdentifier() { + return mAppIdentifier; + } + + int getStatus() { + return mStatus; + } + + Set<Integer> getControls() { + return mControls; + } + + boolean hasControl(int control) { + return mControls.contains(control); + } + + @Override + public boolean equals(Object other) { + if (other instanceof CallMetadataSyncData.Call) { + return ((Call) other).getId() == getId(); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(mId); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull public static final Parcelable.Creator<Call> CREATOR = new Parcelable.Creator<>() { + + @Override + public Call createFromParcel(Parcel source) { + return Call.fromParcel(source); + } + + @Override + public Call[] newArray(int size) { + return new Call[size]; + } + }; + } +} diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java index 077fd2a1157d..dd0bbf2790ee 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java @@ -39,6 +39,8 @@ public class CrossDeviceCall { private static final String TAG = "CrossDeviceCall"; + public static final String EXTRA_CALL_ID = + "com.android.companion.datatransfer.contextsync.extra.CALL_ID"; private static final int APP_ICON_BITMAP_DIMENSION = 256; private static final AtomicLong sNextId = new AtomicLong(1); @@ -47,6 +49,7 @@ public class CrossDeviceCall { private final Call mCall; @VisibleForTesting boolean mIsEnterprise; @VisibleForTesting boolean mIsOtt; + private final String mCallingAppPackageName; private String mCallingAppName; private byte[] mCallingAppIcon; private String mCallerDisplayName; @@ -59,7 +62,7 @@ public class CrossDeviceCall { CallAudioState callAudioState) { mId = sNextId.getAndIncrement(); mCall = call; - final String callingAppPackageName = call != null + mCallingAppPackageName = call != null ? call.getDetails().getAccountHandle().getComponentName().getPackageName() : null; mIsOtt = call != null && (call.getDetails().getCallCapabilities() & Call.Details.PROPERTY_SELF_MANAGED) @@ -69,13 +72,13 @@ public class CrossDeviceCall { == Call.Details.PROPERTY_ENTERPRISE_CALL; try { final ApplicationInfo applicationInfo = packageManager - .getApplicationInfo(callingAppPackageName, + .getApplicationInfo(mCallingAppPackageName, PackageManager.ApplicationInfoFlags.of(0)); mCallingAppName = packageManager.getApplicationLabel(applicationInfo).toString(); mCallingAppIcon = renderDrawableToByteArray( packageManager.getApplicationIcon(applicationInfo)); } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG, "Could not get application info for package " + callingAppPackageName, e); + Slog.e(TAG, "Could not get application info for package " + mCallingAppPackageName, e); } mIsMuted = callAudioState != null && callAudioState.isMuted(); if (call != null) { @@ -170,7 +173,8 @@ public class CrossDeviceCall { } } - private int convertStateToStatus(int callState) { + /** Converts a Telecom call state to a Context Sync status. */ + public static int convertStateToStatus(int callState) { switch (callState) { case Call.STATE_HOLDING: return android.companion.Telecom.Call.ON_HOLD; @@ -178,20 +182,30 @@ public class CrossDeviceCall { return android.companion.Telecom.Call.ONGOING; case Call.STATE_RINGING: return android.companion.Telecom.Call.RINGING; - case Call.STATE_NEW: - case Call.STATE_DIALING: - case Call.STATE_DISCONNECTED: - case Call.STATE_SELECT_PHONE_ACCOUNT: - case Call.STATE_CONNECTING: - case Call.STATE_DISCONNECTING: - case Call.STATE_PULLING_CALL: - case Call.STATE_AUDIO_PROCESSING: - case Call.STATE_SIMULATED_RINGING: default: return android.companion.Telecom.Call.UNKNOWN_STATUS; } } + /** + * Converts a Context Sync status to a Telecom call state. Note that this is lossy for + * and RINGING_SILENCED, as Telecom does not distinguish between RINGING and RINGING_SILENCED. + */ + public static int convertStatusToState(int status) { + switch (status) { + case android.companion.Telecom.Call.ON_HOLD: + return Call.STATE_HOLDING; + case android.companion.Telecom.Call.ONGOING: + return Call.STATE_ACTIVE; + case android.companion.Telecom.Call.RINGING: + case android.companion.Telecom.Call.RINGING_SILENCED: + return Call.STATE_RINGING; + case android.companion.Telecom.Call.UNKNOWN_STATUS: + default: + return Call.STATE_NEW; + } + } + public long getId() { return mId; } @@ -208,6 +222,10 @@ public class CrossDeviceCall { return mCallingAppIcon; } + public String getCallingAppPackageName() { + return mCallingAppPackageName; + } + /** * Get a human-readable "caller id" to display as the origin of the call. * diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java index 9677b70f4916..a3e095e4475b 100644 --- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -96,27 +96,29 @@ public class CompanionTransportManager { /** * Add a listener to receive callbacks when a message is received for the message type */ - @GuardedBy("mTransports") public void addListener(int message, @NonNull IOnMessageReceivedListener listener) { mMessageListeners.put(message, listener); - for (int i = 0; i < mTransports.size(); i++) { - mTransports.valueAt(i).addListener(message, listener); + synchronized (mTransports) { + for (int i = 0; i < mTransports.size(); i++) { + mTransports.valueAt(i).addListener(message, listener); + } } } /** * Add a listener to receive callbacks when any of the transports is changed */ - @GuardedBy("mTransports") public void addListener(IOnTransportsChangedListener listener) { Slog.i(TAG, "Registering OnTransportsChangedListener"); mTransportsListeners.register(listener); List<AssociationInfo> associations = new ArrayList<>(); - for (int i = 0; i < mTransports.size(); i++) { - AssociationInfo association = mAssociationStore.getAssociationById( - mTransports.keyAt(i)); - if (association != null) { - associations.add(association); + synchronized (mTransports) { + for (int i = 0; i < mTransports.size(); i++) { + AssociationInfo association = mAssociationStore.getAssociationById( + mTransports.keyAt(i)); + if (association != null) { + associations.add(association); + } } } mTransportsListeners.broadcast(listener1 -> { @@ -148,18 +150,19 @@ public class CompanionTransportManager { /** * Send a message to remote devices through the transports */ - @GuardedBy("mTransports") public void sendMessage(int message, byte[] data, int[] associationIds) { Slog.i(TAG, "Sending message 0x" + Integer.toHexString(message) + " data length " + data.length); - for (int i = 0; i < associationIds.length; i++) { - if (mTransports.contains(associationIds[i])) { - try { - mTransports.get(associationIds[i]).sendMessage(message, data); - } catch (IOException e) { - Slog.e(TAG, "Failed to send message 0x" + Integer.toHexString(message) - + " data length " + data.length + " to association " - + associationIds[i]); + synchronized (mTransports) { + for (int i = 0; i < associationIds.length; i++) { + if (mTransports.contains(associationIds[i])) { + try { + mTransports.get(associationIds[i]).sendMessage(message, data); + } catch (IOException e) { + Slog.e(TAG, "Failed to send message 0x" + Integer.toHexString(message) + + " data length " + data.length + " to association " + + associationIds[i]); + } } } } @@ -215,14 +218,15 @@ public class CompanionTransportManager { } } - @GuardedBy("mTransports") private void notifyOnTransportsChanged() { List<AssociationInfo> associations = new ArrayList<>(); - for (int i = 0; i < mTransports.size(); i++) { - AssociationInfo association = mAssociationStore.getAssociationById( - mTransports.keyAt(i)); - if (association != null) { - associations.add(association); + synchronized (mTransports) { + for (int i = 0; i < mTransports.size(); i++) { + AssociationInfo association = mAssociationStore.getAssociationById( + mTransports.keyAt(i)); + if (association != null) { + associations.add(association); + } } } mTransportsListeners.broadcast(listener -> { @@ -233,14 +237,15 @@ public class CompanionTransportManager { }); } - @GuardedBy("mTransports") private void initializeTransport(int associationId, ParcelFileDescriptor fd) { Slog.i(TAG, "Initializing transport"); if (!isSecureTransportEnabled()) { Transport transport = new RawTransport(associationId, fd, mContext); addMessageListenersToTransport(transport); transport.start(); - mTransports.put(associationId, transport); + synchronized (mTransports) { + mTransports.put(associationId, transport); + } Slog.i(TAG, "RawTransport is created"); return; } @@ -283,7 +288,6 @@ public class CompanionTransportManager { /** * Depending on the remote platform info to decide which transport should be created */ - @GuardedBy("CompanionTransportManager.this.mTransports") private void onPlatformInfoReceived(int associationId, byte[] data) { if (mTempTransport.getAssociationId() != associationId) { return; @@ -330,7 +334,9 @@ public class CompanionTransportManager { } addMessageListenersToTransport(transport); transport.start(); - mTransports.put(transport.getAssociationId(), transport); + synchronized (mTransports) { + mTransports.put(transport.getAssociationId(), transport); + } // Doesn't need to notifyTransportsChanged here, it'll be done in attachSystemDataTransport } diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index 1a0588e999e2..307f7bfc1fd5 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -367,7 +367,7 @@ class InputController { "Could not send key event to input device for given token"); } return mNativeWrapper.writeDpadKeyEvent(inputDeviceDescriptor.getNativePointer(), - event.getKeyCode(), event.getAction()); + event.getKeyCode(), event.getAction(), event.getEventTimeNanos()); } } @@ -380,7 +380,7 @@ class InputController { "Could not send key event to input device for given token"); } return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getNativePointer(), - event.getKeyCode(), event.getAction()); + event.getKeyCode(), event.getAction(), event.getEventTimeNanos()); } } @@ -398,7 +398,7 @@ class InputController { "Display id associated with this mouse is not currently targetable"); } return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(), - event.getButtonCode(), event.getAction()); + event.getButtonCode(), event.getAction(), event.getEventTimeNanos()); } } @@ -412,7 +412,8 @@ class InputController { } return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getNativePointer(), event.getPointerId(), event.getToolType(), event.getAction(), event.getX(), - event.getY(), event.getPressure(), event.getMajorAxisSize()); + event.getY(), event.getPressure(), event.getMajorAxisSize(), + event.getEventTimeNanos()); } } @@ -430,7 +431,7 @@ class InputController { "Display id associated with this mouse is not currently targetable"); } return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(), - event.getRelativeX(), event.getRelativeY()); + event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos()); } } @@ -448,7 +449,7 @@ class InputController { "Display id associated with this mouse is not currently targetable"); } return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(), - event.getXAxisMovement(), event.getYAxisMovement()); + event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos()); } } @@ -514,15 +515,19 @@ class InputController { private static native long nativeOpenUinputTouchscreen(String deviceName, int vendorId, int productId, String phys, int height, int width); private static native void nativeCloseUinput(long ptr); - private static native boolean nativeWriteDpadKeyEvent(long ptr, int androidKeyCode, int action); - private static native boolean nativeWriteKeyEvent(long ptr, int androidKeyCode, int action); - private static native boolean nativeWriteButtonEvent(long ptr, int buttonCode, int action); + private static native boolean nativeWriteDpadKeyEvent(long ptr, int androidKeyCode, int action, + long eventTimeNanos); + private static native boolean nativeWriteKeyEvent(long ptr, int androidKeyCode, int action, + long eventTimeNanos); + private static native boolean nativeWriteButtonEvent(long ptr, int buttonCode, int action, + long eventTimeNanos); private static native boolean nativeWriteTouchEvent(long ptr, int pointerId, int toolType, - int action, float locationX, float locationY, float pressure, float majorAxisSize); + int action, float locationX, float locationY, float pressure, float majorAxisSize, + long eventTimeNanos); private static native boolean nativeWriteRelativeEvent(long ptr, float relativeX, - float relativeY); + float relativeY, long eventTimeNanos); private static native boolean nativeWriteScrollEvent(long ptr, float xAxisMovement, - float yAxisMovement); + float yAxisMovement, long eventTimeNanos); /** Wrapper around the static native methods for tests. */ @VisibleForTesting @@ -550,32 +555,37 @@ class InputController { nativeCloseUinput(ptr); } - public boolean writeDpadKeyEvent(long ptr, int androidKeyCode, int action) { - return nativeWriteDpadKeyEvent(ptr, androidKeyCode, action); + public boolean writeDpadKeyEvent(long ptr, int androidKeyCode, int action, + long eventTimeNanos) { + return nativeWriteDpadKeyEvent(ptr, androidKeyCode, action, eventTimeNanos); } - public boolean writeKeyEvent(long ptr, int androidKeyCode, int action) { - return nativeWriteKeyEvent(ptr, androidKeyCode, action); + public boolean writeKeyEvent(long ptr, int androidKeyCode, int action, + long eventTimeNanos) { + return nativeWriteKeyEvent(ptr, androidKeyCode, action, eventTimeNanos); } - public boolean writeButtonEvent(long ptr, int buttonCode, int action) { - return nativeWriteButtonEvent(ptr, buttonCode, action); + public boolean writeButtonEvent(long ptr, int buttonCode, int action, + long eventTimeNanos) { + return nativeWriteButtonEvent(ptr, buttonCode, action, eventTimeNanos); } public boolean writeTouchEvent(long ptr, int pointerId, int toolType, int action, - float locationX, float locationY, float pressure, float majorAxisSize) { + float locationX, float locationY, float pressure, float majorAxisSize, + long eventTimeNanos) { return nativeWriteTouchEvent(ptr, pointerId, toolType, action, locationX, locationY, - pressure, majorAxisSize); + pressure, majorAxisSize, eventTimeNanos); } - public boolean writeRelativeEvent(long ptr, float relativeX, float relativeY) { - return nativeWriteRelativeEvent(ptr, relativeX, relativeY); + public boolean writeRelativeEvent(long ptr, float relativeX, float relativeY, + long eventTimeNanos) { + return nativeWriteRelativeEvent(ptr, relativeX, relativeY, eventTimeNanos); } - public boolean writeScrollEvent(long ptr, float xAxisMovement, float yAxisMovement) { - return nativeWriteScrollEvent(ptr, xAxisMovement, - yAxisMovement); + public boolean writeScrollEvent(long ptr, float xAxisMovement, float yAxisMovement, + long eventTimeNanos) { + return nativeWriteScrollEvent(ptr, xAxisMovement, yAxisMovement, eventTimeNanos); } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index ae88f24ab409..de0f68ccd665 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -404,39 +404,44 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub public void close() { super.close_enforcePermission(); // Remove about-to-be-closed virtual device from the service before butchering it. - mService.removeVirtualDevice(mDeviceId); + boolean removed = mService.removeVirtualDevice(mDeviceId); mDeviceId = Context.DEVICE_ID_INVALID; - VirtualDisplayWrapper[] virtualDisplaysToBeReleased; - synchronized (mVirtualDeviceLock) { - if (mVirtualAudioController != null) { - mVirtualAudioController.stopListening(); - mVirtualAudioController = null; - } - mLocaleList = null; - virtualDisplaysToBeReleased = new VirtualDisplayWrapper[mVirtualDisplays.size()]; - for (int i = 0; i < mVirtualDisplays.size(); i++) { - virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i); - } - mVirtualDisplays.clear(); - mVirtualSensorList = null; - mVirtualSensors.clear(); - } - // Destroy the display outside locked section. - for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) { - mDisplayManager.releaseVirtualDisplay(virtualDisplayWrapper.getToken()); - // The releaseVirtualDisplay call above won't trigger - // VirtualDeviceImpl.onVirtualDisplayRemoved callback because we already removed the - // virtual device from the service - we release the other display-tied resources here - // with the guarantee it will be done exactly once. - releaseOwnedVirtualDisplayResources(virtualDisplayWrapper); + // Device is already closed. + if (!removed) { + return; } - mAppToken.unlinkToDeath(this, 0); - mCameraAccessController.stopObservingIfNeeded(); - final long ident = Binder.clearCallingIdentity(); try { + VirtualDisplayWrapper[] virtualDisplaysToBeReleased; + synchronized (mVirtualDeviceLock) { + if (mVirtualAudioController != null) { + mVirtualAudioController.stopListening(); + mVirtualAudioController = null; + } + mLocaleList = null; + virtualDisplaysToBeReleased = new VirtualDisplayWrapper[mVirtualDisplays.size()]; + for (int i = 0; i < mVirtualDisplays.size(); i++) { + virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i); + } + mVirtualDisplays.clear(); + mVirtualSensorList = null; + mVirtualSensors.clear(); + } + // Destroy the display outside locked section. + for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) { + mDisplayManager.releaseVirtualDisplay(virtualDisplayWrapper.getToken()); + // The releaseVirtualDisplay call above won't trigger + // VirtualDeviceImpl.onVirtualDisplayRemoved callback because we already removed the + // virtual device from the service - we release the other display-tied resources + // here with the guarantee it will be done exactly once. + releaseOwnedVirtualDisplayResources(virtualDisplayWrapper); + } + + mAppToken.unlinkToDeath(this, 0); + mCameraAccessController.stopObservingIfNeeded(); + mInputController.close(); mSensorController.close(); } finally { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 96446422dd85..ad4c0bf26d62 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -202,8 +202,19 @@ public class VirtualDeviceManagerService extends SystemService { } } - void removeVirtualDevice(int deviceId) { + /** + * Remove the virtual device. Sends the + * {@link VirtualDeviceManager#ACTION_VIRTUAL_DEVICE_REMOVED} broadcast as a result. + * + * @param deviceId deviceId to be removed + * @return {@code true} if the device was removed, {@code false} if the operation was a no-op + */ + boolean removeVirtualDevice(int deviceId) { synchronized (mVirtualDeviceManagerLock) { + if (!mVirtualDevices.contains(deviceId)) { + return false; + } + mAppsOnVirtualDevices.remove(deviceId); mVirtualDevices.remove(deviceId); } @@ -223,6 +234,7 @@ public class VirtualDeviceManagerService extends SystemService { } finally { Binder.restoreCallingIdentity(identity); } + return true; } private void syncVirtualDevicesToCdmAssociations(List<AssociationInfo> associations) { @@ -248,7 +260,6 @@ public class VirtualDeviceManagerService extends SystemService { for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) { virtualDevice.close(); } - } private void registerCdmAssociationListener() { diff --git a/services/core/Android.bp b/services/core/Android.bp index cfdf3ac5915b..f8d19ec9903d 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -161,6 +161,7 @@ java_library_static { "android.hardware.health-V2-java", // AIDL "android.hardware.health-translate-java", "android.hardware.light-V1-java", + "android.hardware.security.rkp-V3-java", "android.hardware.tv.cec-V1.1-java", "android.hardware.tv.hdmi.cec-V1-java", "android.hardware.tv.hdmi.connection-V1-java", @@ -177,6 +178,7 @@ java_library_static { "android.hardware.power.stats-V2-java", "android.hardware.power-V4-java", "android.hidl.manager-V1.2-java", + "cbor-java", "icu4j_calendar_astronomer", "netd-client", "overlayable_policy_aidl-java", diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java index 12ee13183221..0713999d4354 100644 --- a/services/core/java/android/os/BatteryStatsInternal.java +++ b/services/core/java/android/os/BatteryStatsInternal.java @@ -17,7 +17,6 @@ package android.os; import android.annotation.IntDef; -import android.annotation.NonNull; import android.net.Network; import com.android.internal.os.BinderCallsStats; @@ -40,6 +39,8 @@ public abstract class BatteryStatsInternal { public static final int CPU_WAKEUP_SUBSYSTEM_ALARM = 1; public static final int CPU_WAKEUP_SUBSYSTEM_WIFI = 2; public static final int CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER = 3; + public static final int CPU_WAKEUP_SUBSYSTEM_SENSOR = 4; + public static final int CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA = 5; /** @hide */ @IntDef(prefix = {"CPU_WAKEUP_SUBSYSTEM_"}, value = { @@ -47,9 +48,11 @@ public abstract class BatteryStatsInternal { CPU_WAKEUP_SUBSYSTEM_ALARM, CPU_WAKEUP_SUBSYSTEM_WIFI, CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, + CPU_WAKEUP_SUBSYSTEM_SENSOR, + CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA, }) @Retention(RetentionPolicy.SOURCE) - @interface CpuWakeupSubsystem { + public @interface CpuWakeupSubsystem { } /** @@ -107,19 +110,16 @@ public abstract class BatteryStatsInternal { public abstract void noteBinderThreadNativeIds(int[] binderThreadNativeTids); /** - * Reports any activity that could potentially have caused the CPU to wake up. - * Accepts a timestamp to allow free ordering between the event and its reporting. - * @param subsystem The subsystem this activity should be attributed to. - * @param elapsedMillis The time when this activity happened in the elapsed timebase. - * @param uids The uid (or uids) that should be blamed for this activity. - */ - public abstract void noteCpuWakingActivity(@CpuWakeupSubsystem int subsystem, - long elapsedMillis, @NonNull int... uids); - - /** * Reports a sound trigger recognition event that may have woken up the CPU. * @param elapsedMillis The time when the event happened in the elapsed timebase. * @param uid The uid that requested this trigger. */ public abstract void noteWakingSoundTrigger(long elapsedMillis, int uid); + + /** + * Reports an alarm batch that would have woken up the CPU. + * @param elapsedMillis The time at which this alarm batch was scheduled to go off. + * @param uids the uids of all apps that have any alarm in this batch. + */ + public abstract void noteWakingAlarmBatch(long elapsedMillis, int... uids); } diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index d256aead97e8..f4f5c951faaa 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -580,6 +580,7 @@ public class PackageWatchdog { PackageHealthObserverImpact.USER_IMPACT_LEVEL_10, PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, PackageHealthObserverImpact.USER_IMPACT_LEVEL_50, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_60, PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, PackageHealthObserverImpact.USER_IMPACT_LEVEL_100}) public @interface PackageHealthObserverImpact { @@ -590,6 +591,7 @@ public class PackageWatchdog { /* Actions having medium user impact, user of a device will likely notice. */ int USER_IMPACT_LEVEL_30 = 30; int USER_IMPACT_LEVEL_50 = 50; + int USER_IMPACT_LEVEL_60 = 60; int USER_IMPACT_LEVEL_70 = 70; /* Action has high user impact, a last resort, user of a device will be very frustrated. */ int USER_IMPACT_LEVEL_100 = 100; diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index c0b3a90d923b..d140403f77c3 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -23,6 +23,7 @@ import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.POWER_SAVER; import static android.Manifest.permission.UPDATE_DEVICE_STATS; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE; @@ -51,6 +52,7 @@ import android.os.BatteryConsumer; import android.os.BatteryManagerInternal; import android.os.BatteryStats; import android.os.BatteryStatsInternal; +import android.os.BatteryStatsInternal.CpuWakeupSubsystem; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Binder; @@ -474,6 +476,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub private int transportToSubsystem(NetworkCapabilities nc) { if (nc.hasTransport(TRANSPORT_WIFI)) { return CPU_WAKEUP_SUBSYSTEM_WIFI; + } else if (nc.hasTransport(TRANSPORT_CELLULAR)) { + return CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA; } return CPU_WAKEUP_SUBSYSTEM_UNKNOWN; } @@ -514,14 +518,32 @@ public final class BatteryStatsService extends IBatteryStats.Stub } @Override - public void noteCpuWakingActivity(int subsystem, long elapsedMillis, int... uids) { - Objects.requireNonNull(uids); - mHandler.post(() -> mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids)); - } - @Override public void noteWakingSoundTrigger(long elapsedMillis, int uid) { noteCpuWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, elapsedMillis, uid); } + + @Override + public void noteWakingAlarmBatch(long elapsedMillis, int... uids) { + noteCpuWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, elapsedMillis, uids); + } + } + + /** + * Reports any activity that could potentially have caused the CPU to wake up. + * Accepts a timestamp to allow free ordering between the event and its reporting. + * + * <p> + * This method can be called multiple times for the same wakeup and then all attribution + * reported will be unioned as long as all reports are made within a small amount of cpu uptime + * after the wakeup is reported to batterystats. + * + * @param subsystem The subsystem this activity should be attributed to. + * @param elapsedMillis The time when this activity happened in the elapsed timebase. + * @param uids The uid (or uids) that should be blamed for this activity. + */ + void noteCpuWakingActivity(@CpuWakeupSubsystem int subsystem, long elapsedMillis, int... uids) { + Objects.requireNonNull(uids); + mHandler.post(() -> mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids)); } @Override @@ -1267,6 +1289,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub if (callingUid != Process.SYSTEM_UID) { throw new SecurityException("Calling uid " + callingUid + " is not system uid"); } + final long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(elapsedNanos); final SensorManager sm = mContext.getSystemService(SensorManager.class); final Sensor sensor = sm.getSensorByHandle(sensorHandle); @@ -1275,10 +1298,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub + " received in noteWakeupSensorEvent"); return; } - Slog.i(TAG, "Sensor " + sensor + " wakeup event at " + elapsedNanos + " sent to uid " - + uid); - // TODO (b/275436924): Remove log and pipe to CpuWakeupStats for wakeup attribution - // This method should return as quickly as possible. Use mHandler#post to do longer work. + if (uid < 0) { + Slog.wtf(TAG, "Invalid uid " + uid + " for sensor event with sensor: " + sensor); + return; + } + // TODO (b/278319756): Also pipe in Sensor type for more usefulness. + noteCpuWakingActivity(BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR, elapsedMillis, uid); } @Override diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index d926c2c7c7a8..a181402e2d9f 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -141,6 +141,7 @@ import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; /** * Helper class for {@link ActivityManagerService} responsible for multi-user functionality. @@ -157,7 +158,7 @@ class UserController implements Handler.Callback { private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM; // Amount of time we wait for observers to handle a user switch before - // giving up on them and unfreezing the screen. + // giving up on them and dismissing the user switching dialog. static final int DEFAULT_USER_SWITCH_TIMEOUT_MS = 3 * 1000; /** @@ -207,7 +208,7 @@ class UserController implements Handler.Callback { /** * Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be * called after dismissing the keyguard. - * Otherwise, we should move on to unfreeze the screen {@link #unfreezeScreen} + * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog()} * and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}. */ private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000; @@ -1695,14 +1696,6 @@ class UserController implements Handler.Callback { return false; } - if (foreground && isUserSwitchUiEnabled()) { - t.traceBegin("startFreezingScreen"); - mInjector.getWindowManager().startFreezingScreen( - R.anim.screen_user_exit, R.anim.screen_user_enter); - t.traceEnd(); - } - dismissUserSwitchDialog(); // so that we don't hold a reference to mUserSwitchingDialog - boolean needStart = false; boolean updateUmState = false; UserState uss; @@ -1877,7 +1870,7 @@ class UserController implements Handler.Callback { if (!success) { mInjector.getWindowManager().setSwitchingUser(false); mTargetUserId = UserHandle.USER_NULL; - dismissUserSwitchDialog(); + dismissUserSwitchDialog(null); } } @@ -2015,22 +2008,26 @@ class UserController implements Handler.Callback { mUiHandler.sendMessage(mUiHandler.obtainMessage( START_USER_SWITCH_UI_MSG, userNames)); } else { - mHandler.removeMessages(START_USER_SWITCH_FG_MSG); - mHandler.sendMessage(mHandler.obtainMessage( - START_USER_SWITCH_FG_MSG, targetUserId, 0)); + sendStartUserSwitchFgMessage(targetUserId); } return true; } - private void dismissUserSwitchDialog() { - mInjector.dismissUserSwitchingDialog(); + private void sendStartUserSwitchFgMessage(int targetUserId) { + mHandler.removeMessages(START_USER_SWITCH_FG_MSG); + mHandler.sendMessage(mHandler.obtainMessage(START_USER_SWITCH_FG_MSG, targetUserId, 0)); + } + + private void dismissUserSwitchDialog(Runnable onDismissed) { + mInjector.dismissUserSwitchingDialog(onDismissed); } private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) { // The dialog will show and then initiate the user switch by calling startUserInForeground mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second, getSwitchingFromSystemUserMessageUnchecked(), - getSwitchingToSystemUserMessageUnchecked()); + getSwitchingToSystemUserMessageUnchecked(), + /* onShown= */ () -> sendStartUserSwitchFgMessage(fromToUserPair.second.id)); } private void dispatchForegroundProfileChanged(@UserIdInt int userId) { @@ -2236,7 +2233,7 @@ class UserController implements Handler.Callback { EventLog.writeEvent(EventLogTags.UC_CONTINUE_USER_SWITCH, oldUserId, newUserId); - // Do the keyguard dismiss and unfreeze later + // Do the keyguard dismiss and dismiss the user switching dialog later mHandler.removeMessages(COMPLETE_USER_SWITCH_MSG); mHandler.sendMessage(mHandler.obtainMessage( COMPLETE_USER_SWITCH_MSG, oldUserId, newUserId)); @@ -2251,37 +2248,33 @@ class UserController implements Handler.Callback { @VisibleForTesting void completeUserSwitch(int oldUserId, int newUserId) { final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled(); - final Runnable runnable = () -> { - if (isUserSwitchUiEnabled) { - unfreezeScreen(); - } - mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); - mHandler.sendMessage(mHandler.obtainMessage( - REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId)); - }; + // serialize each conditional step + await( + // STEP 1 - If there is no challenge set, dismiss the keyguard right away + isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId), + mInjector::dismissKeyguard, + () -> await( + // STEP 2 - If user switch ui was enabled, dismiss user switch dialog + isUserSwitchUiEnabled, + this::dismissUserSwitchDialog, + () -> { + // STEP 3 - Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast + // ACTION_USER_SWITCHED & call UserSwitchObservers.onUserSwitchComplete + mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); + mHandler.sendMessage(mHandler.obtainMessage( + REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId)); + } + )); + } - // If there is no challenge set, dismiss the keyguard right away - if (isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId)) { - // Wait until the keyguard is dismissed to unfreeze - mInjector.dismissKeyguard(runnable); + private void await(boolean condition, Consumer<Runnable> conditionalStep, Runnable nextStep) { + if (condition) { + conditionalStep.accept(nextStep); } else { - runnable.run(); + nextStep.run(); } } - /** - * Tell WindowManager we're ready to unfreeze the screen, at its leisure. Note that there is - * likely a lot going on, and WM won't unfreeze until the drawing is all done, so - * the actual unfreeze may still not happen for a long time; this is expected. - */ - @VisibleForTesting - void unfreezeScreen() { - TimingsTraceAndSlog t = new TimingsTraceAndSlog(); - t.traceBegin("stopFreezingScreen"); - mInjector.getWindowManager().stopFreezingScreen(); - t.traceEnd(); - } - private void moveUserToForeground(UserState uss, int newUserId) { boolean homeInFront = mInjector.taskSupervisorSwitchUser(newUserId, uss); if (homeInFront) { @@ -3731,17 +3724,18 @@ class UserController implements Handler.Callback { mService.mCpHelper.installEncryptionUnawareProviders(userId); } - void dismissUserSwitchingDialog() { + void dismissUserSwitchingDialog(@Nullable Runnable onDismissed) { synchronized (mUserSwitchingDialogLock) { if (mUserSwitchingDialog != null) { - mUserSwitchingDialog.dismiss(); + mUserSwitchingDialog.dismiss(onDismissed); mUserSwitchingDialog = null; } } } void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser, - String switchingFromSystemUserMessage, String switchingToSystemUserMessage) { + String switchingFromSystemUserMessage, String switchingToSystemUserMessage, + @NonNull Runnable onShown) { if (mService.mContext.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { // config_customUserSwitchUi is set to true on Automotive as CarSystemUI is @@ -3751,11 +3745,10 @@ class UserController implements Handler.Callback { + "condition if it's shown by CarSystemUI as well"); } synchronized (mUserSwitchingDialogLock) { - dismissUserSwitchingDialog(); - mUserSwitchingDialog = new UserSwitchingDialog(mService, mService.mContext, - fromUser, toUser, true /* above system */, switchingFromSystemUserMessage, - switchingToSystemUserMessage); - mUserSwitchingDialog.show(); + dismissUserSwitchingDialog(null); + mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser, + switchingFromSystemUserMessage, switchingToSystemUserMessage); + mUserSwitchingDialog.show(onShown); } } diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index a5651bfa3dde..649305f7ee01 100644 --- a/services/core/java/com/android/server/am/UserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java @@ -16,160 +16,258 @@ package com.android.server.am; -import android.app.AlertDialog; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.app.Dialog; import android.content.Context; import android.content.pm.UserInfo; import android.content.res.Resources; -import android.os.Handler; -import android.os.Message; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.Animatable2; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.Drawable; +import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; -import android.view.LayoutInflater; +import android.util.TypedValue; import android.view.View; -import android.view.ViewTreeObserver; +import android.view.Window; import android.view.WindowManager; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.widget.ImageView; import android.widget.TextView; import com.android.internal.R; -import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ObjectUtils; +import com.android.internal.util.UserIcons; /** - * Dialog to show when a user switch it about to happen. The intent is to snapshot the screen - * immediately after the dialog shows so that the user is informed that something is happening - * in the background rather than just freeze the screen and not know if the user-switch affordance - * was being handled. + * Dialog to show during the user switch. This dialog shows target user's name and their profile + * picture with a circular spinner animation around it if the animations for this dialog are not + * disabled. And covers the whole screen so that all the UI jank caused by the switch are hidden. */ -class UserSwitchingDialog extends AlertDialog - implements ViewTreeObserver.OnWindowShownListener { - private static final String TAG = "ActivityManagerUserSwitchingDialog"; - - // Time to wait for the onWindowShown() callback before continuing the user switch - private static final int WINDOW_SHOWN_TIMEOUT_MS = 3000; +class UserSwitchingDialog extends Dialog { + private static final String TAG = "UserSwitchingDialog"; + private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER; // User switching doesn't happen that frequently, so it doesn't hurt to have it always on protected static final boolean DEBUG = true; + private static final long DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS = 300; + private final boolean mDisableAnimations; + + protected final UserInfo mOldUser; + protected final UserInfo mNewUser; + private final String mSwitchingFromSystemUserMessage; + private final String mSwitchingToSystemUserMessage; + protected final Context mContext; + private final int mTraceCookie; - private final ActivityManagerService mService; - private final int mUserId; - private static final int MSG_START_USER = 1; - @GuardedBy("this") - private boolean mStartedUser; - final protected UserInfo mOldUser; - final protected UserInfo mNewUser; - final private String mSwitchingFromSystemUserMessage; - final private String mSwitchingToSystemUserMessage; - final protected Context mContext; - - public UserSwitchingDialog(ActivityManagerService service, Context context, UserInfo oldUser, - UserInfo newUser, boolean aboveSystem, String switchingFromSystemUserMessage, - String switchingToSystemUserMessage) { - super(context); + UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, + String switchingFromSystemUserMessage, String switchingToSystemUserMessage) { + // TODO(b/278857848): Make full screen user switcher cover top part of the screen as well. + // This problem is seen only on phones, it works fine on tablets. + super(context, R.style.Theme_Material_NoActionBar_Fullscreen); mContext = context; - mService = service; - mUserId = newUser.id; mOldUser = oldUser; mNewUser = newUser; mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage; mSwitchingToSystemUserMessage = switchingToSystemUserMessage; + mDisableAnimations = ActivityManager.isLowRamDeviceStatic() || SystemProperties.getBoolean( + "debug.usercontroller.disable_user_switching_dialog_animations", false); + mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id; inflateContent(); + configureWindow(); + } - if (aboveSystem) { - getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); - } - - WindowManager.LayoutParams attrs = getWindow().getAttributes(); + private void configureWindow() { + final Window window = getWindow(); + final WindowManager.LayoutParams attrs = window.getAttributes(); attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR | - WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - getWindow().setAttributes(attrs); + WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + window.setAttributes(attrs); + window.setBackgroundDrawableResource(android.R.color.transparent); + window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); } void inflateContent() { - // Set up the dialog contents setCancelable(false); - Resources res = getContext().getResources(); - // Custom view due to alignment and font size requirements - TextView view = (TextView) LayoutInflater.from(getContext()).inflate( - R.layout.user_switching_dialog, null); + setContentView(R.layout.user_switching_dialog); - String viewMessage = null; - if (UserManager.isDeviceInDemoMode(mContext)) { - if (mOldUser.isDemo()) { - viewMessage = res.getString(R.string.demo_restarting_message); + final TextView textView = findViewById(R.id.message); + if (textView != null) { + final String message = getTextMessage(); + textView.setAccessibilityPaneTitle(message); + textView.setText(message); + } + + final ImageView imageView = findViewById(R.id.icon); + if (imageView != null) { + imageView.setImageBitmap(getUserIconRounded()); + } + + final ImageView progressCircular = findViewById(R.id.progress_circular); + if (progressCircular != null) { + if (mDisableAnimations) { + progressCircular.setVisibility(View.GONE); } else { - viewMessage = res.getString(R.string.demo_starting_message); - } - } else { - if (mOldUser.id == UserHandle.USER_SYSTEM) { - viewMessage = mSwitchingFromSystemUserMessage; - } else if (mNewUser.id == UserHandle.USER_SYSTEM) { - viewMessage = mSwitchingToSystemUserMessage; + final TypedValue value = new TypedValue(); + getContext().getTheme().resolveAttribute(R.attr.colorAccentPrimary, value, true); + progressCircular.setColorFilter(value.data); } + } + } - // If switchingFromSystemUserMessage or switchingToSystemUserMessage is null, fallback - // to system message. - if (viewMessage == null) { - viewMessage = res.getString(R.string.user_switching_message, mNewUser.name); - } + private Bitmap getUserIconRounded() { + final Bitmap bmp = ObjectUtils.getOrElse(BitmapFactory.decodeFile(mNewUser.iconPath), + defaultUserIcon(mNewUser.id)); + final int w = bmp.getWidth(); + final int h = bmp.getHeight(); + final Bitmap bmpRounded = Bitmap.createBitmap(w, h, bmp.getConfig()); + final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setShader(new BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); + new Canvas(bmpRounded).drawRoundRect((new RectF(0, 0, w, h)), w / 2f, h / 2f, paint); + return bmpRounded; + } + + private Bitmap defaultUserIcon(@UserIdInt int userId) { + final Resources res = getContext().getResources(); + final Drawable icon = UserIcons.getDefaultUserIcon(res, userId, /* light= */ false); + return UserIcons.convertToBitmapAtUserIconSize(res, icon); + } - view.setCompoundDrawablesWithIntrinsicBounds(null, - getContext().getDrawable(R.drawable.ic_swap_horiz), null, null); + private String getTextMessage() { + final Resources res = getContext().getResources(); + + if (UserManager.isDeviceInDemoMode(mContext)) { + return res.getString(mOldUser.isDemo() + ? R.string.demo_restarting_message + : R.string.demo_starting_message); } - view.setAccessibilityPaneTitle(viewMessage); - view.setText(viewMessage); - setView(view); + + final String message = + mOldUser.id == UserHandle.USER_SYSTEM ? mSwitchingFromSystemUserMessage + : mNewUser.id == UserHandle.USER_SYSTEM ? mSwitchingToSystemUserMessage : null; + + return message != null ? message + // If switchingFromSystemUserMessage or switchingToSystemUserMessage is null, + // fallback to system message. + : res.getString(R.string.user_switching_message, mNewUser.name); } @Override public void show() { - if (DEBUG) Slog.d(TAG, "show called"); + asyncTraceBegin("", 0); super.show(); - final View decorView = getWindow().getDecorView(); - if (decorView != null) { - decorView.getViewTreeObserver().addOnWindowShownListener(this); - } - // Add a timeout as a safeguard, in case a race in screen on/off causes the window - // callback to never come. - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_USER), - WINDOW_SHOWN_TIMEOUT_MS); } @Override - public void onWindowShown() { - if (DEBUG) Slog.d(TAG, "onWindowShown called"); - startUser(); + public void dismiss() { + super.dismiss(); + asyncTraceEnd("", 0); + } + + public void show(@NonNull Runnable onShown) { + if (DEBUG) Slog.d(TAG, "show called"); + show(); + + if (mDisableAnimations) { + onShown.run(); + } else { + startShowAnimation(onShown); + } } - void startUser() { - synchronized (this) { - if (!mStartedUser) { - Slog.i(TAG, "starting user " + mUserId); - mService.mUserController.startUserInForeground(mUserId); + public void dismiss(@Nullable Runnable onDismissed) { + if (DEBUG) Slog.d(TAG, "dismiss called"); + + if (onDismissed == null) { + // no animation needed + dismiss(); + } else if (mDisableAnimations) { + dismiss(); + onDismissed.run(); + } else { + startDismissAnimation(() -> { dismiss(); - mStartedUser = true; - final View decorView = getWindow().getDecorView(); - if (decorView != null) { - decorView.getViewTreeObserver().removeOnWindowShownListener(this); - } - mHandler.removeMessages(MSG_START_USER); - } else { - Slog.i(TAG, "user " + mUserId + " already started"); - } + onDismissed.run(); + }); } } - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_START_USER: - Slog.w(TAG, "user switch window not shown in " - + WINDOW_SHOWN_TIMEOUT_MS + " ms"); - startUser(); - break; + private void startShowAnimation(Runnable onAnimationEnd) { + asyncTraceBegin("-showAnimation", 1); + startDialogAnimation(new AlphaAnimation(0, 1), () -> { + asyncTraceEnd("-showAnimation", 1); + + asyncTraceBegin("-spinnerAnimation", 2); + startProgressAnimation(() -> { + asyncTraceEnd("-spinnerAnimation", 2); + + onAnimationEnd.run(); + }); + }); + } + + private void startDismissAnimation(Runnable onAnimationEnd) { + asyncTraceBegin("-dismissAnimation", 3); + startDialogAnimation(new AlphaAnimation(1, 0), () -> { + asyncTraceEnd("-dismissAnimation", 3); + + onAnimationEnd.run(); + }); + } + + private void startProgressAnimation(Runnable onAnimationEnd) { + final ImageView progressCircular = findViewById(R.id.progress_circular); + final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) progressCircular.getDrawable(); + avd.registerAnimationCallback(new Animatable2.AnimationCallback() { + @Override + public void onAnimationEnd(Drawable drawable) { + onAnimationEnd.run(); } - } - }; + }); + avd.start(); + } + + private void startDialogAnimation(Animation animation, Runnable onAnimationEnd) { + animation.setDuration(DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS); + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + onAnimationEnd.run(); + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }); + findViewById(R.id.content).startAnimation(animation); + } + + private void asyncTraceBegin(String subTag, int subCookie) { + Trace.asyncTraceBegin(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie); + } + + private void asyncTraceEnd(String subTag, int subCookie) { + Trace.asyncTraceEnd(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie); + } } diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 299f86550efd..378363c870c7 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -1160,6 +1160,14 @@ public class AutomaticBrightnessController { update(); } + /** + * Convert a brightness float scale value to a nit value. Adjustments, such as RBC, are not + * applied. This is used when storing the brightness in nits for the default display and when + * passing the brightness value to follower displays. + * + * @param brightness The float scale value + * @return The nit value or -1f if no conversion is possible. + */ public float convertToNits(float brightness) { if (mCurrentBrightnessMapper != null) { return mCurrentBrightnessMapper.convertToNits(brightness); @@ -1168,6 +1176,30 @@ public class AutomaticBrightnessController { } } + /** + * Convert a brightness float scale value to a nit value. Adjustments, such as RBC are applied. + * This is used when sending the brightness value to + * {@link com.android.server.display.BrightnessTracker}. + * + * @param brightness The float scale value + * @return The nit value or -1f if no conversion is possible. + */ + public float convertToAdjustedNits(float brightness) { + if (mCurrentBrightnessMapper != null) { + return mCurrentBrightnessMapper.convertToAdjustedNits(brightness); + } else { + return -1.0f; + } + } + + /** + * Convert a brightness nit value to a float scale value. It is assumed that the nit value + * provided does not have adjustments, such as RBC, applied. + * + * @param nits The nit value + * @return The float scale value or {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if no + * conversion is possible. + */ public float convertToFloatScale(float nits) { if (mCurrentBrightnessMapper != null) { return mCurrentBrightnessMapper.convertToFloatScale(nits); diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index 3456e3e262de..df2a8301f543 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -322,6 +322,14 @@ public abstract class BrightnessMappingStrategy { public abstract float convertToNits(float brightness); /** + * Converts the provided brightness value to nits if possible. Adjustments, such as RBC are + * applied. + * + * Returns -1.0f if there's no available mapping for the brightness to nits. + */ + public abstract float convertToAdjustedNits(float brightness); + + /** * Converts the provided nit value to a float scale value if possible. * * Returns {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if there's no available mapping for @@ -683,6 +691,11 @@ public abstract class BrightnessMappingStrategy { } @Override + public float convertToAdjustedNits(float brightness) { + return -1.0f; + } + + @Override public float convertToFloatScale(float nits) { return PowerManager.BRIGHTNESS_INVALID_FLOAT; } @@ -804,6 +817,14 @@ public abstract class BrightnessMappingStrategy { // a brightness in nits. private Spline mBrightnessToNitsSpline; + // A spline mapping from nits with adjustments applied to the corresponding brightness + // value, normalized to the range [0, 1.0]. + private Spline mAdjustedNitsToBrightnessSpline; + + // A spline mapping from the system brightness value, normalized to the range [0, 1.0], to + // a brightness in nits with adjustments applied. + private Spline mBrightnessToAdjustedNitsSpline; + // The default brightness configuration. private final BrightnessConfiguration mDefaultConfig; @@ -843,6 +864,8 @@ public abstract class BrightnessMappingStrategy { mNits = nits; mBrightness = brightness; computeNitsBrightnessSplines(mNits); + mAdjustedNitsToBrightnessSpline = mNitsToBrightnessSpline; + mBrightnessToAdjustedNitsSpline = mBrightnessToNitsSpline; mDefaultConfig = config; if (mLoggingEnabled) { @@ -892,7 +915,7 @@ public abstract class BrightnessMappingStrategy { nits = mDisplayWhiteBalanceController.calculateAdjustedBrightnessNits(nits); } - float brightness = mNitsToBrightnessSpline.interpolate(nits); + float brightness = mAdjustedNitsToBrightnessSpline.interpolate(nits); // Correct the brightness according to the current application and its category, but // only if no user data point is set (as this will override the user setting). if (mUserLux == -1) { @@ -930,6 +953,11 @@ public abstract class BrightnessMappingStrategy { } @Override + public float convertToAdjustedNits(float brightness) { + return mBrightnessToAdjustedNitsSpline.interpolate(brightness); + } + + @Override public float convertToFloatScale(float nits) { return mNitsToBrightnessSpline.interpolate(nits); } @@ -989,7 +1017,13 @@ public abstract class BrightnessMappingStrategy { @Override public void recalculateSplines(boolean applyAdjustment, float[] adjustedNits) { mBrightnessRangeAdjustmentApplied = applyAdjustment; - computeNitsBrightnessSplines(mBrightnessRangeAdjustmentApplied ? adjustedNits : mNits); + if (applyAdjustment) { + mAdjustedNitsToBrightnessSpline = Spline.createSpline(adjustedNits, mBrightness); + mBrightnessToAdjustedNitsSpline = Spline.createSpline(mBrightness, adjustedNits); + } else { + mAdjustedNitsToBrightnessSpline = mNitsToBrightnessSpline; + mBrightnessToAdjustedNitsSpline = mBrightnessToNitsSpline; + } } @Override @@ -999,6 +1033,8 @@ public abstract class BrightnessMappingStrategy { pw.println(" mBrightnessSpline=" + mBrightnessSpline); pw.println(" mNitsToBrightnessSpline=" + mNitsToBrightnessSpline); pw.println(" mBrightnessToNitsSpline=" + mBrightnessToNitsSpline); + pw.println(" mAdjustedNitsToBrightnessSpline=" + mAdjustedNitsToBrightnessSpline); + pw.println(" mAdjustedBrightnessToNitsSpline=" + mBrightnessToAdjustedNitsSpline); pw.println(" mMaxGamma=" + mMaxGamma); pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment); pw.println(" mUserLux=" + mUserLux); @@ -1072,7 +1108,7 @@ public abstract class BrightnessMappingStrategy { float defaultNits = defaultSpline.interpolate(lux); float longTermNits = currSpline.interpolate(lux); float shortTermNits = mBrightnessSpline.interpolate(lux); - float brightness = mNitsToBrightnessSpline.interpolate(shortTermNits); + float brightness = mAdjustedNitsToBrightnessSpline.interpolate(shortTermNits); String luxPrefix = (lux == mUserLux ? "^" : ""); String strLux = luxPrefix + toStrFloatForDump(lux); @@ -1146,7 +1182,7 @@ public abstract class BrightnessMappingStrategy { float[] defaultNits = defaultCurve.second; float[] defaultBrightness = new float[defaultNits.length]; for (int i = 0; i < defaultBrightness.length; i++) { - defaultBrightness[i] = mNitsToBrightnessSpline.interpolate(defaultNits[i]); + defaultBrightness[i] = mAdjustedNitsToBrightnessSpline.interpolate(defaultNits[i]); } Pair<float[], float[]> curve = getAdjustedCurve(defaultLux, defaultBrightness, mUserLux, mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma); @@ -1154,7 +1190,7 @@ public abstract class BrightnessMappingStrategy { float[] brightness = curve.second; float[] nits = new float[brightness.length]; for (int i = 0; i < nits.length; i++) { - nits[i] = mBrightnessToNitsSpline.interpolate(brightness[i]); + nits[i] = mBrightnessToAdjustedNitsSpline.interpolate(brightness[i]); } mBrightnessSpline = Spline.createSpline(lux, nits); } @@ -1162,7 +1198,7 @@ public abstract class BrightnessMappingStrategy { private float getUnadjustedBrightness(float lux) { Pair<float[], float[]> curve = mConfig.getCurve(); Spline spline = Spline.createSpline(curve.first, curve.second); - return mNitsToBrightnessSpline.interpolate(spline.interpolate(lux)); + return mAdjustedNitsToBrightnessSpline.interpolate(spline.interpolate(lux)); } private float correctBrightness(float brightness, String packageName, int category) { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 5d92c7f8cf05..26b6cb0ebe9b 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1781,7 +1781,24 @@ public final class DisplayManagerService extends SystemService { } else { configurePreferredDisplayModeLocked(display); } - addDisplayPowerControllerLocked(display); + DisplayPowerControllerInterface dpc = addDisplayPowerControllerLocked(display); + + if (dpc != null) { + final int leadDisplayId = display.getLeadDisplayIdLocked(); + updateDisplayPowerControllerLeaderLocked(dpc, leadDisplayId); + + // Loop through all the displays and check if any should follow this one - it could be + // that the follower display was added before the lead display. + mLogicalDisplayMapper.forEachLocked(d -> { + if (d.getLeadDisplayIdLocked() == displayId) { + DisplayPowerControllerInterface followerDpc = + mDisplayPowerControllers.get(d.getDisplayIdLocked()); + if (followerDpc != null) { + updateDisplayPowerControllerLeaderLocked(followerDpc, displayId); + } + } + }); + } mDisplayStates.append(displayId, Display.STATE_UNKNOWN); @@ -1832,8 +1849,8 @@ public final class DisplayManagerService extends SystemService { } } - private void updateDisplayPowerControllerLeaderLocked(DisplayPowerControllerInterface dpc, - int leadDisplayId) { + private void updateDisplayPowerControllerLeaderLocked( + @NonNull DisplayPowerControllerInterface dpc, int leadDisplayId) { if (dpc.getLeadDisplayId() == leadDisplayId) { // Lead display hasn't changed, nothing to do. return; @@ -1851,9 +1868,11 @@ public final class DisplayManagerService extends SystemService { // And then, if it's following, register it with the new one. if (leadDisplayId != Layout.NO_LEAD_DISPLAY) { - final DisplayPowerControllerInterface newLead = + final DisplayPowerControllerInterface newLeader = mDisplayPowerControllers.get(leadDisplayId); - newLead.addDisplayBrightnessFollower(dpc); + if (newLeader != null) { + newLeader.addDisplayBrightnessFollower(dpc); + } } } @@ -1872,6 +1891,7 @@ public final class DisplayManagerService extends SystemService { final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.removeReturnOld(displayId); if (dpc != null) { + updateDisplayPowerControllerLeaderLocked(dpc, Layout.NO_LEAD_DISPLAY); dpc.stop(); } mDisplayStates.delete(displayId); @@ -3062,10 +3082,11 @@ public final class DisplayManagerService extends SystemService { } @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) - private void addDisplayPowerControllerLocked(LogicalDisplay display) { + private DisplayPowerControllerInterface addDisplayPowerControllerLocked( + LogicalDisplay display) { if (mPowerHandler == null) { // initPowerManagement has not yet been called. - return; + return null; } if (mBrightnessTracker == null && display.getDisplayIdLocked() == Display.DEFAULT_DISPLAY) { @@ -3086,7 +3107,7 @@ public final class DisplayManagerService extends SystemService { if (hbmMetadata == null) { Slog.wtf(TAG, "High Brightness Mode Metadata is null in DisplayManagerService for " + "display: " + display.getDisplayIdLocked()); - return; + return null; } if (DeviceConfig.getBoolean("display_manager", "use_newly_structured_display_power_controller", true)) { @@ -3101,6 +3122,7 @@ public final class DisplayManagerService extends SystemService { () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted); } mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController); + return displayPowerController; } private void handleBrightnessChange(LogicalDisplay display) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 1acc20880258..f1efec04fa69 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -789,6 +789,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } + @GuardedBy("mLock") + private void clearDisplayBrightnessFollowersLocked() { + for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) { + DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i); + mHandler.postAtTime(() -> follower.setBrightnessToFollow( + PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1, + /* ambientLux= */ 0), mClock.uptimeMillis()); + } + mDisplayBrightnessFollowers.clear(); + } + @Nullable @Override public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats( @@ -946,6 +957,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @Override public void stop() { synchronized (mLock) { + clearDisplayBrightnessFollowersLocked(); + mStopped = true; Message msg = mHandler.obtainMessage(MSG_STOP); mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); @@ -1039,7 +1052,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call noteScreenBrightness(mPowerState.getScreenBrightness()); // Initialize all of the brightness tracking state - final float brightness = convertToNits(mPowerState.getScreenBrightness()); + final float brightness = convertToAdjustedNits(mPowerState.getScreenBrightness()); if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) { mBrightnessTracker.start(brightness); } @@ -2698,7 +2711,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated, boolean wasShortTermModelActive) { - final float brightnessInNits = convertToNits(brightness); + final float brightnessInNits = convertToAdjustedNits(brightness); if (mUseAutoBrightness && brightnessInNits >= 0.0f && mAutomaticBrightnessController != null && mBrightnessTracker != null) { // We only want to track changes on devices that can actually map the display backlight @@ -2722,6 +2735,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return mAutomaticBrightnessController.convertToNits(brightness); } + private float convertToAdjustedNits(float brightness) { + if (mAutomaticBrightnessController == null) { + return -1f; + } + return mAutomaticBrightnessController.convertToAdjustedNits(brightness); + } + private float convertToFloatScale(float nits) { if (mAutomaticBrightnessController == null) { return PowerManager.BRIGHTNESS_INVALID_FLOAT; @@ -3075,16 +3095,16 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1; float appliedHbmMaxNits = event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF - ? -1f : convertToNits(event.getHbmMax()); + ? -1f : convertToAdjustedNits(event.getHbmMax()); // thermalCapNits set to -1 if not currently capping max brightness float appliedThermalCapNits = event.getThermalMax() == PowerManager.BRIGHTNESS_MAX - ? -1f : convertToNits(event.getThermalMax()); + ? -1f : convertToAdjustedNits(event.getThermalMax()); if (mIsDisplayInternal) { FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED, - convertToNits(event.getInitialBrightness()), - convertToNits(event.getBrightness()), + convertToAdjustedNits(event.getInitialBrightness()), + convertToAdjustedNits(event.getBrightness()), event.getLux(), event.getPhysicalDisplayId(), event.wasShortTermModelActive(), diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index b36aedefa2a7..59e112ecbccb 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -764,6 +764,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal @Override public void stop() { synchronized (mLock) { + clearDisplayBrightnessFollowersLocked(); + mStopped = true; Message msg = mHandler.obtainMessage(MSG_STOP); mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); @@ -854,7 +856,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal noteScreenBrightness(mPowerState.getScreenBrightness()); // Initialize all of the brightness tracking state - final float brightness = mDisplayBrightnessController.convertToNits( + final float brightness = mDisplayBrightnessController.convertToAdjustedNits( mPowerState.getScreenBrightness()); if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) { mBrightnessTracker.start(brightness); @@ -2165,7 +2167,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated, boolean wasShortTermModelActive) { - final float brightnessInNits = mDisplayBrightnessController.convertToNits(brightness); + final float brightnessInNits = + mDisplayBrightnessController.convertToAdjustedNits(brightness); if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness() && brightnessInNits >= 0.0f && mAutomaticBrightnessController != null && mBrightnessTracker != null) { // We only want to track changes on devices that can actually map the display backlight @@ -2200,6 +2203,17 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } } + @GuardedBy("mLock") + private void clearDisplayBrightnessFollowersLocked() { + for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) { + DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i); + mHandler.postAtTime(() -> follower.setBrightnessToFollow( + PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1, + /* ambientLux= */ 0), mClock.uptimeMillis()); + } + mDisplayBrightnessFollowers.clear(); + } + @Override public void dump(final PrintWriter pw) { synchronized (mLock) { @@ -2438,15 +2452,16 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1; float appliedHbmMaxNits = event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF - ? -1f : mDisplayBrightnessController.convertToNits(event.getHbmMax()); + ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getHbmMax()); // thermalCapNits set to -1 if not currently capping max brightness float appliedThermalCapNits = event.getThermalMax() == PowerManager.BRIGHTNESS_MAX - ? -1f : mDisplayBrightnessController.convertToNits(event.getThermalMax()); + ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getThermalMax()); if (mIsDisplayInternal) { FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED, - mDisplayBrightnessController.convertToNits(event.getInitialBrightness()), - mDisplayBrightnessController.convertToNits(event.getBrightness()), + mDisplayBrightnessController + .convertToAdjustedNits(event.getInitialBrightness()), + mDisplayBrightnessController.convertToAdjustedNits(event.getBrightness()), event.getLux(), event.getPhysicalDisplayId(), event.wasShortTermModelActive(), diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index 2916fefefe1b..a3f8c4d16cd1 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -312,7 +312,10 @@ public final class DisplayBrightnessController { } /** - * Convert a brightness float scale value to a nit value. + * Convert a brightness float scale value to a nit value. Adjustments, such as RBC, are not + * applied. This is used when storing the brightness in nits for the default display and when + * passing the brightness value to follower displays. + * * @param brightness The float scale value * @return The nit value or -1f if no conversion is possible. */ @@ -324,7 +327,24 @@ public final class DisplayBrightnessController { } /** - * Convert a brightness nit value to a float scale value. + * Convert a brightness float scale value to a nit value. Adjustments, such as RBC are applied. + * This is used when sending the brightness value to + * {@link com.android.server.display.BrightnessTracker}. + * + * @param brightness The float scale value + * @return The nit value or -1f if no conversion is possible. + */ + public float convertToAdjustedNits(float brightness) { + if (mAutomaticBrightnessController == null) { + return -1f; + } + return mAutomaticBrightnessController.convertToAdjustedNits(brightness); + } + + /** + * Convert a brightness nit value to a float scale value. It is assumed that the nit value + * provided does not have adjustments, such as RBC, applied. + * * @param nits The nit value * @return The float scale value or {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if no * conversion is possible. diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 73440b7f2eec..12fc263e1c8c 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -140,34 +140,39 @@ abstract public class ManagedServices { * The services that have been bound by us. If the service is also connected, it will also * be in {@link #mServices}. */ + @GuardedBy("mMutex") private final ArrayList<Pair<ComponentName, Integer>> mServicesBound = new ArrayList<>(); + @GuardedBy("mMutex") private final ArraySet<Pair<ComponentName, Integer>> mServicesRebinding = new ArraySet<>(); // we need these packages to be protected because classes that inherit from it need to see it protected final Object mDefaultsLock = new Object(); + @GuardedBy("mDefaultsLock") protected final ArraySet<ComponentName> mDefaultComponents = new ArraySet<>(); + @GuardedBy("mDefaultsLock") protected final ArraySet<String> mDefaultPackages = new ArraySet<>(); // lists the component names of all enabled (and therefore potentially connected) // app services for current profiles. - private ArraySet<ComponentName> mEnabledServicesForCurrentProfiles - = new ArraySet<>(); + @GuardedBy("mMutex") + private final ArraySet<ComponentName> mEnabledServicesForCurrentProfiles = new ArraySet<>(); // Just the packages from mEnabledServicesForCurrentProfiles - private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>(); + @GuardedBy("mMutex") + private final ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>(); // Per user id, list of enabled packages that have nevertheless asked not to be run - private final android.util.SparseSetArray<ComponentName> mSnoozing = - new android.util.SparseSetArray<>(); + @GuardedBy("mSnoozing") + private final SparseSetArray<ComponentName> mSnoozing = new SparseSetArray<>(); // List of approved packages or components (by user, then by primary/secondary) that are // allowed to be bound as managed services. A package or component appearing in this list does // not mean that we are currently bound to said package/component. + @GuardedBy("mApproved") protected final ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved = new ArrayMap<>(); - // List of packages or components (by user) that are configured to be enabled/disabled // explicitly by the user @GuardedBy("mApproved") protected ArrayMap<Integer, ArraySet<String>> mUserSetServices = new ArrayMap<>(); - + @GuardedBy("mApproved") protected ArrayMap<Integer, Boolean> mIsUserChanged = new ArrayMap<>(); // True if approved services are stored in xml, not settings. @@ -262,20 +267,18 @@ abstract public class ManagedServices { @NonNull ArrayMap<Boolean, ArrayList<ComponentName>> resetComponents(String packageName, int userId) { // components that we want to enable - ArrayList<ComponentName> componentsToEnable = - new ArrayList<>(mDefaultComponents.size()); - + ArrayList<ComponentName> componentsToEnable; // components that were removed - ArrayList<ComponentName> disabledComponents = - new ArrayList<>(mDefaultComponents.size()); - + ArrayList<ComponentName> disabledComponents; // all components that are enabled now - ArraySet<ComponentName> enabledComponents = - new ArraySet<>(getAllowedComponents(userId)); + ArraySet<ComponentName> enabledComponents = new ArraySet<>(getAllowedComponents(userId)); boolean changed = false; synchronized (mDefaultsLock) { + componentsToEnable = new ArrayList<>(mDefaultComponents.size()); + disabledComponents = new ArrayList<>(mDefaultComponents.size()); + // record all components that are enabled but should not be by default for (int i = 0; i < mDefaultComponents.size() && enabledComponents.size() > 0; i++) { ComponentName currentDefault = mDefaultComponents.valueAt(i); @@ -374,14 +377,14 @@ abstract public class ManagedServices { } } - pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size() - + ") enabled for current profiles:"); - for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) { - if (filter != null && !filter.matches(cmpt)) continue; - pw.println(" " + cmpt); - } - synchronized (mMutex) { + pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size() + + ") enabled for current profiles:"); + for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) { + if (filter != null && !filter.matches(cmpt)) continue; + pw.println(" " + cmpt); + } + pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):"); for (ManagedServiceInfo info : mServices) { if (filter != null && !filter.matches(info.component)) continue; @@ -434,12 +437,12 @@ abstract public class ManagedServices { } } - for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) { - if (filter != null && !filter.matches(cmpt)) continue; - cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED); - } synchronized (mMutex) { + for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) { + if (filter != null && !filter.matches(cmpt)) continue; + cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED); + } for (ManagedServiceInfo info : mServices) { if (filter != null && !filter.matches(info.component)) continue; info.dumpDebug(proto, ManagedServicesProto.LIVE_SERVICES, this); @@ -677,7 +680,9 @@ abstract public class ManagedServices { if (isUserChanged == null) { //NLS userSetComponent = TextUtils.emptyIfNull(userSetComponent); } else { //NAS - mIsUserChanged.put(resolvedUserId, Boolean.valueOf(isUserChanged)); + synchronized (mApproved) { + mIsUserChanged.put(resolvedUserId, Boolean.valueOf(isUserChanged)); + } userSetComponent = Boolean.valueOf(isUserChanged) ? approved : ""; } } else { @@ -688,7 +693,9 @@ abstract public class ManagedServices { if (isUserChanged_Old != null && Boolean.valueOf(isUserChanged_Old)) { //user_set = true userSetComponent = approved; - mIsUserChanged.put(resolvedUserId, true); + synchronized (mApproved) { + mIsUserChanged.put(resolvedUserId, true); + } needUpgradeUserset = false; } else { userSetComponent = ""; @@ -724,8 +731,11 @@ abstract public class ManagedServices { } void upgradeDefaultsXmlVersion() { - // check if any defaults are loaded - int defaultsSize = mDefaultComponents.size() + mDefaultPackages.size(); + int defaultsSize; + synchronized (mDefaultsLock) { + // check if any defaults are loaded + defaultsSize = mDefaultComponents.size() + mDefaultPackages.size(); + } if (defaultsSize == 0) { // load defaults from current allowed if (this.mApprovalLevel == APPROVAL_BY_COMPONENT) { @@ -741,8 +751,10 @@ abstract public class ManagedServices { } } } + synchronized (mDefaultsLock) { + defaultsSize = mDefaultComponents.size() + mDefaultPackages.size(); + } // if no defaults are loaded, then load from config - defaultsSize = mDefaultComponents.size() + mDefaultPackages.size(); if (defaultsSize == 0) { loadDefaultsFromConfig(); } @@ -806,7 +818,9 @@ abstract public class ManagedServices { } protected boolean isComponentEnabledForPackage(String pkg) { - return mEnabledServicesPackageNames.contains(pkg); + synchronized (mMutex) { + return mEnabledServicesPackageNames.contains(pkg); + } } protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId, @@ -959,9 +973,13 @@ abstract public class ManagedServices { } public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) { - if (DEBUG) Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage - + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList)) - + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames); + if (DEBUG) { + synchronized (mMutex) { + Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage + + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList)) + + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames); + } + } if (pkgList != null && (pkgList.length > 0)) { boolean anyServicesInvolved = false; @@ -975,7 +993,7 @@ abstract public class ManagedServices { } } for (String pkgName : pkgList) { - if (mEnabledServicesPackageNames.contains(pkgName)) { + if (isComponentEnabledForPackage(pkgName)) { anyServicesInvolved = true; } if (uidList != null && uidList.length > 0) { @@ -1299,9 +1317,11 @@ abstract public class ManagedServices { } final Set<ComponentName> add = new HashSet<>(userComponents); - ArraySet<ComponentName> snoozed = mSnoozing.get(userId); - if (snoozed != null) { - add.removeAll(snoozed); + synchronized (mSnoozing) { + ArraySet<ComponentName> snoozed = mSnoozing.get(userId); + if (snoozed != null) { + add.removeAll(snoozed); + } } componentsToBind.put(userId, add); @@ -1605,9 +1625,12 @@ abstract public class ManagedServices { } } + @VisibleForTesting boolean isBound(ComponentName cn, int userId) { final Pair<ComponentName, Integer> servicesBindingTag = Pair.create(cn, userId); - return mServicesBound.contains(servicesBindingTag); + synchronized (mMutex) { + return mServicesBound.contains(servicesBindingTag); + } } protected boolean isBoundOrRebinding(final ComponentName cn, final int userId) { @@ -1833,7 +1856,9 @@ abstract public class ManagedServices { public boolean isEnabledForCurrentProfiles() { if (this.isSystem) return true; if (this.connection == null) return false; - return mEnabledServicesForCurrentProfiles.contains(this.component); + synchronized (mMutex) { + return mEnabledServicesForCurrentProfiles.contains(this.component); + } } /** @@ -1877,7 +1902,9 @@ abstract public class ManagedServices { /** convenience method for looking in mEnabledServicesForCurrentProfiles */ public boolean isComponentEnabledForCurrentProfiles(ComponentName component) { - return mEnabledServicesForCurrentProfiles.contains(component); + synchronized (mMutex) { + return mEnabledServicesForCurrentProfiles.contains(component); + } } public static class UserProfiles { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 07891f30abd0..d1d6f5f10bc6 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -693,6 +693,7 @@ public class NotificationManagerService extends SystemService { private NotificationRecordLogger mNotificationRecordLogger; private InstanceIdSequence mNotificationInstanceIdSequence; private Set<String> mMsgPkgsAllowedAsConvos = new HashSet(); + private String mDefaultSearchSelectorPkg; // Broadcast intent receiver for notification permissions review-related intents private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver; @@ -2435,6 +2436,8 @@ public class NotificationManagerService extends SystemService { mMsgPkgsAllowedAsConvos = Set.of(getStringArrayResource( com.android.internal.R.array.config_notificationMsgPkgsAllowedAsConvos)); + mDefaultSearchSelectorPkg = getContext().getString(getContext().getResources() + .getIdentifier("config_defaultSearchSelectorPackageName", "string", "android")); mFlagResolver = flagResolver; @@ -6934,7 +6937,12 @@ public class NotificationManagerService extends SystemService { */ private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) { return notification.isMediaNotification() || isEnterpriseExempted(ai) - || isCallNotification(ai.packageName, ai.uid, notification); + || isCallNotification(ai.packageName, ai.uid, notification) + || isDefaultSearchSelectorPackage(ai.packageName); + } + + private boolean isDefaultSearchSelectorPackage(String pkg) { + return Objects.equals(mDefaultSearchSelectorPkg, pkg); } private boolean isEnterpriseExempted(ApplicationInfo ai) { diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index 2704f56b539d..02052808dd18 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -393,7 +393,7 @@ public final class BroadcastHelper { public static boolean isPrivacySafetyLabelChangeNotificationsEnabled(Context context) { PackageManager packageManager = context.getPackageManager(); return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, false) + SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, true) && !packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) && !packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) && !packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index fc6b4e9dcb20..d62d53ec8495 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3484,6 +3484,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/); } return true; + case KeyEvent.KEYCODE_ESCAPE: + if (down && repeatCount == 0) { + mContext.closeSystemDialogs(); + } + return true; } return false; @@ -4571,6 +4576,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_BACK: return mWakeOnBackKeyPress; + + case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: + return mStylusButtonsEnabled; } return true; diff --git a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java index 1d63489f3c4f..eb6d28e76ff8 100644 --- a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java +++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java @@ -17,6 +17,8 @@ package com.android.server.power.stats.wakeups; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM; +import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA; +import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI; @@ -54,10 +56,11 @@ import java.util.regex.Pattern; */ public class CpuWakeupStats { private static final String TAG = "CpuWakeupStats"; - private static final String SUBSYSTEM_ALARM_STRING = "Alarm"; private static final String SUBSYSTEM_WIFI_STRING = "Wifi"; private static final String SUBSYSTEM_SOUND_TRIGGER_STRING = "Sound_trigger"; + private static final String SUBSYSTEM_SENSOR_STRING = "Sensor"; + private static final String SUBSYSTEM_CELLULAR_DATA_STRING = "Cellular_data"; private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution"; @VisibleForTesting static final long WAKEUP_REASON_HALF_WINDOW_MS = 500; @@ -111,6 +114,10 @@ public class CpuWakeupStats { return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__WIFI; case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER: return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__SOUND_TRIGGER; + case CPU_WAKEUP_SUBSYSTEM_SENSOR: + return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__SENSOR; + case CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA: + return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__CELLULAR_DATA; } return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN; } @@ -542,6 +549,10 @@ public class CpuWakeupStats { return CPU_WAKEUP_SUBSYSTEM_WIFI; case SUBSYSTEM_SOUND_TRIGGER_STRING: return CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER; + case SUBSYSTEM_SENSOR_STRING: + return CPU_WAKEUP_SUBSYSTEM_SENSOR; + case SUBSYSTEM_CELLULAR_DATA_STRING: + return CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA; } return CPU_WAKEUP_SUBSYSTEM_UNKNOWN; } @@ -554,6 +565,10 @@ public class CpuWakeupStats { return SUBSYSTEM_WIFI_STRING; case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER: return SUBSYSTEM_SOUND_TRIGGER_STRING; + case CPU_WAKEUP_SUBSYSTEM_SENSOR: + return SUBSYSTEM_SENSOR_STRING; + case CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA: + return SUBSYSTEM_CELLULAR_DATA_STRING; case CPU_WAKEUP_SUBSYSTEM_UNKNOWN: return "Unknown"; } diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 2007079ea5ca..0ca560398657 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -121,7 +121,7 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; } else if (getAvailableRollback(failedPackage) != null) { // Rollback is available, we may get a callback into #execute - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_60; } else if (anyRollbackAvailable) { // If any rollbacks are available, we will commit them impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70; diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java index cd1a968a8e89..97e463646fdc 100644 --- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java +++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java @@ -19,14 +19,18 @@ package com.android.server.security.rkp; import android.content.Context; import android.os.Binder; import android.os.OutcomeReceiver; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.security.rkp.IGetRegistrationCallback; import android.security.rkp.IRemoteProvisioning; import android.security.rkp.service.RegistrationProxy; import android.util.Log; +import com.android.internal.util.DumpUtils; import com.android.server.SystemService; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.time.Duration; import java.util.concurrent.Executor; @@ -97,5 +101,18 @@ public class RemoteProvisioningService extends SystemService { Binder.restoreCallingIdentity(callingIdentity); } } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; + new RemoteProvisioningShellCommand().dump(pw); + } + + @Override + public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, + ParcelFileDescriptor err, String[] args) { + return new RemoteProvisioningShellCommand().exec(this, in.getFileDescriptor(), + out.getFileDescriptor(), err.getFileDescriptor(), args); + } } } diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java new file mode 100644 index 000000000000..71eca691dba7 --- /dev/null +++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java @@ -0,0 +1,250 @@ +/* + * 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.security.rkp; + +import android.hardware.security.keymint.DeviceInfo; +import android.hardware.security.keymint.IRemotelyProvisionedComponent; +import android.hardware.security.keymint.MacedPublicKey; +import android.hardware.security.keymint.ProtectedData; +import android.hardware.security.keymint.RpcHardwareInfo; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ShellCommand; +import android.util.IndentingPrintWriter; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.util.Base64; + +import co.nstant.in.cbor.CborDecoder; +import co.nstant.in.cbor.CborEncoder; +import co.nstant.in.cbor.CborException; +import co.nstant.in.cbor.model.Array; +import co.nstant.in.cbor.model.ByteString; +import co.nstant.in.cbor.model.DataItem; +import co.nstant.in.cbor.model.Map; +import co.nstant.in.cbor.model.SimpleValue; +import co.nstant.in.cbor.model.UnsignedInteger; + +class RemoteProvisioningShellCommand extends ShellCommand { + private static final String USAGE = "usage: cmd remote_provisioning SUBCOMMAND [ARGS]\n" + + "help\n" + + " Show this message.\n" + + "dump\n" + + " Dump service diagnostics.\n" + + "list [--min-version MIN_VERSION]\n" + + " List the names of the IRemotelyProvisionedComponent instances.\n" + + "csr [--challenge CHALLENGE] NAME\n" + + " Generate and print a base64-encoded CSR from the named\n" + + " IRemotelyProvisionedComponent. A base64-encoded challenge can be provided,\n" + + " or else it defaults to an empty challenge.\n"; + + @VisibleForTesting + static final String EEK_ED25519_BASE64 = "goRDoQEnoFgqpAEBAycgBiFYIJm57t1e5FL2hcZMYtw+YatXSH11N" + + "ymtdoAy0rPLY1jZWEAeIghLpLekyNdOAw7+uK8UTKc7b6XN3Np5xitk/pk5r3bngPpmAIUNB5gqrJFcpyUUS" + + "QY0dcqKJ3rZ41pJ6wIDhEOhASegWE6lAQECWCDQrsEVyirPc65rzMvRlh1l6LHd10oaN7lDOpfVmd+YCAM4G" + + "CAEIVggvoXnRsSjQlpA2TY6phXQLFh+PdwzAjLS/F4ehyVfcmBYQJvPkOIuS6vRGLEOjl0gJ0uEWP78MpB+c" + + "gWDvNeCvvpkeC1UEEvAMb9r6B414vAtzmwvT/L1T6XUg62WovGHWAQ="; + + @VisibleForTesting + static final String EEK_P256_BASE64 = "goRDoQEmoFhNpQECAyYgASFYIPcUituX9MxT79JkEcTjdR9mH6RxDGzP" + + "+glGgHSHVPKtIlggXn9b9uzk9hnM/xM3/Q+hyJPbGAZ2xF3m12p3hsMtr49YQC+XjkL7vgctlUeFR5NAsB/U" + + "m0ekxESp8qEHhxDHn8sR9L+f6Dvg5zRMFfx7w34zBfTRNDztAgRgehXgedOK/ySEQ6EBJqBYcaYBAgJYIDVz" + + "tz+gioCJsSZn6ct8daGvAmH8bmUDkTvTS30UlD5GAzgYIAEhWCDgQc8vDzQPHDMsQbDP1wwwVTXSHmpHE0su" + + "0UiWfiScaCJYIB/ORcX7YbqBIfnlBZubOQ52hoZHuB4vRfHOr9o/gGjbWECMs7p+ID4ysGjfYNEdffCsOI5R" + + "vP9s4Wc7Snm8Vnizmdh8igfY2rW1f3H02GvfMyc0e2XRKuuGmZirOrSAqr1Q"; + + private static final int ERROR = -1; + private static final int SUCCESS = 0; + + private final Injector mInjector; + + RemoteProvisioningShellCommand() { + this(new Injector()); + } + + @VisibleForTesting + RemoteProvisioningShellCommand(Injector injector) { + mInjector = injector; + } + + @Override + public void onHelp() { + getOutPrintWriter().print(USAGE); + } + + @Override + @SuppressWarnings("CatchAndPrintStackTrace") + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + try { + switch (cmd) { + case "list": + return list(); + case "csr": + return csr(); + default: + return handleDefaultCommands(cmd); + } + } catch (Exception e) { + e.printStackTrace(getErrPrintWriter()); + return ERROR; + } + } + + @SuppressWarnings("CatchAndPrintStackTrace") + void dump(PrintWriter pw) { + try { + IndentingPrintWriter ipw = new IndentingPrintWriter(pw); + for (String name : mInjector.getIrpcNames()) { + ipw.println(name + ":"); + ipw.increaseIndent(); + dumpRpcInstance(ipw, name); + ipw.decreaseIndent(); + } + } catch (Exception e) { + e.printStackTrace(pw); + } + } + + private void dumpRpcInstance(PrintWriter pw, String name) throws RemoteException { + RpcHardwareInfo info = mInjector.getIrpcBinder(name).getHardwareInfo(); + pw.println("hwVersion=" + info.versionNumber); + pw.println("rpcAuthorName=" + info.rpcAuthorName); + if (info.versionNumber < 3) { + pw.println("supportedEekCurve=" + info.supportedEekCurve); + } + pw.println("uniqueId=" + info.uniqueId); + pw.println("supportedNumKeysInCsr=" + info.supportedNumKeysInCsr); + } + + private int list() throws RemoteException { + for (String name : mInjector.getIrpcNames()) { + getOutPrintWriter().println(name); + } + return SUCCESS; + } + + private int csr() throws RemoteException, CborException { + byte[] challenge = {}; + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--challenge": + challenge = Base64.getDecoder().decode(getNextArgRequired()); + break; + default: + getErrPrintWriter().println("error: unknown option"); + return ERROR; + } + } + String name = getNextArgRequired(); + + IRemotelyProvisionedComponent binder = mInjector.getIrpcBinder(name); + RpcHardwareInfo info = binder.getHardwareInfo(); + MacedPublicKey[] emptyKeys = new MacedPublicKey[] {}; + byte[] csrBytes; + switch (info.versionNumber) { + case 1: + case 2: + DeviceInfo deviceInfo = new DeviceInfo(); + ProtectedData protectedData = new ProtectedData(); + byte[] eek = getEekChain(info.supportedEekCurve); + byte[] keysToSignMac = binder.generateCertificateRequest( + /*testMode=*/false, emptyKeys, eek, challenge, deviceInfo, protectedData); + csrBytes = composeCertificateRequestV1( + deviceInfo, challenge, protectedData, keysToSignMac); + break; + case 3: + csrBytes = binder.generateCertificateRequestV2(emptyKeys, challenge); + break; + default: + getErrPrintWriter().println("error: unsupported hwVersion: " + info.versionNumber); + return ERROR; + } + getOutPrintWriter().println(Base64.getEncoder().encodeToString(csrBytes)); + return SUCCESS; + } + + private byte[] getEekChain(int supportedEekCurve) { + switch (supportedEekCurve) { + case RpcHardwareInfo.CURVE_25519: + return Base64.getDecoder().decode(EEK_ED25519_BASE64); + case RpcHardwareInfo.CURVE_P256: + return Base64.getDecoder().decode(EEK_P256_BASE64); + default: + throw new IllegalArgumentException("unsupported EEK curve: " + supportedEekCurve); + } + } + + private byte[] composeCertificateRequestV1(DeviceInfo deviceInfo, byte[] challenge, + ProtectedData protectedData, byte[] keysToSignMac) throws CborException { + Array info = new Array() + .add(decode(deviceInfo.deviceInfo)) + .add(new Map()); + + // COSE_Signature with the hmac-sha256 algorithm and without a payload. + Array mac = new Array() + .add(new ByteString(encode( + new Map().put(new UnsignedInteger(1), new UnsignedInteger(5))))) + .add(new Map()) + .add(SimpleValue.NULL) + .add(new ByteString(keysToSignMac)); + + Array csr = new Array() + .add(info) + .add(new ByteString(challenge)) + .add(decode(protectedData.protectedData)) + .add(mac); + + return encode(csr); + } + + private byte[] encode(DataItem item) throws CborException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new CborEncoder(baos).encode(item); + return baos.toByteArray(); + } + + private DataItem decode(byte[] data) throws CborException { + ByteArrayInputStream bais = new ByteArrayInputStream(data); + return new CborDecoder(bais).decodeNext(); + } + + @VisibleForTesting + static class Injector { + String[] getIrpcNames() { + return ServiceManager.getDeclaredInstances(IRemotelyProvisionedComponent.DESCRIPTOR); + } + + IRemotelyProvisionedComponent getIrpcBinder(String name) { + String irpc = IRemotelyProvisionedComponent.DESCRIPTOR + "/" + name; + IRemotelyProvisionedComponent binder = + IRemotelyProvisionedComponent.Stub.asInterface( + ServiceManager.waitForDeclaredService(irpc)); + if (binder == null) { + throw new IllegalArgumentException("failed to find " + irpc); + } + return binder; + } + } +} diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index e17866922990..51872b339c40 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1229,10 +1229,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } + // Live wallpapers always are system wallpapers unless lock screen live wp is + // enabled. + which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM; mWallpaper.primaryColors = primaryColors; - // Live wallpapers always are system wallpapers. - which = FLAG_SYSTEM; // It's also the lock screen wallpaper when we don't have a bitmap in there. if (displayId == DEFAULT_DISPLAY) { final WallpaperData lockedWallpaper = mLockWallpaperMap.get(mWallpaper.userId); diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index be503fc61c4c..7d220df949e0 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -82,6 +82,7 @@ import static com.android.server.wm.Task.TAG_CLEANUP; import static com.android.server.wm.WindowContainer.POSITION_TOP; import android.Manifest; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; @@ -261,6 +262,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { /** Helper for {@link Task#fillTaskInfo}. */ final TaskInfoHelper mTaskInfoHelper = new TaskInfoHelper(); + final OpaqueActivityHelper mOpaqueActivityHelper = new OpaqueActivityHelper(); + private final ActivityTaskSupervisorHandler mHandler; final Looper mLooper; @@ -2906,6 +2909,38 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } } + /** The helper to get the top opaque activity of a container. */ + static class OpaqueActivityHelper implements Predicate<ActivityRecord> { + private ActivityRecord mStarting; + private boolean mIncludeInvisibleAndFinishing; + + ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) { + mIncludeInvisibleAndFinishing = true; + return container.getActivity(this, + true /* traverseTopToBottom */, null /* boundary */); + } + + ActivityRecord getVisibleOpaqueActivity(@NonNull WindowContainer<?> container, + @Nullable ActivityRecord starting) { + mStarting = starting; + mIncludeInvisibleAndFinishing = false; + final ActivityRecord opaque = container.getActivity(this, + true /* traverseTopToBottom */, null /* boundary */); + mStarting = null; + return opaque; + } + + @Override + public boolean test(ActivityRecord r) { + if (!mIncludeInvisibleAndFinishing && !r.visibleIgnoringKeyguard && r != mStarting) { + // Ignore invisible activities that are not the currently starting activity + // (about to be visible). + return false; + } + return r.occludesParent(mIncludeInvisibleAndFinishing /* includingFinishing */); + } + } + /** * Fills the info that needs to iterate all activities of task, such as the number of * non-finishing activities and collecting launch cookies. diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index d916a1be1a03..7ecc0839fe53 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -133,7 +133,7 @@ class BLASTSyncEngine { return mOrphanTransaction; } - private void onSurfacePlacement() { + private void tryFinish() { if (!mReady) return; ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s", mSyncId, mRootMembers); @@ -168,14 +168,13 @@ class BLASTSyncEngine { class CommitCallback implements Runnable { // Can run a second time if the action completes after the timeout. boolean ran = false; - public void onCommitted() { + public void onCommitted(SurfaceControl.Transaction t) { synchronized (mWm.mGlobalLock) { if (ran) { return; } mHandler.removeCallbacks(this); ran = true; - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); for (WindowContainer wc : wcAwaitingCommit) { wc.onSyncTransactionCommitted(t); } @@ -194,12 +193,12 @@ class BLASTSyncEngine { Slog.e(TAG, "WM sent Transaction to organized, but never received" + " commit callback. Application ANR likely to follow."); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - onCommitted(); - + onCommitted(merged); } }; CommitCallback callback = new CommitCallback(); - merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted); + merged.addTransactionCommittedListener(Runnable::run, + () -> callback.onCommitted(new SurfaceControl.Transaction())); mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady"); @@ -223,6 +222,12 @@ class BLASTSyncEngine { } }); } + // Notify idle listeners + for (int i = mOnIdleListeners.size() - 1; i >= 0; --i) { + // If an idle listener adds a sync, though, then stop notifying. + if (mActiveSyncs.size() > 0) break; + mOnIdleListeners.get(i).run(); + } } private void setReady(boolean ready) { @@ -281,6 +286,8 @@ class BLASTSyncEngine { */ private final ArrayList<PendingSyncSet> mPendingSyncSets = new ArrayList<>(); + private final ArrayList<Runnable> mOnIdleListeners = new ArrayList<>(); + BLASTSyncEngine(WindowManagerService wms) { this(wms, wms.mH); } @@ -379,10 +386,15 @@ class BLASTSyncEngine { void onSurfacePlacement() { // backwards since each state can remove itself if finished for (int i = mActiveSyncs.size() - 1; i >= 0; --i) { - mActiveSyncs.valueAt(i).onSurfacePlacement(); + mActiveSyncs.valueAt(i).tryFinish(); } } + /** Only use this for tests! */ + void tryFinishForTest(int syncId) { + getSyncSet(syncId).tryFinish(); + } + /** * Queues a sync operation onto this engine. It will wait until any current/prior sync-sets * have finished to run. This is needed right now because currently {@link BLASTSyncEngine} @@ -409,4 +421,8 @@ class BLASTSyncEngine { boolean hasPendingSyncSets() { return !mPendingSyncSets.isEmpty(); } + + void addOnIdleListener(Runnable onIdleListener) { + mOnIdleListeners.add(onIdleListener); + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index bad64d357b13..76d6951c438e 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1858,7 +1858,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return false; } if (mLastWallpaperVisible && r.windowsCanBeWallpaperTarget() - && mFixedRotationTransitionListener.mAnimatingRecents == null) { + && mFixedRotationTransitionListener.mAnimatingRecents == null + && !mTransitionController.isTransientLaunch(r)) { // Use normal rotation animation for orientation change of visible wallpaper if recents // animation is not running (it may be swiping to home). return false; diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index 3d006868c79c..35513707ebc5 100644 --- a/services/core/java/com/android/server/wm/Letterbox.java +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.view.SurfaceControl.HIDDEN; +import static android.window.TaskConstants.TASK_CHILD_LAYER_LETTERBOX_BACKGROUND; import android.content.Context; import android.graphics.Color; @@ -361,7 +362,8 @@ public class Letterbox { .setCallsite("LetterboxSurface.createSurface") .build(); - t.setLayer(mSurface, -1).setColorSpaceAgnostic(mSurface, true); + t.setLayer(mSurface, TASK_CHILD_LAYER_LETTERBOX_BACKGROUND) + .setColorSpaceAgnostic(mSurface, true); } void attachInput(WindowState win) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 5c33e6470024..99d3cc0737d1 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5652,7 +5652,7 @@ class Task extends TaskFragment { (deferred) -> { // Need to check again if deferred since the system might // be in a different state. - if (deferred && !canMoveTaskToBack(tr)) { + if (!isAttached() || (deferred && !canMoveTaskToBack(tr))) { Slog.e(TAG, "Failed to move task to back after saying we could: " + tr.mTaskId); transition.abort(); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 311b9a6d2876..3f7ab14d02be 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -102,8 +102,6 @@ import android.window.TaskFragmentOrganizerToken; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; -import com.android.internal.util.function.pooled.PooledLambda; -import com.android.internal.util.function.pooled.PooledPredicate; import com.android.server.am.HostingRecord; import com.android.server.pm.pkg.AndroidPackage; @@ -934,11 +932,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (!isAttached() || isForceHidden() || isForceTranslucent()) { return true; } - final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity, - PooledLambda.__(ActivityRecord.class), starting, false /* including*/); - final ActivityRecord opaque = getActivity(p); - p.recycle(); - return opaque == null; + // A TaskFragment isn't translucent if it has at least one visible activity that occludes + // this TaskFragment. + return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, + starting) == null; } /** @@ -951,25 +948,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { return true; } // Including finishing Activity if the TaskFragment is becoming invisible in the transition. - final boolean includingFinishing = !isVisibleRequested(); - final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity, - PooledLambda.__(ActivityRecord.class), null /* starting */, includingFinishing); - final ActivityRecord opaque = getActivity(p); - p.recycle(); - return opaque == null; - } - - private static boolean isOpaqueActivity(@NonNull ActivityRecord r, - @Nullable ActivityRecord starting, boolean includingFinishing) { - if (!r.visibleIgnoringKeyguard && r != starting) { - // Also ignore invisible activities that are not the currently starting - // activity (about to be visible). - return false; - } - - // TaskFragment isn't translucent if it has at least one fullscreen activity that is - // visible. - return r.occludesParent(includingFinishing); + return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null; } ActivityRecord getTopNonFinishingActivity() { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 76b0e7b82ba6..452bd6d7b347 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -104,7 +104,8 @@ import java.util.Objects; import java.util.function.Predicate; /** - * Represents a logical transition. + * Represents a logical transition. This keeps track of all the changes associated with a logical + * WM state -> state transition. * @see TransitionController */ class Transition implements BLASTSyncEngine.TransactionReadyListener { @@ -416,6 +417,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return mFinishTransaction; } + boolean isPending() { + return mState == STATE_PENDING; + } + boolean isCollecting() { return mState == STATE_COLLECTING || mState == STATE_STARTED; } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index cbb4fe2eaa21..0ae9c4c41972 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -51,6 +51,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.server.FgThread; @@ -88,6 +89,7 @@ class TransitionController { private WindowProcessController mTransitionPlayerProc; final ActivityTaskManagerService mAtm; + BLASTSyncEngine mSyncEngine; final RemotePlayer mRemotePlayer; SnapshotController mSnapshotController; @@ -121,6 +123,26 @@ class TransitionController { private final IBinder.DeathRecipient mTransitionPlayerDeath; + static class QueuedTransition { + final Transition mTransition; + final OnStartCollect mOnStartCollect; + final BLASTSyncEngine.SyncGroup mLegacySync; + + QueuedTransition(Transition transition, OnStartCollect onStartCollect) { + mTransition = transition; + mOnStartCollect = onStartCollect; + mLegacySync = null; + } + + QueuedTransition(BLASTSyncEngine.SyncGroup legacySync, OnStartCollect onStartCollect) { + mTransition = null; + mOnStartCollect = onStartCollect; + mLegacySync = legacySync; + } + } + + private final ArrayList<QueuedTransition> mQueuedTransitions = new ArrayList<>(); + /** The transition currently being constructed (collecting participants). */ private Transition mCollectingTransition = null; @@ -158,6 +180,14 @@ class TransitionController { mTransitionTracer = wms.mTransitionTracer; mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled; registerLegacyListener(wms.mActivityManagerAppTransitionNotifier); + setSyncEngine(wms.mSyncEngine); + } + + @VisibleForTesting + void setSyncEngine(BLASTSyncEngine syncEngine) { + mSyncEngine = syncEngine; + // Check the queue whenever the sync-engine becomes idle. + mSyncEngine.addOnIdleListener(this::tryStartCollectFromQueue); } private void detachPlayer() { @@ -195,7 +225,7 @@ class TransitionController { throw new IllegalStateException("Simultaneous transition collection not supported" + " yet. Use {@link #createPendingTransition} for explicit queueing."); } - Transition transit = new Transition(type, flags, this, mAtm.mWindowManager.mSyncEngine); + Transition transit = new Transition(type, flags, this, mSyncEngine); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", transit); moveToCollecting(transit); return transit; @@ -325,7 +355,7 @@ class TransitionController { /** @return {@code true} if a transition is running */ boolean inTransition() { // TODO(shell-transitions): eventually properly support multiple - return isCollecting() || isPlaying(); + return isCollecting() || isPlaying() || !mQueuedTransitions.isEmpty(); } /** @return {@code true} if a transition is running in a participant subtree of wc */ @@ -453,8 +483,7 @@ class TransitionController { // some frames before and after the display projection transaction is applied by the // remote player. That may cause some buffers to show in different rotation. So use // sync method to pause clients drawing until the projection transaction is applied. - mAtm.mWindowManager.mSyncEngine.setSyncMethod(displayTransition.getSyncId(), - BLASTSyncEngine.METHOD_BLAST); + mSyncEngine.setSyncMethod(displayTransition.getSyncId(), BLASTSyncEngine.METHOD_BLAST); } final Rect startBounds = displayChange.getStartAbsBounds(); final Rect endBounds = displayChange.getEndAbsBounds(); @@ -741,6 +770,31 @@ class TransitionController { mStateValidators.clear(); } + void tryStartCollectFromQueue() { + if (mQueuedTransitions.isEmpty()) return; + // Only need to try the next one since, even when transition can collect in parallel, + // they still need to serialize on readiness. + final QueuedTransition queued = mQueuedTransitions.get(0); + if (mCollectingTransition != null || mSyncEngine.hasActiveSync()) { + return; + } + mQueuedTransitions.remove(0); + // This needs to happen immediately to prevent another sync from claiming the syncset + // out-of-order (moveToCollecting calls startSyncSet) + if (queued.mTransition != null) { + moveToCollecting(queued.mTransition); + } else { + // legacy sync + mSyncEngine.startSyncSet(queued.mLegacySync); + } + // Post this so that the now-playing transition logic isn't interrupted. + mAtm.mH.post(() -> { + synchronized (mAtm.mGlobalLock) { + queued.mOnStartCollect.onCollectStarted(true /* deferred */); + } + }); + } + void moveToPlaying(Transition transition) { if (transition != mCollectingTransition) { throw new IllegalStateException("Trying to move non-collecting transition to playing"); @@ -749,6 +803,7 @@ 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. } void updateAnimatingState(SurfaceControl.Transaction t) { @@ -758,12 +813,12 @@ class TransitionController { t.setEarlyWakeupStart(); // Usually transitions put quite a load onto the system already (with all the things // happening in app), so pause task snapshot persisting to not increase the load. - mAtm.mWindowManager.mSnapshotController.setPause(true); + mSnapshotController.setPause(true); mAnimatingState = true; Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */); } else if (!animatingState && mAnimatingState) { t.setEarlyWakeupEnd(); - mAtm.mWindowManager.mSnapshotController.setPause(false); + mSnapshotController.setPause(false); mAnimatingState = false; Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */); } @@ -793,6 +848,7 @@ class TransitionController { transition.abort(); mCollectingTransition = null; mTransitionTracer.logState(transition); + // abort will call through the normal finish paths and thus check the queue. } /** @@ -874,7 +930,7 @@ class TransitionController { if (!mPlayingTransitions.isEmpty()) { state = LEGACY_STATE_RUNNING; } else if ((mCollectingTransition != null && mCollectingTransition.getLegacyIsReady()) - || mAtm.mWindowManager.mSyncEngine.hasPendingSyncSets()) { + || mSyncEngine.hasPendingSyncSets()) { // The transition may not be "ready", but we have a sync-transaction waiting to start. // Usually the pending transaction is for a transition, so assuming that is the case, // we can't be IDLE for test purposes. Ideally, we should have a STATE_COLLECTING. @@ -885,25 +941,43 @@ class TransitionController { } /** Returns {@code true} if it started collecting, {@code false} if it was queued. */ + private void queueTransition(Transition transit, OnStartCollect onStartCollect) { + mQueuedTransitions.add(new QueuedTransition(transit, onStartCollect)); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, + "Queueing transition: %s", transit); + } + + /** Returns {@code true} if it started collecting, {@code false} if it was queued. */ boolean startCollectOrQueue(Transition transit, OnStartCollect onStartCollect) { - if (mAtm.mWindowManager.mSyncEngine.hasActiveSync()) { + if (!mQueuedTransitions.isEmpty()) { + // Just add to queue since we already have a queue. + queueTransition(transit, onStartCollect); + return false; + } + if (mSyncEngine.hasActiveSync()) { if (!isCollecting()) { Slog.w(TAG, "Ongoing Sync outside of transition."); } + queueTransition(transit, onStartCollect); + return false; + } + moveToCollecting(transit); + onStartCollect.onCollectStarted(false /* deferred */); + return true; + } + + /** Returns {@code true} if it started collecting, {@code false} if it was queued. */ + boolean startLegacySyncOrQueue(BLASTSyncEngine.SyncGroup syncGroup, Runnable applySync) { + if (!mQueuedTransitions.isEmpty() || mSyncEngine.hasActiveSync()) { + // Just add to queue since we already have a queue. + mQueuedTransitions.add(new QueuedTransition(syncGroup, (d) -> applySync.run())); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, - "Queueing transition: %s", transit); - mAtm.mWindowManager.mSyncEngine.queueSyncSet( - // Make sure to collect immediately to prevent another transition - // from sneaking in before it. Note: moveToCollecting internally - // calls startSyncSet. - () -> moveToCollecting(transit), - () -> onStartCollect.onCollectStarted(true /* deferred */)); + "Queueing legacy sync-set: %s", syncGroup.mSyncId); return false; - } else { - moveToCollecting(transit); - onStartCollect.onCollectStarted(false /* deferred */); - return true; } + mSyncEngine.startSyncSet(syncGroup); + applySync.run(); + return true; } interface OnStartCollect { diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java index a4c931c17a66..6597d4c7f916 100644 --- a/services/core/java/com/android/server/wm/TransitionTracer.java +++ b/services/core/java/com/android/server/wm/TransitionTracer.java @@ -154,6 +154,7 @@ public class TransitionTracer { } outputStream.write(com.android.server.wm.shell.Transition.TYPE, transition.mType); + outputStream.write(com.android.server.wm.shell.Transition.FLAGS, transition.getFlags()); for (int i = 0; i < targets.size(); ++i) { final long changeToken = outputStream @@ -162,6 +163,7 @@ public class TransitionTracer { final Transition.ChangeInfo target = targets.get(i); final int mode = target.getTransitMode(target.mContainer); + final int flags = target.getChangeFlags(target.mContainer); final int layerId; if (target.mContainer.mSurfaceControl.isValid()) { layerId = target.mContainer.mSurfaceControl.getLayerId(); @@ -170,6 +172,7 @@ public class TransitionTracer { } outputStream.write(com.android.server.wm.shell.Target.MODE, mode); + outputStream.write(com.android.server.wm.shell.Target.FLAGS, flags); outputStream.write(com.android.server.wm.shell.Target.LAYER_ID, layerId); if (mActiveTracingEnabled) { diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 6c38c6fb7ac4..1ffee05d20ec 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -129,26 +129,10 @@ class WallpaperWindowToken extends WindowToken { } void updateWallpaperWindows(boolean visible) { - boolean changed = false; if (mVisibleRequested != visible) { ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b", token, visible); setVisibility(visible); - changed = true; - } - if (mTransitionController.isShellTransitionsEnabled()) { - // Apply legacy fixed rotation to wallpaper if it is becoming visible - if (!mTransitionController.useShellTransitionsRotation() && changed && visible) { - final WindowState wallpaperTarget = - mDisplayContent.mWallpaperController.getWallpaperTarget(); - if (wallpaperTarget != null && wallpaperTarget.mToken.hasFixedRotationTransform()) { - linkFixedRotationTransform(wallpaperTarget.mToken); - } - } - // If wallpaper is in transition, setVisible() will be called from commitVisibility() - // when finishing transition. Otherwise commitVisibility() is already called from above - // setVisibility(). - return; } final WindowState wallpaperTarget = @@ -172,6 +156,12 @@ class WallpaperWindowToken extends WindowToken { linkFixedRotationTransform(wallpaperTarget.mToken); } } + if (mTransitionController.isShellTransitionsEnabled()) { + // If wallpaper is in transition, setVisible() will be called from commitVisibility() + // when finishing transition. Otherwise commitVisibility() is already called from above + // setVisibility(). + return; + } setVisible(visible); } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index ee86b97e9404..cd42528ad79b 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -231,19 +231,26 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub */ final BLASTSyncEngine.SyncGroup syncGroup = prepareSyncWithOrganizer(callback); final int syncId = syncGroup.mSyncId; - if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) { - mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup); - applyTransaction(t, syncId, null /*transition*/, caller); - setSyncReady(syncId); + if (mTransitionController.isShellTransitionsEnabled()) { + mTransitionController.startLegacySyncOrQueue(syncGroup, () -> { + applyTransaction(t, syncId, null /*transition*/, caller); + setSyncReady(syncId); + }); } else { - // Because the BLAST engine only supports one sync at a time, queue the - // transaction. - mService.mWindowManager.mSyncEngine.queueSyncSet( - () -> mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup), - () -> { - applyTransaction(t, syncId, null /*transition*/, caller); - setSyncReady(syncId); - }); + if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) { + mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup); + applyTransaction(t, syncId, null /*transition*/, caller); + setSyncReady(syncId); + } else { + // Because the BLAST engine only supports one sync at a time, queue the + // transaction. + mService.mWindowManager.mSyncEngine.queueSyncSet( + () -> mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup), + () -> { + applyTransaction(t, syncId, null /*transition*/, caller); + setSyncReady(syncId); + }); + } } return syncId; } diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index 2584b86f53db..d9acf4182736 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -27,3 +27,4 @@ per-file com_android_server_security_* = file:/core/java/android/security/OWNERS per-file com_android_server_tv_* = file:/media/java/android/media/tv/OWNERS per-file com_android_server_vibrator_* = file:/services/core/java/com/android/server/vibrator/OWNERS per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com +per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp index 4898d95b8e4b..ad098b757eae 100644 --- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp +++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp @@ -233,44 +233,50 @@ static void nativeCloseUinput(JNIEnv* env, jobject thiz, jlong ptr) { // Native methods for VirtualDpad static bool nativeWriteDpadKeyEvent(JNIEnv* env, jobject thiz, jlong ptr, jint androidKeyCode, - jint action) { + jint action, jlong eventTimeNanos) { VirtualDpad* virtualDpad = reinterpret_cast<VirtualDpad*>(ptr); - return virtualDpad->writeDpadKeyEvent(androidKeyCode, action); + return virtualDpad->writeDpadKeyEvent(androidKeyCode, action, + std::chrono::nanoseconds(eventTimeNanos)); } // Native methods for VirtualKeyboard static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jlong ptr, jint androidKeyCode, - jint action) { + jint action, jlong eventTimeNanos) { VirtualKeyboard* virtualKeyboard = reinterpret_cast<VirtualKeyboard*>(ptr); - return virtualKeyboard->writeKeyEvent(androidKeyCode, action); + return virtualKeyboard->writeKeyEvent(androidKeyCode, action, + std::chrono::nanoseconds(eventTimeNanos)); } // Native methods for VirtualTouchscreen static bool nativeWriteTouchEvent(JNIEnv* env, jobject thiz, jlong ptr, jint pointerId, jint toolType, jint action, jfloat locationX, jfloat locationY, - jfloat pressure, jfloat majorAxisSize) { + jfloat pressure, jfloat majorAxisSize, jlong eventTimeNanos) { VirtualTouchscreen* virtualTouchscreen = reinterpret_cast<VirtualTouchscreen*>(ptr); return virtualTouchscreen->writeTouchEvent(pointerId, toolType, action, locationX, locationY, - pressure, majorAxisSize); + pressure, majorAxisSize, + std::chrono::nanoseconds(eventTimeNanos)); } // Native methods for VirtualMouse static bool nativeWriteButtonEvent(JNIEnv* env, jobject thiz, jlong ptr, jint buttonCode, - jint action) { + jint action, jlong eventTimeNanos) { VirtualMouse* virtualMouse = reinterpret_cast<VirtualMouse*>(ptr); - return virtualMouse->writeButtonEvent(buttonCode, action); + return virtualMouse->writeButtonEvent(buttonCode, action, + std::chrono::nanoseconds(eventTimeNanos)); } static bool nativeWriteRelativeEvent(JNIEnv* env, jobject thiz, jlong ptr, jfloat relativeX, - jfloat relativeY) { + jfloat relativeY, jlong eventTimeNanos) { VirtualMouse* virtualMouse = reinterpret_cast<VirtualMouse*>(ptr); - return virtualMouse->writeRelativeEvent(relativeX, relativeY); + return virtualMouse->writeRelativeEvent(relativeX, relativeY, + std::chrono::nanoseconds(eventTimeNanos)); } static bool nativeWriteScrollEvent(JNIEnv* env, jobject thiz, jlong ptr, jfloat xAxisMovement, - jfloat yAxisMovement) { + jfloat yAxisMovement, jlong eventTimeNanos) { VirtualMouse* virtualMouse = reinterpret_cast<VirtualMouse*>(ptr); - return virtualMouse->writeScrollEvent(xAxisMovement, yAxisMovement); + return virtualMouse->writeScrollEvent(xAxisMovement, yAxisMovement, + std::chrono::nanoseconds(eventTimeNanos)); } static JNINativeMethod methods[] = { @@ -283,12 +289,12 @@ static JNINativeMethod methods[] = { {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)J", (void*)nativeOpenUinputTouchscreen}, {"nativeCloseUinput", "(J)V", (void*)nativeCloseUinput}, - {"nativeWriteDpadKeyEvent", "(JII)Z", (void*)nativeWriteDpadKeyEvent}, - {"nativeWriteKeyEvent", "(JII)Z", (void*)nativeWriteKeyEvent}, - {"nativeWriteButtonEvent", "(JII)Z", (void*)nativeWriteButtonEvent}, - {"nativeWriteTouchEvent", "(JIIIFFFF)Z", (void*)nativeWriteTouchEvent}, - {"nativeWriteRelativeEvent", "(JFF)Z", (void*)nativeWriteRelativeEvent}, - {"nativeWriteScrollEvent", "(JFF)Z", (void*)nativeWriteScrollEvent}, + {"nativeWriteDpadKeyEvent", "(JIIJ)Z", (void*)nativeWriteDpadKeyEvent}, + {"nativeWriteKeyEvent", "(JIIJ)Z", (void*)nativeWriteKeyEvent}, + {"nativeWriteButtonEvent", "(JIIJ)Z", (void*)nativeWriteButtonEvent}, + {"nativeWriteTouchEvent", "(JIIIFFFFJ)Z", (void*)nativeWriteTouchEvent}, + {"nativeWriteRelativeEvent", "(JFFJ)Z", (void*)nativeWriteRelativeEvent}, + {"nativeWriteScrollEvent", "(JFFJ)Z", (void*)nativeWriteScrollEvent}, }; int register_android_server_companion_virtual_InputController(JNIEnv* env) { diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index f39de43b5076..0271727249b1 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -19,7 +19,6 @@ 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; @@ -36,7 +35,6 @@ import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; import java.util.Set; -import java.util.stream.Collectors; /** * Central session for a single getCredentials request. This class listens to the @@ -56,11 +54,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, super(context, sessionCallback, lock, userId, callingUid, request, callback, RequestInfo.TYPE_GET, callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp); - int numTypes = (request.getCredentialOptions().stream() - .map(CredentialOption::getType).collect( - Collectors.toSet())).size(); // Dedupe type strings - mRequestSessionMetric.collectGetFlowInitialMetricInfo(numTypes, - /*origin=*/request.getOrigin() != null); + mRequestSessionMetric.collectGetFlowInitialMetricInfo(request); } /** diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index 47b45ac1d53d..c07f06b332fb 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -50,6 +50,8 @@ public class MetricUtilities { 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; /** * This retrieves the uid of any package name, given a context and a component name for the @@ -87,6 +89,18 @@ public class MetricUtilities { } /** + * Given the current design, we can designate how the strings in the backend should appear. + * This helper method lets us cut strings for our class types. + * + * @param classtype the classtype string we want to cut to generate a key + * @param deltaFromEnd the starting point from the end of the string we wish to begin at + * @return the cut up string key we want to use for metric logs + */ + public static String generateMetricKey(String classtype, int deltaFromEnd) { + return classtype.substring(classtype.length() - deltaFromEnd); + } + + /** * A logging utility used primarily for the final phase of the current metric setup. * * @param finalPhaseMetric the coalesced data of the chosen provider @@ -158,7 +172,9 @@ public class MetricUtilities { } /** - * A logging utility used primarily for the candidate phase of the current metric setup. + * A logging utility used primarily for the candidate phase of the current metric setup. This + * will primarily focus on track 2, where the session id is associated with known providers, + * but NOT the calling app. * * @param providers a map with known providers and their held metric objects * @param emitSequenceId an emitted sequence id for the current session @@ -209,6 +225,7 @@ public class MetricUtilities { candidateActionEntryCountList[index] = metric.getActionEntryCount(); candidateAuthEntryCountList[index] = metric.getAuthenticationEntryCount(); candidateRemoteEntryCountList[index] = metric.getRemoteEntryCount(); + // frameworkExceptionList[index] = metric.getFrameworkException(); index++; } FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_CANDIDATE_PHASE_REPORTED, @@ -297,10 +314,11 @@ public class MetricUtilities { initialPhaseMetric.getCredentialServiceStartedTimeNanoseconds(), /* count_credential_request_classtypes */ initialPhaseMetric.getCountRequestClassType(), - // TODO(b/271135048) - add total count of request options - // TODO(b/271135048) - Uncomment once built past PWG review - - DEFAULT_REPEATED_STR, - DEFAULT_REPEATED_INT_32, + /* request_unique_classtypes */ + initialPhaseMetric.getUniqueRequestStrings(), + /* per_classtype_counts */ + initialPhaseMetric.getUniqueRequestCounts(), + /* origin_specified */ initialPhaseMetric.isOriginSpecified() ); } catch (Exception e) { diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java index 1c3d213c8072..441c87b1569a 100644 --- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java @@ -59,8 +59,7 @@ public class PrepareGetRequestSession extends GetRequestSession { int numTypes = (request.getCredentialOptions().stream() .map(CredentialOption::getType).collect( Collectors.toSet())).size(); // Dedupe type strings - mRequestSessionMetric.collectGetFlowInitialMetricInfo(numTypes, - /*origin=*/request.getOrigin() != null); + mRequestSessionMetric.collectGetFlowInitialMetricInfo(request); mPrepareGetCredentialCallback = prepareGetCredentialCallback; } 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 0210b14943db..0ecd9cc79e48 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java @@ -16,6 +16,11 @@ package com.android.server.credentials.metrics; +import android.util.Log; + +import java.util.LinkedHashMap; +import java.util.Map; + /** * This handles metrics collected prior to any remote calls to providers. * Some types are redundant across these metric collectors, but that has debug use-cases as @@ -32,7 +37,6 @@ public class InitialPhaseMetric { private int mCallerUid = -1; // The session id to unite multiple atom emits, default to -1 private int mSessionId = -1; - private int mCountRequestClassType = -1; // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a // reference point. @@ -46,6 +50,9 @@ public class InitialPhaseMetric { // TODO(b/271135048) - Emit once metrics approved private boolean mOriginSpecified = false; + // Stores the deduped request information, particularly {"req":5}. + private Map<String, Integer> mRequestCounts = new LinkedHashMap<>(); + public InitialPhaseMetric() { } @@ -55,8 +62,8 @@ public class InitialPhaseMetric { /* -- Direct Latency Utility -- */ public int getServiceStartToQueryLatencyMicroseconds() { - return (int) ((this.mCredentialServiceStartedTimeNanoseconds - - this.mCredentialServiceBeginQueryTimeNanoseconds) / 1000); + return (int) ((mCredentialServiceStartedTimeNanoseconds + - mCredentialServiceBeginQueryTimeNanoseconds) / 1000); } /* -- Timestamps -- */ @@ -64,7 +71,7 @@ public class InitialPhaseMetric { public void setCredentialServiceStartedTimeNanoseconds( long credentialServiceStartedTimeNanoseconds ) { - this.mCredentialServiceStartedTimeNanoseconds = credentialServiceStartedTimeNanoseconds; + mCredentialServiceStartedTimeNanoseconds = credentialServiceStartedTimeNanoseconds; } public void setCredentialServiceBeginQueryTimeNanoseconds( @@ -112,14 +119,12 @@ public class InitialPhaseMetric { /* ------ Count Request Class Types ------ */ - public void setCountRequestClassType(int countRequestClassType) { - mCountRequestClassType = countRequestClassType; - } - public int getCountRequestClassType() { - return mCountRequestClassType; + return mRequestCounts.size(); } + /* ------ Origin Specified ------ */ + public void setOriginSpecified(boolean originSpecified) { mOriginSpecified = originSpecified; } @@ -127,4 +132,34 @@ public class InitialPhaseMetric { public boolean isOriginSpecified() { return mOriginSpecified; } + + /* ------ Unique Request Counts Map Information ------ */ + + public void setRequestCounts(Map<String, Integer> requestCounts) { + mRequestCounts = requestCounts; + } + + /** + * Reruns the unique, deduped, request classtypes for logging. + * @return a string array for deduped classtypes + */ + public String[] getUniqueRequestStrings() { + if (mRequestCounts.isEmpty()) { + Log.w(TAG, "There are no unique string request types collected"); + } + String[] result = new String[mRequestCounts.keySet().size()]; + mRequestCounts.keySet().toArray(result); + return result; + } + + /** + * Reruns the unique, deduped, request classtype counts for logging. + * @return a string array for deduped classtype counts + */ + public int[] getUniqueRequestCounts() { + if (mRequestCounts.isEmpty()) { + Log.w(TAG, "There are no unique string request type counts collected"); + } + return mRequestCounts.values().stream().mapToInt(Integer::intValue).toArray(); + } } 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 10bf56c853f5..18e04df9416a 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -16,10 +16,12 @@ package com.android.server.credentials.metrics; +import static com.android.server.credentials.MetricUtilities.DELTA_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 android.annotation.NonNull; +import android.credentials.GetCredentialRequest; import android.credentials.ui.UserSelectionDialogResult; import android.os.IBinder; import android.util.Log; @@ -27,6 +29,7 @@ import android.util.Log; import com.android.server.credentials.ProviderSession; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -48,7 +51,6 @@ public class RequestSessionMetric { protected final ChosenProviderFinalPhaseMetric mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric(); // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4) - @NonNull protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>(); public RequestSessionMetric() { @@ -161,16 +163,32 @@ public class RequestSessionMetric { } } + // Used by get flows to generate the unique request count maps + private Map<String, Integer> getRequestCountMap(GetCredentialRequest request) { + 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); + }); + } catch (Exception e) { + Log.w(TAG, "Unexpected error during get request metric logging: " + e); + } + return uniqueRequestCounts; + } + /** * Collects initializations for Get flow metrics. * - * @param requestClassTypeCount the number of class types in the request - * @param origin indicates if an origin was passed in or not + * @param request the get credential request containing information to parse for metrics */ - public void collectGetFlowInitialMetricInfo(int requestClassTypeCount, boolean origin) { + public void collectGetFlowInitialMetricInfo(GetCredentialRequest request) { try { - mInitialPhaseMetric.setCountRequestClassType(requestClassTypeCount); - mInitialPhaseMetric.setOriginSpecified(origin); + mInitialPhaseMetric.setOriginSpecified(request.getOrigin() != null); + mInitialPhaseMetric.setRequestCounts(getRequestCountMap(request)); } catch (Exception e) { Log.w(TAG, "Unexpected error during metric logging: " + e); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7e5d5aae06e4..4d739d2c3685 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -160,6 +160,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; +import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED; import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED; import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY; @@ -533,7 +534,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -1186,9 +1186,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Resume logging if all remaining users are affiliated. maybeResumeDeviceWideLoggingLocked(); } - if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) { - mDevicePolicyEngine.handleUserRemoved(userHandle); - } + } + if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) { + mDevicePolicyEngine.handleUserRemoved(userHandle); } } else if (Intent.ACTION_USER_STARTED.equals(action)) { sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_STARTED, userHandle); @@ -4157,8 +4157,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_ACTIVE_ADMIN); enforceUserUnlocked(userHandle); + ActiveAdmin admin; synchronized (getLockObject()) { - ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle); + admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle); if (admin == null) { return; } @@ -4169,14 +4170,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + adminReceiver); return; } - mInjector.binderWithCleanCallingIdentity(() -> removeActiveAdminLocked(adminReceiver, userHandle)); - if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) { - mDevicePolicyEngine.removePoliciesForAdmin( - EnforcingAdmin.createEnterpriseEnforcingAdmin( - adminReceiver, userHandle, admin)); - } + } + if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) { + mDevicePolicyEngine.removePoliciesForAdmin( + EnforcingAdmin.createEnterpriseEnforcingAdmin( + adminReceiver, userHandle, admin)); } } @@ -13804,8 +13804,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { admin, new BooleanPolicyValue(hidden), userId); - Boolean resolvedPolicy = mDevicePolicyEngine.getResolvedPolicy( - PolicyDefinition.APPLICATION_HIDDEN(packageName), userId); result = mInjector.binderWithCleanCallingIdentity(() -> { try { // This is a best effort to continue returning the same value that was @@ -16549,11 +16547,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { hasCallingOrSelfPermission(permission.NOTIFY_PENDING_SYSTEM_UPDATE), "Only the system update service can broadcast update information"); - if (UserHandle.getCallingUserId() != UserHandle.USER_SYSTEM) { - Slogf.w(LOG_TAG, "Only the system update service in the system user can broadcast " - + "update information."); - return; - } + mInjector.binderWithCleanCallingIdentity(() -> { + if (!mUserManager.getUserInfo(UserHandle.getCallingUserId()).isMain()) { + Slogf.w(LOG_TAG, "Only the system update service in the main user can broadcast " + + "update information."); + return; + } + }); if (!mOwners.saveSystemUpdateInfo(info)) { // Pending system update hasn't changed, don't send duplicate notification. @@ -16661,8 +16661,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforcePermissionGrantStateOnFinancedDevice(packageName, permission); } } + EnforcingAdmin enforcingAdmin; if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( + enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( admin, MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, callerPackage, @@ -16686,17 +16687,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { callback.sendResult(null); return; } - // TODO(b/266924257): decide how to handle the internal state if the package doesn't - // exist, or the permission isn't requested by the app, because we could end up with - // inconsistent state between the policy engine and package manager. Also a package - // might get removed or has it's permission updated after we've set the policy. - mDevicePolicyEngine.setLocalPolicy( - PolicyDefinition.PERMISSION_GRANT(packageName, permission), - enforcingAdmin, - new IntegerPolicyValue(grantState), - caller.getUserId()); - // TODO: update javadoc to reflect that callback no longer return success/failure - callback.sendResult(Bundle.EMPTY); } else { Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller) @@ -16704,51 +16694,81 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT))); synchronized (getLockObject()) { - long ident = mInjector.binderClearCallingIdentity(); - try { - boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) - >= android.os.Build.VERSION_CODES.Q; - if (!isPostQAdmin) { - // Legacy admins assume that they cannot control pre-M apps - if (getTargetSdk(packageName, caller.getUserId()) - < android.os.Build.VERSION_CODES.M) { + long ident = mInjector.binderClearCallingIdentity(); + try { + boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) + >= android.os.Build.VERSION_CODES.Q; + if (!isPostQAdmin) { + // Legacy admins assume that they cannot control pre-M apps + if (getTargetSdk(packageName, caller.getUserId()) + < android.os.Build.VERSION_CODES.M) { + callback.sendResult(null); + return; + } + } + if (!isRuntimePermission(permission)) { callback.sendResult(null); return; } - } - if (!isRuntimePermission(permission)) { + } catch (SecurityException e) { + Slogf.e(LOG_TAG, "Could not set permission grant state", e); callback.sendResult(null); - return; + } finally { + mInjector.binderRestoreCallingIdentity(ident); } - if (grantState == PERMISSION_GRANT_STATE_GRANTED - || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED - || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { - AdminPermissionControlParams permissionParams = - new AdminPermissionControlParams(packageName, permission, - grantState, - canAdminGrantSensorsPermissions()); - mInjector.getPermissionControllerManager(caller.getUserHandle()) - .setRuntimePermissionGrantStateByDeviceAdmin( - caller.getPackageName(), - permissionParams, mContext.getMainExecutor(), - (permissionWasSet) -> { - if (isPostQAdmin && !permissionWasSet) { - callback.sendResult(null); - return; - } - - DevicePolicyEventLogger - .createEvent(DevicePolicyEnums - .SET_PERMISSION_GRANT_STATE) - .setAdmin(caller.getPackageName()) - .setStrings(permission) - .setInt(grantState) - .setBoolean( - /* isDelegate */ isCallerDelegate(caller)) - .write(); - - callback.sendResult(Bundle.EMPTY); - }); + } + } + // TODO(b/278710449): enable when we stop policy enforecer callback from blocking the main + // thread + if (false) { + // TODO(b/266924257): decide how to handle the internal state if the package doesn't + // exist, or the permission isn't requested by the app, because we could end up with + // inconsistent state between the policy engine and package manager. Also a package + // might get removed or has it's permission updated after we've set the policy. + if (grantState == PERMISSION_GRANT_STATE_DEFAULT) { + mDevicePolicyEngine.removeLocalPolicy( + PolicyDefinition.PERMISSION_GRANT(packageName, permission), + enforcingAdmin, + caller.getUserId()); + } else { + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.PERMISSION_GRANT(packageName, permission), + enforcingAdmin, + new IntegerPolicyValue(grantState), + caller.getUserId()); + } + int newState = mInjector.binderWithCleanCallingIdentity(() -> + getPermissionGrantStateForUser( + packageName, permission, caller, caller.getUserId())); + if (newState == grantState) { + callback.sendResult(Bundle.EMPTY); + } else { + callback.sendResult(null); + } + } else { + synchronized (getLockObject()) { + long ident = mInjector.binderClearCallingIdentity(); + try { + boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) + >= android.os.Build.VERSION_CODES.Q; + if (grantState == PERMISSION_GRANT_STATE_GRANTED + || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED + || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { + AdminPermissionControlParams permissionParams = + new AdminPermissionControlParams(packageName, permission, + grantState, + canAdminGrantSensorsPermissions()); + mInjector.getPermissionControllerManager(caller.getUserHandle()) + .setRuntimePermissionGrantStateByDeviceAdmin( + caller.getPackageName(), + permissionParams, mContext.getMainExecutor(), + (permissionWasSet) -> { + if (isPostQAdmin && !permissionWasSet) { + callback.sendResult(null); + return; + } + callback.sendResult(Bundle.EMPTY); + }); } } catch (SecurityException e) { Slogf.e(LOG_TAG, "Could not set permission grant state", e); @@ -16759,6 +16779,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } + DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_PERMISSION_GRANT_STATE) + .setAdmin(caller.getPackageName()) + .setStrings(permission) + .setInt(grantState) + .setBoolean(/* isDelegate */ isCallerDelegate(caller)) + .write(); } private static final List<String> SENSOR_PERMISSIONS = new ArrayList<>(); @@ -16822,10 +16848,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (isFinancedDeviceOwner(caller)) { enforcePermissionGrantStateOnFinancedDevice(packageName, permission); } - return mInjector.binderWithCleanCallingIdentity(() -> { - return getPermissionGrantStateForUser( - packageName, permission, caller, caller.getUserId()); - }); + return mInjector.binderWithCleanCallingIdentity(() -> getPermissionGrantStateForUser( + packageName, permission, caller, caller.getUserId())); } } @@ -18952,7 +18976,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { admin, MANAGE_DEVICE_POLICY_RESET_PASSWORD, caller.getPackageName(), - UserHandle.USER_ALL); + userId); Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin( PolicyDefinition.RESET_PASSWORD_TOKEN, enforcingAdmin, @@ -19016,7 +19040,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { admin, MANAGE_DEVICE_POLICY_RESET_PASSWORD, caller.getPackageName(), - UserHandle.USER_ALL); + userId); Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin( PolicyDefinition.RESET_PASSWORD_TOKEN, enforcingAdmin, @@ -19062,7 +19086,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { admin, MANAGE_DEVICE_POLICY_RESET_PASSWORD, caller.getPackageName(), - UserHandle.USER_ALL); + userId); Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin( PolicyDefinition.RESET_PASSWORD_TOKEN, enforcingAdmin, @@ -19114,7 +19138,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { admin, MANAGE_DEVICE_POLICY_RESET_PASSWORD, caller.getPackageName(), - UserHandle.USER_ALL); + userId); Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin( PolicyDefinition.RESET_PASSWORD_TOKEN, enforcingAdmin, @@ -19140,10 +19164,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (result) { - DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN) - .setAdmin(caller.getComponentName()) - .write(); + if (isPermissionCheckFlagEnabled()) { + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN) + .setAdmin(callerPackageName) + .write(); + } else { + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN) + .setAdmin(caller.getComponentName()) + .write(); + } } return result; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index d65d366e4476..12a8a75317e5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -84,6 +84,7 @@ final class PolicyEnforcerCallbacks { ? DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT : grantState; + // TODO(b/278710449): stop blocking in the main thread BlockingCallback callback = new BlockingCallback(); // TODO: remove canAdminGrantSensorPermissions once we expose a new method in // permissionController that doesn't need it. diff --git a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java new file mode 100644 index 000000000000..77c339646400 --- /dev/null +++ b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java @@ -0,0 +1,244 @@ +/* + * 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.security.rkp; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.security.keymint.DeviceInfo; +import android.hardware.security.keymint.IRemotelyProvisionedComponent; +import android.hardware.security.keymint.MacedPublicKey; +import android.hardware.security.keymint.ProtectedData; +import android.hardware.security.keymint.RpcHardwareInfo; +import android.os.Binder; +import android.os.FileUtils; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.Arrays; +import java.util.Base64; +import java.util.Map; + +@RunWith(AndroidJUnit4.class) +public class RemoteProvisioningShellCommandTest { + + private static class Injector extends RemoteProvisioningShellCommand.Injector { + + private final Map<String, IRemotelyProvisionedComponent> mIrpcs; + + Injector(Map irpcs) { + mIrpcs = irpcs; + } + + @Override + String[] getIrpcNames() { + return mIrpcs.keySet().toArray(new String[0]); + } + + @Override + IRemotelyProvisionedComponent getIrpcBinder(String name) { + IRemotelyProvisionedComponent irpc = mIrpcs.get(name); + if (irpc == null) { + throw new IllegalArgumentException("failed to find " + irpc); + } + return irpc; + } + } + + private static class CommandResult { + private int mCode; + private String mOut; + private String mErr; + + CommandResult(int code, String out, String err) { + mCode = code; + mOut = out; + mErr = err; + } + + int getCode() { + return mCode; + } + + String getOut() { + return mOut; + } + + String getErr() { + return mErr; + } + } + + private static CommandResult exec( + RemoteProvisioningShellCommand cmd, String[] args) throws Exception { + File in = File.createTempFile("rpsct_in_", null); + File out = File.createTempFile("rpsct_out_", null); + File err = File.createTempFile("rpsct_err_", null); + int code = cmd.exec( + new Binder(), + new FileInputStream(in).getFD(), + new FileOutputStream(out).getFD(), + new FileOutputStream(err).getFD(), + args); + return new CommandResult( + code, FileUtils.readTextFile(out, 0, null), FileUtils.readTextFile(err, 0, null)); + } + + @Test + public void list_zeroInstances() throws Exception { + RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( + new Injector(Map.of())); + CommandResult res = exec(cmd, new String[] {"list"}); + assertThat(res.getErr()).isEmpty(); + assertThat(res.getCode()).isEqualTo(0); + assertThat(res.getOut()).isEmpty(); + } + + @Test + public void list_oneInstances() throws Exception { + RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( + new Injector(Map.of("default", mock(IRemotelyProvisionedComponent.class)))); + CommandResult res = exec(cmd, new String[] {"list"}); + assertThat(res.getErr()).isEmpty(); + assertThat(res.getCode()).isEqualTo(0); + assertThat(Arrays.asList(res.getOut().split("\n"))).containsExactly("default"); + } + + @Test + public void list_twoInstances() throws Exception { + RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( + new Injector(Map.of( + "default", mock(IRemotelyProvisionedComponent.class), + "strongbox", mock(IRemotelyProvisionedComponent.class)))); + CommandResult res = exec(cmd, new String[] {"list"}); + assertThat(res.getErr()).isEmpty(); + assertThat(res.getCode()).isEqualTo(0); + assertThat(Arrays.asList(res.getOut().split("\n"))).containsExactly("default", "strongbox"); + } + + @Test + public void csr_hwVersion1_withChallenge() throws Exception { + IRemotelyProvisionedComponent defaultMock = mock(IRemotelyProvisionedComponent.class); + RpcHardwareInfo defaultInfo = new RpcHardwareInfo(); + defaultInfo.versionNumber = 1; + defaultInfo.supportedEekCurve = RpcHardwareInfo.CURVE_25519; + when(defaultMock.getHardwareInfo()).thenReturn(defaultInfo); + doAnswer(invocation -> { + ((DeviceInfo) invocation.getArgument(4)).deviceInfo = new byte[] {0x00}; + ((ProtectedData) invocation.getArgument(5)).protectedData = new byte[] {0x00}; + return new byte[] {0x77, 0x77, 0x77, 0x77}; + }).when(defaultMock).generateCertificateRequest( + anyBoolean(), any(), any(), any(), any(), any()); + + RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( + new Injector(Map.of("default", defaultMock))); + CommandResult res = exec(cmd, new String[] { + "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"}); + verify(defaultMock).generateCertificateRequest( + /*test_mode=*/eq(false), + eq(new MacedPublicKey[0]), + eq(Base64.getDecoder().decode(RemoteProvisioningShellCommand.EEK_ED25519_BASE64)), + eq(new byte[] { + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74}), + any(DeviceInfo.class), + any(ProtectedData.class)); + assertThat(res.getErr()).isEmpty(); + assertThat(res.getCode()).isEqualTo(0); + } + + @Test + public void csr_hwVersion2_withChallenge() throws Exception { + IRemotelyProvisionedComponent defaultMock = mock(IRemotelyProvisionedComponent.class); + RpcHardwareInfo defaultInfo = new RpcHardwareInfo(); + defaultInfo.versionNumber = 2; + defaultInfo.supportedEekCurve = RpcHardwareInfo.CURVE_P256; + when(defaultMock.getHardwareInfo()).thenReturn(defaultInfo); + doAnswer(invocation -> { + ((DeviceInfo) invocation.getArgument(4)).deviceInfo = new byte[] {0x00}; + ((ProtectedData) invocation.getArgument(5)).protectedData = new byte[] {0x00}; + return new byte[] {0x77, 0x77, 0x77, 0x77}; + }).when(defaultMock).generateCertificateRequest( + anyBoolean(), any(), any(), any(), any(), any()); + + RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( + new Injector(Map.of("default", defaultMock))); + CommandResult res = exec(cmd, new String[] { + "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"}); + verify(defaultMock).generateCertificateRequest( + /*test_mode=*/eq(false), + eq(new MacedPublicKey[0]), + eq(Base64.getDecoder().decode(RemoteProvisioningShellCommand.EEK_P256_BASE64)), + eq(new byte[] { + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74}), + any(DeviceInfo.class), + any(ProtectedData.class)); + assertThat(res.getErr()).isEmpty(); + assertThat(res.getCode()).isEqualTo(0); + } + + @Test + public void csr_hwVersion3_withoutChallenge() throws Exception { + IRemotelyProvisionedComponent defaultMock = mock(IRemotelyProvisionedComponent.class); + RpcHardwareInfo defaultInfo = new RpcHardwareInfo(); + defaultInfo.versionNumber = 3; + when(defaultMock.getHardwareInfo()).thenReturn(defaultInfo); + when(defaultMock.generateCertificateRequestV2(any(), any())) + .thenReturn(new byte[] {0x68, 0x65, 0x6c, 0x6c, 0x6f}); + + RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( + new Injector(Map.of("default", defaultMock))); + CommandResult res = exec(cmd, new String[] {"csr", "default"}); + verify(defaultMock).generateCertificateRequestV2(new MacedPublicKey[0], new byte[0]); + assertThat(res.getErr()).isEmpty(); + assertThat(res.getCode()).isEqualTo(0); + assertThat(res.getOut()).isEqualTo("aGVsbG8=\n"); + } + + @Test + public void csr_hwVersion3_withChallenge() throws Exception { + IRemotelyProvisionedComponent defaultMock = mock(IRemotelyProvisionedComponent.class); + RpcHardwareInfo defaultInfo = new RpcHardwareInfo(); + defaultInfo.versionNumber = 3; + when(defaultMock.getHardwareInfo()).thenReturn(defaultInfo); + when(defaultMock.generateCertificateRequestV2(any(), any())) + .thenReturn(new byte[] {0x68, 0x69}); + + RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( + new Injector(Map.of("default", defaultMock))); + CommandResult res = exec(cmd, new String[] {"csr", "--challenge", "dHJpYWw=", "default"}); + verify(defaultMock).generateCertificateRequestV2( + new MacedPublicKey[0], new byte[] {0x74, 0x72, 0x69, 0x61, 0x6c}); + assertThat(res.getErr()).isEmpty(); + assertThat(res.getCode()).isEqualTo(0); + assertThat(res.getOut()).isEqualTo("aGk=\n"); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java index ca857f121624..c4aa0bbc24b5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -445,7 +445,7 @@ public final class DisplayPowerController2Test { } @Test - public void testDisplayBrightnessFollowersRemoval() { + public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() { DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController( @@ -520,6 +520,78 @@ public final class DisplayPowerController2Test { } @Test + public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() { + DisplayPowerControllerHolder followerHolder = + createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); + DisplayPowerControllerHolder secondFollowerHolder = + createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID, + SECOND_FOLLOWER_UNIQUE_DISPLAY_ID); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor = + ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class); + verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture()); + BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue(); + + // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for + // it to return to. + listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class); + verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture()); + BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue(); + listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class); + verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture()); + BrightnessSetting.BrightnessSettingListener secondFollowerListener = + listenerCaptor.getValue(); + final float initialFollowerBrightness = 0.3f; + when(followerHolder.brightnessSetting.getBrightness()).thenReturn( + initialFollowerBrightness); + when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn( + initialFollowerBrightness); + followerListener.onBrightnessChanged(initialFollowerBrightness); + secondFollowerListener.onBrightnessChanged(initialFollowerBrightness); + advanceTime(1); + verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), + anyFloat(), anyFloat()); + verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), + anyFloat(), anyFloat()); + + mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc); + mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc); + clearInvocations(followerHolder.animator, secondFollowerHolder.animator); + + // Validate both followers are correctly registered and receiving brightness updates + float brightness = 0.6f; + float nits = 600; + when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits); + when(followerHolder.automaticBrightnessController.convertToFloatScale(nits)) + .thenReturn(brightness); + when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits)) + .thenReturn(brightness); + when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness); + listener.onBrightnessChanged(brightness); + advanceTime(1); // Send messages, run updatePowerState + verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat()); + verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat()); + verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat()); + + clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator); + + // Stop the lead DPC and validate that the followers go back to their original brightness. + mHolder.dpc.stop(); + advanceTime(1); + verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), + anyFloat(), anyFloat()); + verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), + anyFloat(), anyFloat()); + clearInvocations(followerHolder.animator, secondFollowerHolder.animator); + } + + @Test public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() { // We should still set screen state for the default display DisplayPowerRequest dpr = new DisplayPowerRequest(); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java index 0b97c5cb6ca1..415adbbac91e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -91,7 +91,7 @@ public final class DisplayPowerControllerTest { private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY; private static final String UNIQUE_ID = "unique_id_test123"; private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1; - private static final String FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_456"; + private static final String FOLLOWER_UNIQUE_ID = "unique_id_456"; private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1; private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789"; private static final float PROX_SENSOR_MAX_RANGE = 5; @@ -279,7 +279,7 @@ public final class DisplayPowerControllerTest { @Test public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() { DisplayPowerControllerHolder followerDpc = - createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID); + createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); // send a display power request @@ -298,7 +298,7 @@ public final class DisplayPowerControllerTest { @Test public void testDisplayBrightnessFollowers_BothDpcsSupportNits() { DisplayPowerControllerHolder followerDpc = - createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID); + createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); DisplayPowerRequest dpr = new DisplayPowerRequest(); mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); @@ -344,7 +344,7 @@ public final class DisplayPowerControllerTest { @Test public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() { DisplayPowerControllerHolder followerDpc = - createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID); + createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); DisplayPowerRequest dpr = new DisplayPowerRequest(); mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); @@ -372,7 +372,7 @@ public final class DisplayPowerControllerTest { @Test public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() { DisplayPowerControllerHolder followerDpc = - createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID); + createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); DisplayPowerRequest dpr = new DisplayPowerRequest(); mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); @@ -398,7 +398,7 @@ public final class DisplayPowerControllerTest { @Test public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() { DisplayPowerControllerHolder followerDpc = - createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID); + createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); DisplayPowerRequest dpr = new DisplayPowerRequest(); mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); @@ -449,9 +449,9 @@ public final class DisplayPowerControllerTest { } @Test - public void testDisplayBrightnessFollowersRemoval() { + public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() { DisplayPowerControllerHolder followerHolder = - createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID); + createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); DisplayPowerControllerHolder secondFollowerHolder = createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID); @@ -525,6 +525,78 @@ public final class DisplayPowerControllerTest { } @Test + public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() { + DisplayPowerControllerHolder followerHolder = + createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); + DisplayPowerControllerHolder secondFollowerHolder = + createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID, + SECOND_FOLLOWER_UNIQUE_DISPLAY_ID); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor = + ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class); + verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture()); + BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue(); + + // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for + // it to return to. + listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class); + verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture()); + BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue(); + listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class); + verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture()); + BrightnessSetting.BrightnessSettingListener secondFollowerListener = + listenerCaptor.getValue(); + final float initialFollowerBrightness = 0.3f; + when(followerHolder.brightnessSetting.getBrightness()).thenReturn( + initialFollowerBrightness); + when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn( + initialFollowerBrightness); + followerListener.onBrightnessChanged(initialFollowerBrightness); + secondFollowerListener.onBrightnessChanged(initialFollowerBrightness); + advanceTime(1); + verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), + anyFloat(), anyFloat()); + verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), + anyFloat(), anyFloat()); + + mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc); + mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc); + clearInvocations(followerHolder.animator, secondFollowerHolder.animator); + + // Validate both followers are correctly registered and receiving brightness updates + float brightness = 0.6f; + float nits = 600; + when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits); + when(followerHolder.automaticBrightnessController.convertToFloatScale(nits)) + .thenReturn(brightness); + when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits)) + .thenReturn(brightness); + when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness); + listener.onBrightnessChanged(brightness); + advanceTime(1); // Send messages, run updatePowerState + verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat()); + verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat()); + verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat()); + + clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator); + + // Stop the lead DPC and validate that the followers go back to their original brightness. + mHolder.dpc.stop(); + advanceTime(1); + verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), + anyFloat(), anyFloat()); + verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), + anyFloat(), anyFloat()); + clearInvocations(followerHolder.animator, secondFollowerHolder.animator); + } + + @Test public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() { // We should still set screen state for the default display DisplayPowerRequest dpr = new DisplayPowerRequest(); diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java index a14073006c31..35d4ffdd94d0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java @@ -145,7 +145,7 @@ public class RollbackPackageHealthObserverTest { observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1)); // non-native crash for the package - assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_60, observer.onHealthCheckFailed(testFailedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); // non-native crash for a different package diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/servicestests/res/xml/irq_device_map_3.xml index 7e2529aa0090..fd55428c48df 100644 --- a/services/tests/servicestests/res/xml/irq_device_map_3.xml +++ b/services/tests/servicestests/res/xml/irq_device_map_3.xml @@ -26,4 +26,10 @@ <device name="test.sound_trigger.device"> <subsystem>Sound_trigger</subsystem> </device> + <device name="test.cellular_data.device"> + <subsystem>Cellular_data</subsystem> + </device> + <device name="test.sensor.device"> + <subsystem>Sensor</subsystem> + </device> </irq-device-map>
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java index f1ad577fde88..a01c7bdd5144 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java @@ -200,7 +200,7 @@ public class FullScreenMagnificationControllerTest { assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0)); assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_1)); - verify(mMockThumbnail, times(2)).hideThumbNail(); + verify(mMockThumbnail, times(2)).hideThumbnail(); } @Test @@ -538,7 +538,10 @@ public class FullScreenMagnificationControllerTest { mConfigCaptor.capture()); assertConfigEquals(config, mConfigCaptor.getValue()); - verify(mMockThumbnail).setThumbNailBounds(any(), anyFloat(), anyFloat(), anyFloat()); + // The first time is triggered when the thumbnail is just created. + // The second time is triggered when the magnification region changed. + verify(mMockThumbnail, times(2)).setThumbnailBounds( + any(), anyFloat(), anyFloat(), anyFloat()); } @Test @@ -909,7 +912,7 @@ public class FullScreenMagnificationControllerTest { verifyNoMoreInteractions(mMockWindowManager); verify(mMockThumbnail) - .updateThumbNail(eq(scale), eq(startCenter.x), eq(startCenter.y)); + .updateThumbnail(eq(scale), eq(startCenter.x), eq(startCenter.y)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java index 60c8148180da..3baa102b882b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java @@ -66,14 +66,14 @@ public class MagnificationThumbnailTest { @Test public void updateThumbnailShows() { - runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail( + runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail( /* scale= */ 2f, /* centerX= */ 5, /* centerY= */ 10 )); idle(); - runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail( + runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail( /* scale= */ 2.2f, /* centerX= */ 15, /* centerY= */ 50 @@ -86,7 +86,7 @@ public class MagnificationThumbnailTest { @Test public void updateThumbnailLingersThenHidesAfterTimeout() throws InterruptedException { - runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail( + runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail( /* scale= */ 2f, /* centerX= */ 5, /* centerY= */ 10 @@ -103,14 +103,14 @@ public class MagnificationThumbnailTest { @Test public void hideThumbnailRemoves() throws InterruptedException { - runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail( + runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail( /* scale= */ 2f, /* centerX= */ 5, /* centerY= */ 10 )); idle(); - runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail()); + runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail()); idle(); // Wait for the fade out animation @@ -122,10 +122,10 @@ public class MagnificationThumbnailTest { @Test public void hideShowHideShowHideRemoves() throws InterruptedException { - runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail()); + runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail()); idle(); - runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail( + runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail( /* scale= */ 2f, /* centerX= */ 5, /* centerY= */ 10 @@ -135,17 +135,17 @@ public class MagnificationThumbnailTest { // Wait for the fade in animation Thread.sleep(200L); - runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail()); + runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail()); idle(); - runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail( + runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail( /* scale= */ 2f, /* centerX= */ 5, /* centerY= */ 10 )); idle(); - runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail()); + runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail()); idle(); @@ -158,7 +158,7 @@ public class MagnificationThumbnailTest { @Test public void hideWithoutShowDoesNothing() throws InterruptedException { - runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail()); + runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail()); idle(); // Wait for the fade out animation @@ -172,7 +172,7 @@ public class MagnificationThumbnailTest { @Test public void whenHidden_setBoundsDoesNotShow() throws InterruptedException { - runOnMainSync(() -> mMagnificationThumbnail.setThumbNailBounds( + runOnMainSync(() -> mMagnificationThumbnail.setThumbnailBounds( new Rect(), /* scale= */ 2f, /* centerX= */ 5, diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index ab8f3f2279fe..d12741ac8bd6 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -211,8 +211,7 @@ public class UserControllerTest { @Test public void testStartUser_foreground() { mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); - verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt()); - verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); + verify(mInjector, never()).dismissUserSwitchingDialog(any()); verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); verify(mInjector.getWindowManager()).setSwitchingUser(true); verify(mInjector).clearAllLockedTasks(anyString()); @@ -224,7 +223,8 @@ public class UserControllerTest { public void testStartUser_background() { boolean started = mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND); assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue(); - verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); + verify(mInjector, never()).showUserSwitchingDialog( + any(), any(), anyString(), anyString(), any()); verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); verify(mInjector, never()).clearAllLockedTasks(anyString()); startBackgroundUserAssertions(); @@ -276,7 +276,8 @@ public class UserControllerTest { assertWithMessage("startUserOnDisplay(%s, %s)", TEST_USER_ID, 42).that(started).isTrue(); verifyUserAssignedToDisplay(TEST_USER_ID, 42); - verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); + verify(mInjector, never()).showUserSwitchingDialog( + any(), any(), anyString(), anyString(), any()); verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); verify(mInjector, never()).clearAllLockedTasks(anyString()); startBackgroundUserAssertions(); @@ -288,8 +289,9 @@ public class UserControllerTest { /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); - verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); - verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); + verify(mInjector, never()).showUserSwitchingDialog( + any(), any(), anyString(), anyString(), any()); + verify(mInjector, never()).dismissUserSwitchingDialog(any()); verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); startForegroundUserAssertions(); } @@ -310,7 +312,8 @@ public class UserControllerTest { // Make sure no intents have been fired for pre-created users. assertTrue(mInjector.mSentIntents.isEmpty()); - verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); + verify(mInjector, never()).showUserSwitchingDialog( + any(), any(), anyString(), anyString(), any()); verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); verify(mInjector, never()).clearAllLockedTasks(anyString()); @@ -442,7 +445,7 @@ public class UserControllerTest { // Verify that continueUserSwitch worked as expected continueAndCompleteUserSwitch(userState, oldUserId, newUserId); verify(mInjector, times(0)).dismissKeyguard(any()); - verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen(); + verify(mInjector, times(1)).dismissUserSwitchingDialog(any()); continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false); verifySystemUserVisibilityChangesNeverNotified(); } @@ -463,7 +466,7 @@ public class UserControllerTest { // Verify that continueUserSwitch worked as expected continueAndCompleteUserSwitch(userState, oldUserId, newUserId); verify(mInjector, times(1)).dismissKeyguard(any()); - verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen(); + verify(mInjector, times(1)).dismissUserSwitchingDialog(any()); continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false); verifySystemUserVisibilityChangesNeverNotified(); } @@ -483,7 +486,7 @@ public class UserControllerTest { mInjector.mHandler.clearAllRecordedMessages(); // Verify that continueUserSwitch worked as expected continueAndCompleteUserSwitch(userState, oldUserId, newUserId); - verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); + verify(mInjector, never()).dismissUserSwitchingDialog(any()); continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false); } @@ -985,8 +988,7 @@ public class UserControllerTest { mInjector.mHandler.clearAllRecordedMessages(); // Verify that continueUserSwitch worked as expected continueAndCompleteUserSwitch(userState, oldUserId, newUserId); - verify(mInjector.getWindowManager(), times(expectedNumberOfCalls)) - .stopFreezingScreen(); + verify(mInjector, times(expectedNumberOfCalls)).dismissUserSwitchingDialog(any()); continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping); } @@ -1189,6 +1191,22 @@ public class UserControllerTest { } @Override + void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser, + String switchingFromSystemUserMessage, String switchingToSystemUserMessage, + Runnable onShown) { + if (onShown != null) { + onShown.run(); + } + } + + @Override + void dismissUserSwitchingDialog(Runnable onDismissed) { + if (onDismissed != null) { + onDismissed.run(); + } + } + + @Override protected LockPatternUtils getLockPatternUtils() { return mLockPatternUtilsMock; } diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java new file mode 100644 index 000000000000..c5a9af7d909d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java @@ -0,0 +1,64 @@ +/* + * 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.companion.datatransfer.contextsync; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.testing.AndroidTestingRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +public class CallMetadataSyncDataTest { + + @Test + public void call_writeToParcel_fromParcel_reconstructsSuccessfully() { + final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call(); + final long id = 5; + final String callerId = "callerId"; + final byte[] appIcon = "appIcon".getBytes(); + final String appName = "appName"; + final String appIdentifier = "com.google.test"; + final int status = 1; + final int control1 = 2; + final int control2 = 3; + call.setId(id); + call.setCallerId(callerId); + call.setAppIcon(appIcon); + call.setAppName(appName); + call.setAppIdentifier(appIdentifier); + call.setStatus(status); + call.addControl(control1); + call.addControl(control2); + + Parcel parcel = Parcel.obtain(); + call.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + final CallMetadataSyncData.Call reconstructedCall = CallMetadataSyncData.Call.fromParcel( + parcel); + + assertThat(reconstructedCall.getId()).isEqualTo(id); + assertThat(reconstructedCall.getCallerId()).isEqualTo(callerId); + assertThat(reconstructedCall.getAppIcon()).isEqualTo(appIcon); + assertThat(reconstructedCall.getAppName()).isEqualTo(appName); + assertThat(reconstructedCall.getAppIdentifier()).isEqualTo(appIdentifier); + assertThat(reconstructedCall.getStatus()).isEqualTo(status); + assertThat(reconstructedCall.getControls()).containsExactly(control1, control2); + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index a4a3e363ab4d..c8c1d6f0f7ed 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -1160,6 +1160,7 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final int keyCode = KeyEvent.KEYCODE_A; final int action = VirtualKeyEvent.ACTION_UP; + final long eventTimeNanos = 5000L; mInputController.addDeviceForTesting(BINDER, fd, InputController.InputDeviceDescriptor.TYPE_KEYBOARD, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); @@ -1167,8 +1168,9 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder() .setKeyCode(keyCode) .setAction(action) + .setEventTimeNanos(eventTimeNanos) .build()); - verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action); + verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action, eventTimeNanos); } @Test @@ -1188,14 +1190,17 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK; final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; + final long eventTimeNanos = 5000L; mInputController.addDeviceForTesting(BINDER, fd, InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() .setButtonCode(buttonCode) - .setAction(action).build()); - verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action); + .setAction(action) + .setEventTimeNanos(eventTimeNanos) + .build()); + verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action, eventTimeNanos); } @Test @@ -1229,13 +1234,17 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = -0.2f; final float y = 0.7f; + final long eventTimeNanos = 5000L; mInputController.addDeviceForTesting(BINDER, fd, InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() - .setRelativeX(x).setRelativeY(y).build()); - verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y); + .setRelativeX(x) + .setRelativeY(y) + .setEventTimeNanos(eventTimeNanos) + .build()); + verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y, eventTimeNanos); } @Test @@ -1270,14 +1279,17 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = 0.5f; final float y = 1f; + final long eventTimeNanos = 5000L; mInputController.addDeviceForTesting(BINDER, fd, InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() .setXAxisMovement(x) - .setYAxisMovement(y).build()); - verify(mNativeWrapperMock).writeScrollEvent(fd, x, y); + .setYAxisMovement(y) + .setEventTimeNanos(eventTimeNanos) + .build()); + verify(mNativeWrapperMock).writeScrollEvent(fd, x, y, eventTimeNanos); } @Test @@ -1318,6 +1330,7 @@ public class VirtualDeviceManagerServiceTest { final float x = 100.5f; final float y = 200.5f; final int action = VirtualTouchEvent.ACTION_UP; + final long eventTimeNanos = 5000L; mInputController.addDeviceForTesting(BINDER, fd, InputController.InputDeviceDescriptor.TYPE_TOUCHSCREEN, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); @@ -1327,9 +1340,10 @@ public class VirtualDeviceManagerServiceTest { .setAction(action) .setPointerId(pointerId) .setToolType(toolType) + .setEventTimeNanos(eventTimeNanos) .build()); verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN, - Float.NaN); + Float.NaN, eventTimeNanos); } @Test @@ -1342,6 +1356,7 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualTouchEvent.ACTION_UP; final float pressure = 1.0f; final float majorAxisSize = 10.0f; + final long eventTimeNanos = 5000L; mInputController.addDeviceForTesting(BINDER, fd, InputController.InputDeviceDescriptor.TYPE_TOUCHSCREEN, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); @@ -1353,9 +1368,10 @@ public class VirtualDeviceManagerServiceTest { .setToolType(toolType) .setPressure(pressure) .setMajorAxisSize(majorAxisSize) + .setEventTimeNanos(eventTimeNanos) .build()); verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, pressure, - majorAxisSize); + majorAxisSize, eventTimeNanos); } @Test diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java index 89ff2c258c26..5f81869903c3 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -287,25 +287,37 @@ public class BrightnessMappingStrategyTest { adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f; } - // Default is unadjusted + // Default assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), - 0.0001f /* tolerance */); + TOLERANCE); assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), - 0.0001f /* tolerance */); + TOLERANCE); + assertEquals(DISPLAY_RANGE_NITS[0], + strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE); + assertEquals(DISPLAY_RANGE_NITS[1], + strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE); - // When adjustment is turned on, adjustment array is used + // Adjustment is turned on strategy.recalculateSplines(true, adjustedNits50p); + assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), + TOLERANCE); + assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), + TOLERANCE); assertEquals(DISPLAY_RANGE_NITS[0] / 2, - strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), 0.0001f /* tolerance */); + strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE); assertEquals(DISPLAY_RANGE_NITS[1] / 2, - strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), 0.0001f /* tolerance */); + strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE); - // When adjustment is turned off, adjustment array is ignored + // Adjustment is turned off strategy.recalculateSplines(false, adjustedNits50p); assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), - 0.0001f /* tolerance */); + TOLERANCE); assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), - 0.0001f /* tolerance */); + TOLERANCE); + assertEquals(DISPLAY_RANGE_NITS[0], + strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE); + assertEquals(DISPLAY_RANGE_NITS[1], + strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE); } @Test diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index cfb432ab3784..d7b12e031c35 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -279,20 +279,27 @@ public final class DisplayBrightnessControllerTest { @Test public void testConvertToNits() { - float brightness = 0.5f; - float nits = 300; + final float brightness = 0.5f; + final float nits = 300; + final float adjustedNits = 200; // ABC is null assertEquals(-1f, mDisplayBrightnessController.convertToNits(brightness), /* delta= */ 0); + assertEquals(-1f, mDisplayBrightnessController.convertToAdjustedNits(brightness), + /* delta= */ 0); AutomaticBrightnessController automaticBrightnessController = mock(AutomaticBrightnessController.class); when(automaticBrightnessController.convertToNits(brightness)).thenReturn(nits); + when(automaticBrightnessController.convertToAdjustedNits(brightness)) + .thenReturn(adjustedNits); mDisplayBrightnessController.setAutomaticBrightnessController( automaticBrightnessController); assertEquals(nits, mDisplayBrightnessController.convertToNits(brightness), /* delta= */ 0); + assertEquals(adjustedNits, mDisplayBrightnessController.convertToAdjustedNits(brightness), + /* delta= */ 0); } @Test diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java index dca67d6da7ad..76b6a820e4a7 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java @@ -17,6 +17,8 @@ package com.android.server.power.stats.wakeups; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM; +import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA; +import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI; @@ -50,6 +52,8 @@ public class CpuWakeupStatsTest { private static final String KERNEL_REASON_ALARM_IRQ = "120 test.alarm.device"; private static final String KERNEL_REASON_WIFI_IRQ = "130 test.wifi.device"; private static final String KERNEL_REASON_SOUND_TRIGGER_IRQ = "129 test.sound_trigger.device"; + private static final String KERNEL_REASON_SENSOR_IRQ = "15 test.sensor.device"; + private static final String KERNEL_REASON_CELLULAR_DATA_IRQ = "18 test.cellular_data.device"; private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device"; private static final String KERNEL_REASON_UNKNOWN = "free-form-reason test.alarm.device"; private static final String KERNEL_REASON_ALARM_ABNORMAL = "-1 test.alarm.device"; @@ -110,6 +114,83 @@ public class CpuWakeupStatsTest { } @Test + public void irqAttributionAllCombinations() { + final int[] subsystems = new int[] { + CPU_WAKEUP_SUBSYSTEM_ALARM, + CPU_WAKEUP_SUBSYSTEM_WIFI, + CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, + CPU_WAKEUP_SUBSYSTEM_SENSOR, + CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA, + }; + + final String[] kernelReasons = new String[] { + KERNEL_REASON_ALARM_IRQ, + KERNEL_REASON_WIFI_IRQ, + KERNEL_REASON_SOUND_TRIGGER_IRQ, + KERNEL_REASON_SENSOR_IRQ, + KERNEL_REASON_CELLULAR_DATA_IRQ, + }; + + final int[] uids = new int[] { + TEST_UID_2, TEST_UID_3, TEST_UID_4, TEST_UID_1, TEST_UID_5 + }; + + final int[] procStates = new int[] { + TEST_PROC_STATE_2, + TEST_PROC_STATE_3, + TEST_PROC_STATE_4, + TEST_PROC_STATE_1, + TEST_PROC_STATE_5 + }; + + final int total = subsystems.length; + assertThat(kernelReasons.length).isEqualTo(total); + assertThat(uids.length).isEqualTo(total); + assertThat(procStates.length).isEqualTo(total); + + for (int mask = 1; mask < (1 << total); mask++) { + final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, + mHandler); + populateDefaultProcStates(obj); + + final long wakeupTime = mRandom.nextLong(123456); + + String combinedKernelReason = null; + for (int i = 0; i < total; i++) { + if ((mask & (1 << i)) != 0) { + combinedKernelReason = (combinedKernelReason == null) + ? kernelReasons[i] + : String.join(":", combinedKernelReason, kernelReasons[i]); + } + + obj.noteWakingActivity(subsystems[i], wakeupTime + 2, uids[i]); + } + obj.noteWakeupTimeAndReason(wakeupTime, 1, combinedKernelReason); + + assertThat(obj.mWakeupAttribution.size()).isEqualTo(1); + + final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime); + assertThat(attribution.size()).isEqualTo(Integer.bitCount(mask)); + + for (int i = 0; i < total; i++) { + if ((mask & (1 << i)) == 0) { + assertThat(attribution.contains(subsystems[i])).isFalse(); + continue; + } + assertThat(attribution.contains(subsystems[i])).isTrue(); + assertThat(attribution.get(subsystems[i]).get(uids[i])).isEqualTo(procStates[i]); + + for (int j = 0; j < total; j++) { + if (i == j) { + continue; + } + assertThat(attribution.get(subsystems[i]).indexOfKey(uids[j])).isLessThan(0); + } + } + } + } + + @Test public void alarmIrqAttributionSolo() { final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler); final long wakeupTime = 12423121; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 8fcbf2f9e97a..541739d50024 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -96,6 +96,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.CountDownLatch; public class ManagedServicesTest extends UiServiceTestCase { @@ -1920,6 +1921,18 @@ public class ManagedServicesTest extends UiServiceTestCase { assertTrue(service.isBound(cn_disallowed, 0)); } + @Test + public void isComponentEnabledForCurrentProfiles_isThreadSafe() throws InterruptedException { + for (UserInfo userInfo : mUm.getUsers()) { + mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", userInfo.id, true); + } + testThreadSafety(() -> { + mService.rebindServices(false, 0); + assertThat(mService.isComponentEnabledForCurrentProfiles( + new ComponentName("pkg1", "cmp1"))).isTrue(); + }, 20, 30); + } + private void mockServiceInfoWithMetaData(List<ComponentName> componentNames, ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException { @@ -2298,4 +2311,38 @@ public class ManagedServicesTest extends UiServiceTestCase { return false; } } + + /** + * Helper method to test the thread safety of some operations. + * + * <p>Runs the supplied {@code operationToTest}, {@code nRunsPerThread} times, + * concurrently using {@code nThreads} threads, and waits for all of them to finish. + */ + private static void testThreadSafety(Runnable operationToTest, int nThreads, + int nRunsPerThread) throws InterruptedException { + final CountDownLatch startLatch = new CountDownLatch(1); + final CountDownLatch doneLatch = new CountDownLatch(nThreads); + + for (int i = 0; i < nThreads; i++) { + Runnable threadRunnable = () -> { + try { + startLatch.await(); + for (int j = 0; j < nRunsPerThread; j++) { + operationToTest.run(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + doneLatch.countDown(); + } + }; + new Thread(threadRunnable, "Test Thread #" + i).start(); + } + + // Ready set go + startLatch.countDown(); + + // Wait for all test threads to be done. + doneLatch.await(); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index dd9f3cb3d343..94f52bbc6021 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -358,7 +358,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private static final int NOTIFICATION_LOCATION_UNKNOWN = 0; private static final String VALID_CONVO_SHORTCUT_ID = "shortcut"; - + private static final String SEARCH_SELECTOR_PKG = "searchSelector"; @Mock private NotificationListeners mListeners; @Mock @@ -549,6 +549,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // apps allowed as convos mService.setStringArrayResourceValue(PKG_O); + TestableResources tr = mContext.getOrCreateTestableResources(); + tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName, + SEARCH_SELECTOR_PKG); + mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper())); mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient, mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, @@ -10636,6 +10640,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void fixSystemNotification_defaultSearchSelectior_withOnGoingFlag_nondismissible() + throws Exception { + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = SEARCH_SELECTOR_PKG; + ai.uid = mUid; + ai.flags |= ApplicationInfo.FLAG_SYSTEM; + + when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(ai); + when(mAppOpsManager.checkOpNoThrow( + AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid, + ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED); + // Given: a notification from an app on the system partition has the flag + // FLAG_ONGOING_EVENT set + // feature flag: ALLOW_DISMISS_ONGOING is on + mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true); + Notification n = new Notification.Builder(mContext, "test") + .setOngoing(true) + .build(); + + // When: fix the notification with NotificationManagerService + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); + + // Then: the notification's flag FLAG_NO_DISMISS should be set + assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS); + } + + @Test public void fixCallNotification_withOnGoingFlag_shouldNotBeNonDismissible() throws Exception { // Given: a call notification has the flag FLAG_ONGOING_EVENT set diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index f12b53acd06b..fe7cd4a5edd9 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -89,6 +89,12 @@ <activity android:name="com.android.server.wm.SurfaceControlViewHostTests$TestActivity" /> + <activity android:name="android.server.wm.scvh.SurfaceSyncGroupActivity" + android:screenOrientation="locked" + android:turnScreenOn="true" + android:theme="@style/WhiteBackgroundTheme" + android:exported="true"/> + <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService" android:foregroundServiceType="mediaProjection" android:enabled="true"> diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java new file mode 100644 index 000000000000..9db647acebb7 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java @@ -0,0 +1,187 @@ +/* + * 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.wm; + +import static android.window.SurfaceSyncGroup.TRANSACTION_READY_TIMEOUT; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.app.Instrumentation; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Handler; +import android.os.HandlerThread; +import android.platform.test.annotations.Presubmit; +import android.server.wm.scvh.SurfaceSyncGroupActivity; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.WindowManager; +import android.view.cts.surfacevalidator.BitmapPixelChecker; +import android.window.SurfaceSyncGroup; + +import androidx.test.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@Presubmit +public class SurfaceSyncGroupTests { + private static final String TAG = "SurfaceSyncGroupTests"; + + @Rule + public ActivityTestRule<SurfaceSyncGroupActivity> mActivityRule = new ActivityTestRule<>( + SurfaceSyncGroupActivity.class); + + private SurfaceSyncGroupActivity mActivity; + + Instrumentation mInstrumentation; + + private final HandlerThread mHandlerThread = new HandlerThread("applyTransaction"); + private Handler mHandler; + + @Before + public void setup() { + mActivity = mActivityRule.getActivity(); + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mHandlerThread.start(); + mHandler = mHandlerThread.getThreadHandler(); + } + + @Test + public void testOverlappingSyncsEnsureOrder_WhenTimeout() throws InterruptedException { + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.format = PixelFormat.TRANSLUCENT; + + CountDownLatch secondDrawCompleteLatch = new CountDownLatch(1); + CountDownLatch bothSyncGroupsComplete = new CountDownLatch(2); + final SurfaceSyncGroup firstSsg = new SurfaceSyncGroup(TAG + "-first"); + final SurfaceSyncGroup secondSsg = new SurfaceSyncGroup(TAG + "-second"); + final SurfaceSyncGroup infiniteSsg = new SurfaceSyncGroup(TAG + "-infinite"); + + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown); + firstSsg.addTransaction(t); + + View backgroundView = mActivity.getBackgroundView(); + firstSsg.add(backgroundView.getRootSurfaceControl(), + () -> mActivity.runOnUiThread(() -> backgroundView.setBackgroundColor(Color.RED))); + + addSecondSyncGroup(secondSsg, secondDrawCompleteLatch, bothSyncGroupsComplete); + + assertTrue("Failed to draw two frames", + secondDrawCompleteLatch.await(5, TimeUnit.SECONDS)); + + mHandler.postDelayed(() -> { + // Don't add a markSyncReady for the first sync group until after it's added to another + // SSG to ensure the timeout is longer than the second frame's timeout. The infinite SSG + // will never complete to ensure it reaches the timeout, but only after the second SSG + // had a chance to reach its timeout. + infiniteSsg.add(firstSsg, null /* runnable */); + firstSsg.markSyncReady(); + }, 200); + + assertTrue("Failed to wait for both SurfaceSyncGroups to apply", + bothSyncGroupsComplete.await(5, TimeUnit.SECONDS)); + + validateScreenshot(); + } + + @Test + public void testOverlappingSyncsEnsureOrder_WhileHoldingTransaction() + throws InterruptedException { + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.format = PixelFormat.TRANSLUCENT; + + CountDownLatch secondDrawCompleteLatch = new CountDownLatch(1); + CountDownLatch bothSyncGroupsComplete = new CountDownLatch(2); + + final SurfaceSyncGroup firstSsg = new SurfaceSyncGroup(TAG + "-first", + transaction -> mHandler.postDelayed(() -> { + try { + assertTrue("Failed to draw two frames", + secondDrawCompleteLatch.await(5, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + transaction.apply(); + }, TRANSACTION_READY_TIMEOUT + 200)); + final SurfaceSyncGroup secondSsg = new SurfaceSyncGroup(TAG + "-second"); + + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown); + firstSsg.addTransaction(t); + + View backgroundView = mActivity.getBackgroundView(); + firstSsg.add(backgroundView.getRootSurfaceControl(), + () -> mActivity.runOnUiThread(() -> backgroundView.setBackgroundColor(Color.RED))); + firstSsg.markSyncReady(); + + addSecondSyncGroup(secondSsg, secondDrawCompleteLatch, bothSyncGroupsComplete); + + assertTrue("Failed to wait for both SurfaceSyncGroups to apply", + bothSyncGroupsComplete.await(5, TimeUnit.SECONDS)); + + validateScreenshot(); + } + + private void addSecondSyncGroup(SurfaceSyncGroup surfaceSyncGroup, + CountDownLatch waitForSecondDraw, CountDownLatch bothSyncGroupsComplete) { + View backgroundView = mActivity.getBackgroundView(); + ViewTreeObserver viewTreeObserver = backgroundView.getViewTreeObserver(); + viewTreeObserver.registerFrameCommitCallback(() -> mHandler.post(() -> { + surfaceSyncGroup.add(backgroundView.getRootSurfaceControl(), + () -> mActivity.runOnUiThread( + () -> backgroundView.setBackgroundColor(Color.BLUE))); + + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown); + surfaceSyncGroup.addTransaction(t); + surfaceSyncGroup.markSyncReady(); + viewTreeObserver.registerFrameCommitCallback(waitForSecondDraw::countDown); + })); + } + + private void validateScreenshot() { + Bitmap screenshot = mInstrumentation.getUiAutomation().takeScreenshot( + mActivity.getWindow()); + assertNotNull("Failed to generate a screenshot", screenshot); + Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false); + screenshot.recycle(); + + BitmapPixelChecker pixelChecker = new BitmapPixelChecker(Color.BLUE); + int halfWidth = swBitmap.getWidth() / 2; + int halfHeight = swBitmap.getHeight() / 2; + // We don't need to check all the pixels since we only care that at least some of them are + // blue. If the buffers were submitted out of order, all the pixels will be red. + Rect bounds = new Rect(halfWidth, halfHeight, halfWidth + 10, halfHeight + 10); + int numMatchingPixels = pixelChecker.getNumMatchingPixels(swBitmap, bounds); + assertEquals("Expected 100 received " + numMatchingPixels + " matching pixels", 100, + numMatchingPixels); + + swBitmap.recycle(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 43b429c76749..653b52b06720 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -109,14 +109,19 @@ public class TransitionTests extends WindowTestsBase { final SurfaceControl.Transaction mMockT = mock(SurfaceControl.Transaction.class); private BLASTSyncEngine mSyncEngine; + private Transition createTestTransition(int transitType, TransitionController controller) { + return new Transition(transitType, 0 /* flags */, controller, controller.mSyncEngine); + } + private Transition createTestTransition(int transitType) { final TransitionController controller = new TestTransitionController( mock(ActivityTaskManagerService.class)); mSyncEngine = createTestBLASTSyncEngine(); - final Transition t = new Transition(transitType, 0 /* flags */, controller, mSyncEngine); - t.startCollecting(0 /* timeoutMs */); - return t; + controller.setSyncEngine(mSyncEngine); + final Transition out = createTestTransition(transitType, controller); + out.startCollecting(0 /* timeoutMs */); + return out; } @Test @@ -367,7 +372,6 @@ public class TransitionTests extends WindowTestsBase { final ActivityRecord act = createActivityRecord(tasks[i]); // alternate so that the transition doesn't get promoted to the display area act.setVisibleRequested((i % 2) == 0); // starts invisible - act.visibleIgnoringKeyguard = (i % 2) == 0; if (i == showWallpaperTask) { doReturn(true).when(act).showWallpaper(); } @@ -754,10 +758,8 @@ public class TransitionTests extends WindowTestsBase { final ActivityRecord closing = createActivityRecord(oldTask); closing.setOccludesParent(true); - closing.visibleIgnoringKeyguard = true; final ActivityRecord opening = createActivityRecord(newTask); opening.setOccludesParent(true); - opening.visibleIgnoringKeyguard = true; // Start states. changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */)); changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */)); @@ -795,10 +797,8 @@ public class TransitionTests extends WindowTestsBase { final ActivityRecord closing = createActivityRecord(oldTask); closing.setOccludesParent(true); - closing.visibleIgnoringKeyguard = true; final ActivityRecord opening = createActivityRecord(newTask); opening.setOccludesParent(false); - opening.visibleIgnoringKeyguard = true; // Start states. changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */)); changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */)); @@ -837,10 +837,8 @@ public class TransitionTests extends WindowTestsBase { final ActivityRecord closing = closingTaskFragment.getTopMostActivity(); closing.setOccludesParent(true); - closing.visibleIgnoringKeyguard = true; final ActivityRecord opening = openingTaskFragment.getTopMostActivity(); opening.setOccludesParent(true); - opening.visibleIgnoringKeyguard = true; // Start states. changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment, false /* vis */, true /* exChg */)); @@ -881,10 +879,8 @@ public class TransitionTests extends WindowTestsBase { final ActivityRecord closing = closingTaskFragment.getTopMostActivity(); closing.setOccludesParent(true); - closing.visibleIgnoringKeyguard = true; final ActivityRecord opening = openingTaskFragment.getTopMostActivity(); opening.setOccludesParent(false); - opening.visibleIgnoringKeyguard = true; // Start states. changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment, false /* vis */, true /* exChg */)); @@ -925,10 +921,8 @@ public class TransitionTests extends WindowTestsBase { final ActivityRecord opening = openingTaskFragment.getTopMostActivity(); opening.setOccludesParent(true); - opening.visibleIgnoringKeyguard = true; final ActivityRecord closing = closingTaskFragment.getTopMostActivity(); closing.setOccludesParent(true); - closing.visibleIgnoringKeyguard = true; closing.finishing = true; // Start states. changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment, @@ -970,10 +964,8 @@ public class TransitionTests extends WindowTestsBase { final ActivityRecord opening = openingTaskFragment.getTopMostActivity(); opening.setOccludesParent(true); - opening.visibleIgnoringKeyguard = true; final ActivityRecord closing = closingTaskFragment.getTopMostActivity(); closing.setOccludesParent(false); - closing.visibleIgnoringKeyguard = true; closing.finishing = true; // Start states. changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment, @@ -1282,6 +1274,7 @@ public class TransitionTests extends WindowTestsBase { @Test public void testIntermediateVisibility() { final TransitionController controller = new TestTransitionController(mAtm); + controller.setSyncEngine(mWm.mSyncEngine); final ITransitionPlayer player = new ITransitionPlayer.Default(); controller.registerTransitionPlayer(player, null /* playerProc */); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); @@ -1365,6 +1358,7 @@ public class TransitionTests extends WindowTestsBase { super.dispatchLegacyAppTransitionFinished(ar); } }; + controller.setSyncEngine(mWm.mSyncEngine); controller.mSnapshotController = mWm.mSnapshotController; final TaskSnapshotController taskSnapshotController = controller.mSnapshotController .mTaskSnapshotController; @@ -1462,6 +1456,7 @@ public class TransitionTests extends WindowTestsBase { @Test public void testNotReadyPushPop() { final TransitionController controller = new TestTransitionController(mAtm); + controller.setSyncEngine(mWm.mSyncEngine); final ITransitionPlayer player = new ITransitionPlayer.Default(); controller.registerTransitionPlayer(player, null /* playerProc */); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); @@ -1918,6 +1913,110 @@ public class TransitionTests extends WindowTestsBase { assertEquals(TRANSIT_CHANGE, info.getChanges().get(0).getMode()); } + @Test + public void testQueueStartCollect() { + final TransitionController controller = mAtm.getTransitionController(); + final TestTransitionPlayer player = registerTestTransitionPlayer(); + + mSyncEngine = createTestBLASTSyncEngine(); + controller.setSyncEngine(mSyncEngine); + + final Transition transitA = createTestTransition(TRANSIT_OPEN, controller); + final Transition transitB = createTestTransition(TRANSIT_OPEN, controller); + final Transition transitC = createTestTransition(TRANSIT_OPEN, controller); + + final boolean[] onStartA = new boolean[]{false, false}; + final boolean[] onStartB = new boolean[]{false, false}; + controller.startCollectOrQueue(transitA, (deferred) -> { + onStartA[0] = true; + onStartA[1] = deferred; + }); + controller.startCollectOrQueue(transitB, (deferred) -> { + onStartB[0] = true; + onStartB[1] = deferred; + }); + waitUntilHandlersIdle(); + + assertTrue(onStartA[0]); + assertFalse(onStartA[1]); + assertTrue(transitA.isCollecting()); + + // B should be queued, so no calls yet + assertFalse(onStartB[0]); + assertTrue(transitB.isPending()); + + // finish collecting A + transitA.start(); + transitA.setAllReady(); + mSyncEngine.tryFinishForTest(transitA.getSyncId()); + waitUntilHandlersIdle(); + + assertTrue(transitA.isPlaying()); + assertTrue(transitB.isCollecting()); + assertTrue(onStartB[0]); + // Should receive deferred = true + assertTrue(onStartB[1]); + + // finish collecting B + transitB.start(); + transitB.setAllReady(); + mSyncEngine.tryFinishForTest(transitB.getSyncId()); + assertTrue(transitB.isPlaying()); + + // Now we should be able to start collecting directly a new transition + final boolean[] onStartC = new boolean[]{false, false}; + controller.startCollectOrQueue(transitC, (deferred) -> { + onStartC[0] = true; + onStartC[1] = deferred; + }); + waitUntilHandlersIdle(); + assertTrue(onStartC[0]); + assertFalse(onStartC[1]); + assertTrue(transitC.isCollecting()); + } + + @Test + public void testQueueWithLegacy() { + final TransitionController controller = mAtm.getTransitionController(); + final TestTransitionPlayer player = registerTestTransitionPlayer(); + + mSyncEngine = createTestBLASTSyncEngine(); + controller.setSyncEngine(mSyncEngine); + + final Transition transitA = createTestTransition(TRANSIT_OPEN, controller); + final Transition transitB = createTestTransition(TRANSIT_OPEN, controller); + + controller.startCollectOrQueue(transitA, (deferred) -> {}); + + BLASTSyncEngine.SyncGroup legacySync = mSyncEngine.prepareSyncSet( + mock(BLASTSyncEngine.TransactionReadyListener.class), "test"); + final boolean[] applyLegacy = new boolean[]{false}; + controller.startLegacySyncOrQueue(legacySync, () -> applyLegacy[0] = true); + assertFalse(applyLegacy[0]); + waitUntilHandlersIdle(); + + controller.startCollectOrQueue(transitB, (deferred) -> {}); + assertTrue(transitA.isCollecting()); + + // finish collecting A + transitA.start(); + transitA.setAllReady(); + mSyncEngine.tryFinishForTest(transitA.getSyncId()); + waitUntilHandlersIdle(); + + assertTrue(transitA.isPlaying()); + // legacy sync should start now + assertTrue(applyLegacy[0]); + // transitB must wait + assertTrue(transitB.isPending()); + + // finish legacy sync + mSyncEngine.setReady(legacySync.mSyncId); + mSyncEngine.tryFinishForTest(legacySync.mSyncId); + // transitioncontroller should be notified so it can start collecting B + assertTrue(transitB.isCollecting()); + } + private static void makeTaskOrganized(Task... tasks) { final ITaskOrganizer organizer = mock(ITaskOrganizer.class); for (Task t : tasks) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java index aadb38da5469..2e05e2073e80 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java @@ -81,7 +81,12 @@ final class VisualQueryDetectorSession extends DetectorSession { void informRestartProcessLocked() { Slog.v(TAG, "informRestartProcessLocked"); mUpdateStateAfterStartFinished.set(false); - //TODO(b/261783819): Starts detection in VisualQueryDetectionService. + try { + mCallback.onProcessRestarted(); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to communicate #onProcessRestarted", e); + notifyOnDetectorRemoteException(); + } } void setVisualQueryDetectionAttentionListenerLocked( diff --git a/telecomm/java/android/telecom/CallStreamingService.java b/telecomm/java/android/telecom/CallStreamingService.java index 3f538a7f262d..df48cd6a16e2 100644 --- a/telecomm/java/android/telecom/CallStreamingService.java +++ b/telecomm/java/android/telecom/CallStreamingService.java @@ -52,6 +52,7 @@ import java.lang.annotation.RetentionPolicy; * </service> * } * </pre> + * * @hide */ @SystemApi @@ -65,7 +66,7 @@ public abstract class CallStreamingService extends Service { private static final int MSG_SET_STREAMING_CALL_ADAPTER = 1; private static final int MSG_CALL_STREAMING_STARTED = 2; private static final int MSG_CALL_STREAMING_STOPPED = 3; - private static final int MSG_CALL_STREAMING_CHANGED_CHANGED = 4; + private static final int MSG_CALL_STREAMING_STATE_CHANGED = 4; /** Default Handler used to consolidate binder method calls onto a single thread. */ private final Handler mHandler = new Handler(Looper.getMainLooper()) { @@ -77,8 +78,10 @@ public abstract class CallStreamingService extends Service { switch (msg.what) { case MSG_SET_STREAMING_CALL_ADAPTER: - mStreamingCallAdapter = new StreamingCallAdapter( - (IStreamingCallAdapter) msg.obj); + if (msg.obj != null) { + mStreamingCallAdapter = new StreamingCallAdapter( + (IStreamingCallAdapter) msg.obj); + } break; case MSG_CALL_STREAMING_STARTED: mCall = (StreamingCall) msg.obj; @@ -90,10 +93,12 @@ public abstract class CallStreamingService extends Service { mStreamingCallAdapter = null; CallStreamingService.this.onCallStreamingStopped(); break; - case MSG_CALL_STREAMING_CHANGED_CHANGED: + case MSG_CALL_STREAMING_STATE_CHANGED: int state = (int) msg.obj; - mCall.requestStreamingState(state); - CallStreamingService.this.onCallStreamingStateChanged(state); + if (mStreamingCallAdapter != null) { + mCall.requestStreamingState(state); + CallStreamingService.this.onCallStreamingStateChanged(state); + } break; default: break; @@ -128,7 +133,7 @@ public abstract class CallStreamingService extends Service { @Override public void onCallStreamingStateChanged(int state) throws RemoteException { - mHandler.obtainMessage(MSG_CALL_STREAMING_CHANGED_CHANGED, state).sendToTarget(); + mHandler.obtainMessage(MSG_CALL_STREAMING_STATE_CHANGED, state).sendToTarget(); } } @@ -173,9 +178,12 @@ public abstract class CallStreamingService extends Service { STREAMING_FAILED_ALREADY_STREAMING, STREAMING_FAILED_NO_SENDER, STREAMING_FAILED_SENDER_BINDING_ERROR - }) + }) @Retention(RetentionPolicy.SOURCE) - public @interface StreamingFailedReason {}; + public @interface StreamingFailedReason { + } + + ; /** * Called when a {@code StreamingCall} has been added to this call streaming session. The call |