diff options
210 files changed, 4675 insertions, 1424 deletions
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb index cfcb4e7d69b1..80317e4634f3 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "7351002" + build_id: "7396576" target: "CtsShim" source_file: "aosp_arm64/CtsShimPriv.apk" } @@ -9,4 +9,5 @@ drops { version_group: "" git_project: "platform/frameworks/base" git_branch: "sc-dev" + transform: TRANSFORM_NONE } diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb index 0948e47561a8..3605b6d0433b 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "7351002" + build_id: "7396576" target: "CtsShim" source_file: "aosp_arm64/CtsShim.apk" } @@ -9,4 +9,5 @@ drops { version_group: "" git_project: "platform/frameworks/base" git_branch: "sc-dev" + transform: TRANSFORM_NONE } diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb index db6447525a18..025ec3a9fdaf 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "7351002" + build_id: "7396576" target: "CtsShim" source_file: "aosp_x86_64/CtsShimPriv.apk" } @@ -9,4 +9,5 @@ drops { version_group: "" git_project: "platform/frameworks/base" git_branch: "sc-dev" + transform: TRANSFORM_NONE } diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb index 80812df718f8..e19235a12f8f 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "7351002" + build_id: "7396576" target: "CtsShim" source_file: "aosp_x86_64/CtsShim.apk" } @@ -9,4 +9,5 @@ drops { version_group: "" git_project: "platform/frameworks/base" git_branch: "sc-dev" + transform: TRANSFORM_NONE } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 6d2d0238ed37..91e2d88ad7cb 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -779,6 +779,9 @@ public class ActivityManager { /** @hide requestType for assist context: generate full AssistStructure for autofill. */ public static final int ASSIST_CONTEXT_AUTOFILL = 2; + /** @hide requestType for assist context: generate AssistContent but not AssistStructure. */ + public static final int ASSIST_CONTEXT_CONTENT = 3; + /** @hide Flag for registerUidObserver: report changes in process state. */ public static final int UID_OBSERVER_PROCSTATE = 1<<0; diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index ab610e4e71c6..d962fa3bc316 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -48,6 +48,23 @@ import java.util.Set; */ public abstract class ActivityManagerInternal { + public enum ServiceNotificationPolicy { + /** + * The Notification is not associated with any foreground service. + */ + NOT_FOREGROUND_SERVICE, + /** + * The Notification is associated with a foreground service, but the + * notification system should handle it just like non-FGS notifications. + */ + SHOW_IMMEDIATELY, + /** + * The Notification is associated with a foreground service, and the + * notification system should ignore it unless it has already been shown (in + * which case it should be used to update the currently displayed UI). + */ + UPDATE_ONLY + } // Access modes for handleIncomingUser. public static final int ALLOW_NON_FULL = 0; @@ -458,6 +475,24 @@ public abstract class ActivityManagerInternal { String channelId); /** + * Tell the service lifecycle logic that the given Notification content is now + * canonical for any foreground-service visibility policy purposes. + * + * Returns a description of any FGs-related policy around the given Notification: + * not associated with an FGS; ensure display; or only update if already displayed. + */ + public abstract ServiceNotificationPolicy applyForegroundServiceNotification( + Notification notification, int id, String pkg, @UserIdInt int userId); + + /** + * Callback from the notification subsystem that the given FGS notification has + * been shown or updated. This can happen after either Service.startForeground() + * or NotificationManager.notify(). + */ + public abstract void onForegroundServiceNotificationUpdate(Notification notification, + int id, String pkg, @UserIdInt int userId); + + /** * If the given app has any FGSs whose notifications are in the given channel, * stop them. */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7149096ee806..02ab3143eed9 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3843,6 +3843,8 @@ public final class ActivityThread extends ClientTransactionHandler // - it does not call onProvideAssistData() // - it needs an IAutoFillCallback boolean forAutofill = cmd.requestType == ActivityManager.ASSIST_CONTEXT_AUTOFILL; + // When only the AssistContent is requested, omit the AsssistStructure + boolean requestedOnlyContent = cmd.requestType == ActivityManager.ASSIST_CONTEXT_CONTENT; // TODO: decide if lastSessionId logic applies to autofill sessions if (mLastSessionId != cmd.sessionId) { @@ -3869,8 +3871,11 @@ public final class ActivityThread extends ClientTransactionHandler r.activity.onProvideAssistData(data); referrer = r.activity.onProvideReferrer(); } - if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutofill) { - structure = new AssistStructure(r.activity, forAutofill, cmd.flags); + if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutofill + || requestedOnlyContent) { + if (!requestedOnlyContent) { + structure = new AssistStructure(r.activity, forAutofill, cmd.flags); + } Intent activityIntent = r.activity.getIntent(); boolean notSecure = r.window == null || (r.window.getAttributes().flags @@ -3892,18 +3897,21 @@ public final class ActivityThread extends ClientTransactionHandler r.activity.onProvideAssistContent(content); } } - - } - if (structure == null) { - structure = new AssistStructure(); } - // TODO: decide if lastSessionId logic applies to autofill sessions + if (!requestedOnlyContent) { + if (structure == null) { + structure = new AssistStructure(); + } + + // TODO: decide if lastSessionId logic applies to autofill sessions - structure.setAcquisitionStartTime(startTime); - structure.setAcquisitionEndTime(SystemClock.uptimeMillis()); + structure.setAcquisitionStartTime(startTime); + structure.setAcquisitionEndTime(SystemClock.uptimeMillis()); + + mLastAssistStructures.add(new WeakReference<>(structure)); + } - mLastAssistStructures.add(new WeakReference<>(structure)); IActivityTaskManager mgr = ActivityTaskManager.getService(); try { mgr.reportAssistContextExtras(cmd.requestToken, data, structure, content, referrer); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 02520afea147..1415212ef07d 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2784,16 +2784,6 @@ public class AppOpsManager { private static final ThreadLocal<Integer> sBinderThreadCallingUid = new ThreadLocal<>(); /** - * Optimization: we need to propagate to IPCs whether the current thread is collecting - * app ops but using only the thread local above is too slow as it requires a map lookup - * on every IPC. We add this static var that is lockless and stores an OR-ed mask of the - * thread id's currently collecting ops, thus reducing the map lookup to a simple bit - * operation except the extremely unlikely case when threads with overlapping id bits - * execute op collecting ops. - */ - private static volatile long sThreadsListeningForOpNotedInBinderTransaction = 0L; - - /** * If a thread is currently executing a two-way binder transaction, this stores the * ops that were noted blaming any app (the caller, the caller of the caller, etc). * @@ -8903,7 +8893,6 @@ public class AppOpsManager { * @hide */ public static void startNotedAppOpsCollection(int callingUid) { - sThreadsListeningForOpNotedInBinderTransaction |= Thread.currentThread().getId(); sBinderThreadCallingUid.set(callingUid); } @@ -8918,7 +8907,6 @@ public class AppOpsManager { */ public static void finishNotedAppOpsCollection() { sBinderThreadCallingUid.remove(); - sThreadsListeningForOpNotedInBinderTransaction &= ~Thread.currentThread().getId(); sAppOpsNotedInThisBinderTransaction.remove(); } @@ -9263,9 +9251,7 @@ public class AppOpsManager { * @return whether we are in a binder transaction and collecting appops. */ private static boolean isListeningForOpNotedInBinderTransaction() { - return (sThreadsListeningForOpNotedInBinderTransaction - & Thread.currentThread().getId()) != 0 - && sBinderThreadCallingUid.get() != null; + return sBinderThreadCallingUid.get() != null; } /** diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 432d99d80b89..30ddd20b312c 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -450,10 +450,12 @@ public class Notification implements Parcelable STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text); STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox); STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging); STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation); STANDARD_LAYOUTS.add(R.layout.notification_template_material_media); STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media); STANDARD_LAYOUTS.add(R.layout.notification_template_material_call); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call); STANDARD_LAYOUTS.add(R.layout.notification_template_header); } @@ -5817,7 +5819,7 @@ public class Notification implements Parcelable * @hide */ public RemoteViews createContentView(boolean increasedHeight) { - if (mN.contentView != null && useExistingRemoteView()) { + if (useExistingRemoteView(mN.contentView)) { return fullyCustomViewRequiresDecoration(false /* fromStyle */) ? minimallyDecoratedContentView(mN.contentView) : mN.contentView; } else if (mStyle != null) { @@ -5833,8 +5835,24 @@ public class Notification implements Parcelable return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */); } - private boolean useExistingRemoteView() { - return mStyle == null || !mStyle.displayCustomViewInline(); + private boolean useExistingRemoteView(RemoteViews customContent) { + if (customContent == null) { + return false; + } + if (styleDisplaysCustomViewInline()) { + // the provided custom view is intended to be wrapped by the style. + return false; + } + if (fullyCustomViewRequiresDecoration(false) + && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) { + // If the app's custom views are objects returned from Builder.create*ContentView() + // then the app is most likely attempting to spoof the user. Even if they are not, + // the result would be broken (b/189189308) so we will ignore it. + Log.w(TAG, "For apps targeting S, a custom content view that is a modified " + + "version of any standard layout is disallowed."); + return false; + } + return true; } /** @@ -5842,7 +5860,7 @@ public class Notification implements Parcelable */ public RemoteViews createBigContentView() { RemoteViews result = null; - if (mN.bigContentView != null && useExistingRemoteView()) { + if (useExistingRemoteView(mN.bigContentView)) { return fullyCustomViewRequiresDecoration(false /* fromStyle */) ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView; } @@ -5947,7 +5965,7 @@ public class Notification implements Parcelable * @hide */ public RemoteViews createHeadsUpContentView(boolean increasedHeight) { - if (mN.headsUpContentView != null && useExistingRemoteView()) { + if (useExistingRemoteView(mN.headsUpContentView)) { return fullyCustomViewRequiresDecoration(false /* fromStyle */) ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView) : mN.headsUpContentView; @@ -6396,7 +6414,7 @@ public class Notification implements Parcelable mN.reduceImageSizes(mContext); if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N - && (useExistingRemoteView())) { + && !styleDisplaysCustomViewInline()) { if (mN.contentView == null) { mN.contentView = createContentView(); mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, @@ -6427,6 +6445,10 @@ public class Notification implements Parcelable return mN; } + private boolean styleDisplaysCustomViewInline() { + return mStyle != null && mStyle.displayCustomViewInline(); + } + /** * Apply this Builder to an existing {@link Notification} object. * @@ -6576,7 +6598,7 @@ public class Notification implements Parcelable public boolean usesTemplate() { return (mN.contentView == null && mN.headsUpContentView == null && mN.bigContentView == null) - || (mStyle != null && mStyle.displayCustomViewInline()); + || styleDisplaysCustomViewInline(); } } @@ -9782,10 +9804,6 @@ public class Notification implements Parcelable * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> * the associated notification in the notification shade.</p> * - * <p>Apps sending bubbles can only apply this flag when the app is in the foreground, - * otherwise the flag is not respected. The app is considered foreground if it is visible - * and on the screen, note that a foreground service does not qualify.</p> - * * <p>Generally this flag should only be set by the app if the user has performed an * action to request or create a bubble, or if the user has seen the content in the * notification and the notification is no longer relevant. </p> @@ -9936,10 +9954,6 @@ public class Notification implements Parcelable * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> * the associated notification in the notification shade.</p> * - * <p>Apps sending bubbles can only apply this flag when the app is in the foreground, - * otherwise the flag is not respected. The app is considered foreground if it is visible - * and on the screen, note that a foreground service does not qualify.</p> - * * <p>Generally the app should only set this flag if the user has performed an * action to request or create a bubble, or if the user has seen the content in the * notification and the notification is no longer relevant. </p> @@ -10137,6 +10151,8 @@ public class Notification implements Parcelable * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. * </p> * + * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE. + * * @throws NullPointerException if intent is null. * @throws NullPointerException if icon is null. */ @@ -10325,11 +10341,6 @@ public class Notification implements Parcelable * Sets whether the bubble will be posted <b>without</b> the associated notification in * the notification shade. * - * <p>This flag has no effect if the app posting the bubble is not in the foreground. - * The app is considered foreground if it is visible and on the screen, note that - * a foreground service does not qualify. - * </p> - * * <p>Generally, this flag should only be set if the user has performed an action to * request or create a bubble, or if the user has seen the content in the notification * and the notification is no longer relevant.</p> diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index a88aed7f20a3..18c638112480 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -963,6 +963,9 @@ public class AppWidgetManager { /** * Set the component for a given appWidgetId. * + * If successful, the app widget provider will receive a {@link #ACTION_APPWIDGET_UPDATE} + * broadcast. + * * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding * widgets always for your component. Should be used by apps that host widgets; if this * method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to @@ -983,6 +986,9 @@ public class AppWidgetManager { /** * Set the component for a given appWidgetId. * + * If successful, the app widget provider will receive a {@link #ACTION_APPWIDGET_UPDATE} + * broadcast. + * * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding * widgets always for your component. Should be used by apps that host widgets; if this * method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to @@ -1006,6 +1012,10 @@ public class AppWidgetManager { /** * Set the provider for a given appWidgetId if the caller has a permission. + * + * If successful, the app widget provider will receive a {@link #ACTION_APPWIDGET_UPDATE} + * broadcast. + * * <p> * <strong>Note:</strong> You need the {@link android.Manifest.permission#BIND_APPWIDGET} * permission or the user must have enabled binding widgets always for your component. diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 0662f160a009..8dfb787bb3ab 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -846,6 +846,14 @@ public final class OutputConfiguration implements Parcelable { return 0; } + private static int[] convertIntegerToIntList(List<Integer> integerList) { + int[] integerArray = new int[integerList.size()]; + for (int i = 0; i < integerList.size(); i++) { + integerArray[i] = integerList.get(i); + } + return integerArray; + } + @Override public void writeToParcel(Parcel dest, int flags) { if (dest == null) { @@ -861,7 +869,9 @@ public final class OutputConfiguration implements Parcelable { dest.writeTypedList(mSurfaces); dest.writeString(mPhysicalCameraId); dest.writeInt(mIsMultiResolution ? 1 : 0); - dest.writeList(mSensorPixelModesUsed); + // writeList doesn't seem to work well with Integer list. + dest.writeIntArray(convertIntegerToIntList(mSensorPixelModesUsed)); + //dest.writeArray(mSensorPixelModesUsed.toArray()); } /** diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl index d2cb5bfe6910..f18360ff4108 100644 --- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl +++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl @@ -35,6 +35,10 @@ oneway interface IUdfpsOverlayController { // Hides the overlay. void hideUdfpsOverlay(int sensorId); + // Good image captured. Turn off HBM. Success/Reject comes after, which is when hideUdfpsOverlay + // will be called. + void onAcquiredGood(int sensorId); + // Notifies of enrollment progress changes. void onEnrollmentProgress(int sensorId, int remaining); diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index fd446cdf5c7f..249154aa9129 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -514,6 +514,10 @@ public class NetworkTemplate implements Parcelable { return mSubscriberIdMatchRule; } + public int getMeteredness() { + return mMetered; + } + /** * Test if given {@link NetworkIdentity} matches this template. */ diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 9d1fd50a4112..3aa0bcb6abee 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -2107,6 +2107,8 @@ public final class PowerManager { * Return whether the given application package name is on the device's power allowlist. * Apps can be placed on the allowlist through the settings UI invoked by * {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}. + * <p>Being on the power allowlist means that the system will not apply most power saving + * features to the app. Guardrails for extreme cases may still be applied. */ public boolean isIgnoringBatteryOptimizations(String packageName) { return getPowerWhitelistManager().isWhitelisted(packageName, true); diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 781800dd49ce..a0cbbfe3327b 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -182,6 +182,11 @@ public abstract class VibrationEffect implements Parcelable { * @return The desired effect. */ public static VibrationEffect createOneShot(long milliseconds, int amplitude) { + if (amplitude == 0) { + throw new IllegalArgumentException( + "amplitude must either be DEFAULT_AMPLITUDE, " + + "or between 1 and 255 inclusive (amplitude=" + amplitude + ")"); + } return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */); } @@ -581,22 +586,16 @@ public abstract class VibrationEffect implements Parcelable { public void validate() { int segmentCount = mSegments.size(); boolean hasNonZeroDuration = false; - boolean hasNonZeroAmplitude = false; for (int i = 0; i < segmentCount; i++) { VibrationEffectSegment segment = mSegments.get(i); segment.validate(); // A segment with unknown duration = -1 still counts as a non-zero duration. hasNonZeroDuration |= segment.getDuration() != 0; - hasNonZeroAmplitude |= segment.hasNonZeroAmplitude(); } if (!hasNonZeroDuration) { throw new IllegalArgumentException("at least one timing must be non-zero" + " (segments=" + mSegments + ")"); } - if (!hasNonZeroAmplitude) { - throw new IllegalArgumentException("at least one amplitude must be non-zero" - + " (segments=" + mSegments + ")"); - } if (mRepeatIndex != -1) { Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1, "repeat index must be within the bounds of the segments (segments.length=" diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 0b99b85fdf24..8854e270eed6 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -17,6 +17,7 @@ package android.permission; import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED; +import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED; import static android.permission.PermissionControllerManager.COUNT_ONLY_WHEN_GRANTED; import static android.permission.PermissionControllerManager.COUNT_WHEN_SYSTEM; @@ -510,7 +511,7 @@ public abstract class PermissionControllerService extends Service { String callerPackageName, AdminPermissionControlParams params, AndroidFuture callback) { checkStringNotEmpty(callerPackageName); - if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) { + if (params.getGrantState() == PERMISSION_GRANT_STATE_GRANTED) { enforceSomePermissionsGrantedToCaller( Manifest.permission.GRANT_RUNTIME_PERMISSIONS); } @@ -542,6 +543,9 @@ public abstract class PermissionControllerService extends Service { public void updateUserSensitiveForApp(int uid, @NonNull AndroidFuture callback) { Preconditions.checkNotNull(callback, "callback cannot be null"); + enforceSomePermissionsGrantedToCaller( + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY); + try { onUpdateUserSensitivePermissionFlags(uid, () -> callback.complete(null)); } catch (Exception e) { @@ -608,9 +612,7 @@ public abstract class PermissionControllerService extends Service { try { Objects.requireNonNull(permissionGroupName); Objects.requireNonNull(callback); - PermissionControllerService - .this - .onGetGroupOfPlatformPermission( + PermissionControllerService.this.onGetGroupOfPlatformPermission( permissionGroupName, callback::complete); } catch (Throwable t) { callback.completeExceptionally(t); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e3302d1ca9a0..36bc3e779c12 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2773,6 +2773,7 @@ public final class Settings { private final ArraySet<String> mReadableFields; private final ArraySet<String> mAllFields; + private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk; @GuardedBy("this") private GenerationTracker mGenerationTracker; @@ -2794,7 +2795,9 @@ public final class Settings { mProviderHolder = providerHolder; mReadableFields = new ArraySet<>(); mAllFields = new ArraySet<>(); - getPublicSettingsForClass(callerClass, mAllFields, mReadableFields); + mReadableFieldsWithMaxTargetSdk = new ArrayMap<>(); + getPublicSettingsForClass(callerClass, mAllFields, mReadableFields, + mReadableFieldsWithMaxTargetSdk); } public boolean putStringForUser(ContentResolver cr, String name, String value, @@ -2851,13 +2854,34 @@ public final class Settings { // Settings.Global and is not annotated as @Readable. // Notice that a key string that is not defined in any of the Settings.* classes will // still be regarded as readable. - if (!isCallerExemptFromReadableRestriction() - && mAllFields.contains(name) && !mReadableFields.contains(name)) { - throw new SecurityException( - "Settings key: <" + name + "> is not readable. From S+, settings keys " - + "annotated with @hide are restricted to system_server and system " - + "apps only, unless they are annotated with @Readable."); + if (!isCallerExemptFromReadableRestriction() && mAllFields.contains(name)) { + if (!mReadableFields.contains(name)) { + throw new SecurityException( + "Settings key: <" + name + "> is not readable. From S+, settings keys " + + "annotated with @hide are restricted to system_server and " + + "system apps only, unless they are annotated with @Readable." + ); + } else { + // When the target settings key has @Readable annotation, if the caller app's + // target sdk is higher than the maxTargetSdk of the annotation, reject access. + if (mReadableFieldsWithMaxTargetSdk.containsKey(name)) { + final int maxTargetSdk = mReadableFieldsWithMaxTargetSdk.get(name); + final Application application = ActivityThread.currentApplication(); + final boolean targetSdkCheckOk = application != null + && application.getApplicationInfo() != null + && application.getApplicationInfo().targetSdkVersion + <= maxTargetSdk; + if (!targetSdkCheckOk) { + throw new SecurityException( + "Settings key: <" + name + "> is only readable to apps with " + + "targetSdkVersion lower than or equal to: " + + maxTargetSdk + ); + } + } + } } + final boolean isSelf = (userHandle == UserHandle.myUserId()); int currentGeneration = -1; if (isSelf) { @@ -3225,10 +3249,12 @@ public final class Settings { @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) private @interface Readable { + int maxTargetSdk() default 0; } private static <T extends NameValueTable> void getPublicSettingsForClass( - Class<T> callerClass, Set<String> allKeys, Set<String> readableKeys) { + Class<T> callerClass, Set<String> allKeys, Set<String> readableKeys, + ArrayMap<String, Integer> keysWithMaxTargetSdk) { final Field[] allFields = callerClass.getDeclaredFields(); try { for (int i = 0; i < allFields.length; i++) { @@ -3241,8 +3267,15 @@ public final class Settings { continue; } allKeys.add((String) value); - if (field.getAnnotation(Readable.class) != null) { - readableKeys.add((String) value); + final Readable annotation = field.getAnnotation(Readable.class); + + if (annotation != null) { + final String key = (String) value; + final int maxTargetSdk = annotation.maxTargetSdk(); + readableKeys.add(key); + if (maxTargetSdk != 0) { + keysWithMaxTargetSdk.put(key, maxTargetSdk); + } } } } catch (IllegalAccessException ignored) { @@ -3404,8 +3437,10 @@ public final class Settings { } /** @hide */ - public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys) { - getPublicSettingsForClass(System.class, allKeys, readableKeys); + public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys, + ArrayMap<String, Integer> readableKeysWithMaxTargetSdk) { + getPublicSettingsForClass(System.class, allKeys, readableKeys, + readableKeysWithMaxTargetSdk); } /** @@ -5734,8 +5769,10 @@ public final class Settings { } /** @hide */ - public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys) { - getPublicSettingsForClass(Secure.class, allKeys, readableKeys); + public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys, + ArrayMap<String, Integer> readableKeysWithMaxTargetSdk) { + getPublicSettingsForClass(Secure.class, allKeys, readableKeys, + readableKeysWithMaxTargetSdk); } /** @@ -14965,8 +15002,10 @@ public final class Settings { } /** @hide */ - public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys) { - getPublicSettingsForClass(Global.class, allKeys, readableKeys); + public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys, + ArrayMap<String, Integer> readableKeysWithMaxTargetSdk) { + getPublicSettingsForClass(Global.class, allKeys, readableKeys, + readableKeysWithMaxTargetSdk); } /** diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java index 09b731091bfd..7dd85cc8f988 100644 --- a/core/java/android/service/smartspace/SmartspaceService.java +++ b/core/java/android/service/smartspace/SmartspaceService.java @@ -122,14 +122,18 @@ public abstract class SmartspaceService extends Service { @Override public void onCreate() { super.onCreate(); - Log.d(TAG, "onCreate mSessionCallbacks: " + mSessionCallbacks); + if (DEBUG) { + Log.d(TAG, "onCreate mSessionCallbacks: " + mSessionCallbacks); + } mHandler = new Handler(Looper.getMainLooper(), null, true); } @Override @NonNull public final IBinder onBind(@NonNull Intent intent) { - Log.d(TAG, "onBind mSessionCallbacks: " + mSessionCallbacks); + if (DEBUG) { + Log.d(TAG, "onBind mSessionCallbacks: " + mSessionCallbacks); + } if (SERVICE_INTERFACE.equals(intent.getAction())) { return mInterface.asBinder(); } @@ -140,7 +144,9 @@ public abstract class SmartspaceService extends Service { private void doCreateSmartspaceSession(@NonNull SmartspaceConfig config, @NonNull SmartspaceSessionId sessionId) { - Log.d(TAG, "doCreateSmartspaceSession mSessionCallbacks: " + mSessionCallbacks); + if (DEBUG) { + Log.d(TAG, "doCreateSmartspaceSession mSessionCallbacks: " + mSessionCallbacks); + } mSessionCallbacks.put(sessionId, new ArrayList<>()); onCreateSmartspaceSession(config, sessionId); } @@ -166,7 +172,9 @@ public abstract class SmartspaceService extends Service { private void doRegisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId, @NonNull ISmartspaceCallback callback) { - Log.d(TAG, "doRegisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks); + if (DEBUG) { + Log.d(TAG, "doRegisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks); + } final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId); if (callbacks == null) { Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId); @@ -184,7 +192,9 @@ public abstract class SmartspaceService extends Service { private void doUnregisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId, @NonNull ISmartspaceCallback callback) { - Log.d(TAG, "doUnregisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks); + if (DEBUG) { + Log.d(TAG, "doUnregisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks); + } final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId); if (callbacks == null) { Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId); @@ -198,7 +208,9 @@ public abstract class SmartspaceService extends Service { } private void doRequestPredictionUpdate(@NonNull SmartspaceSessionId sessionId) { - Log.d(TAG, "doRequestPredictionUpdate mSessionCallbacks: " + mSessionCallbacks); + if (DEBUG) { + Log.d(TAG, "doRequestPredictionUpdate mSessionCallbacks: " + mSessionCallbacks); + } // Just an optimization, if there are no callbacks, then don't bother notifying the service final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId); if (callbacks != null && !callbacks.isEmpty()) { @@ -246,7 +258,9 @@ public abstract class SmartspaceService extends Service { */ public final void updateSmartspaceTargets(@NonNull SmartspaceSessionId sessionId, @NonNull List<SmartspaceTarget> targets) { - Log.d(TAG, "updateSmartspaceTargets mSessionCallbacks: " + mSessionCallbacks); + if (DEBUG) { + Log.d(TAG, "updateSmartspaceTargets mSessionCallbacks: " + mSessionCallbacks); + } List<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId); if (callbacks != null) { for (CallbackWrapper callback : callbacks) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 908d236c6c02..8138b3d30ddf 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4727,9 +4727,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback; /** - * This lives here since it's only valid for interactive views. + * This lives here since it's only valid for interactive views. This list is null until the + * first use. */ - private List<Rect> mSystemGestureExclusionRects; + private List<Rect> mSystemGestureExclusionRects = null; /** * Used to track {@link #mSystemGestureExclusionRects} @@ -11603,8 +11604,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * a precision touch gesture in a small area in either the X or Y dimension, such as * an edge swipe or dragging a <code>SeekBar</code> thumb.</p> * - * <p>Do not modify the provided list after this method is called.</p> - * * <p>Note: the system will put a limit of <code>200dp</code> on the vertical extent of the * exclusions it takes into account. The limit does not apply while the navigation * bar is {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY stickily} hidden, nor to the @@ -11618,13 +11617,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (rects.isEmpty() && mListenerInfo == null) return; final ListenerInfo info = getListenerInfo(); + if (info.mSystemGestureExclusionRects != null) { + info.mSystemGestureExclusionRects.clear(); + info.mSystemGestureExclusionRects.addAll(rects); + } else { + info.mSystemGestureExclusionRects = new ArrayList<>(rects); + } if (rects.isEmpty()) { - info.mSystemGestureExclusionRects = null; if (info.mPositionUpdateListener != null) { mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener); } } else { - info.mSystemGestureExclusionRects = rects; if (info.mPositionUpdateListener == null) { info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() { @Override diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index afd6878da429..67cf85cee9ac 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2783,7 +2783,7 @@ public final class ViewRootImpl implements ViewParent, & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0; final boolean dragResizing = freeformResizing || dockedResizing; if (mSurfaceControl.isValid()) { - updateOpacity(params, dragResizing); + updateOpacity(mWindowAttributes, dragResizing); } if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString() @@ -7749,9 +7749,10 @@ public final class ViewRootImpl implements ViewParent, return relayoutResult; } - private void updateOpacity(@Nullable WindowManager.LayoutParams params, boolean dragResizing) { + private void updateOpacity(WindowManager.LayoutParams params, boolean dragResizing) { boolean opaque = false; - if (params != null && !PixelFormat.formatHasAlpha(params.format) + + if (!PixelFormat.formatHasAlpha(params.format) // Don't make surface with surfaceInsets opaque as they display a // translucent shadow. && params.surfaceInsets.left == 0 diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java index ea3f1b1fc04c..40ada0b2ffdc 100644 --- a/core/java/com/android/internal/app/AbstractResolverComparator.java +++ b/core/java/com/android/internal/app/AbstractResolverComparator.java @@ -64,6 +64,7 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC private static final int WATCHDOG_TIMEOUT_MILLIS = 500; private final Comparator<ResolveInfo> mAzComparator; + private ChooserActivityLogger mChooserActivityLogger; protected final Handler mHandler = new Handler(Looper.getMainLooper()) { public void handleMessage(Message msg) { @@ -85,6 +86,9 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC } mHandler.removeMessages(RANKER_SERVICE_RESULT); afterCompute(); + if (mChooserActivityLogger != null) { + mChooserActivityLogger.logSharesheetAppShareRankingTimeout(); + } break; default: @@ -131,6 +135,14 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC mAfterCompute = afterCompute; } + void setChooserActivityLogger(ChooserActivityLogger chooserActivityLogger) { + mChooserActivityLogger = chooserActivityLogger; + } + + ChooserActivityLogger getChooserActivityLogger() { + return mChooserActivityLogger; + } + protected final void afterCompute() { final AfterCompute afterCompute = mAfterCompute; if (afterCompute != null) { diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java index b76ef0fd4ed8..bc9eff04636d 100644 --- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java +++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java @@ -61,17 +61,19 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator private ResolverRankerServiceResolverComparator mResolverRankerService; AppPredictionServiceResolverComparator( - Context context, - Intent intent, - String referrerPackage, - AppPredictor appPredictor, - UserHandle user) { + Context context, + Intent intent, + String referrerPackage, + AppPredictor appPredictor, + UserHandle user, + ChooserActivityLogger chooserActivityLogger) { super(context, intent); mContext = context; mIntent = intent; mAppPredictor = appPredictor; mUser = user; mReferrerPackage = referrerPackage; + setChooserActivityLogger(chooserActivityLogger); } @Override @@ -116,8 +118,9 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator // APS for chooser is disabled. Fallback to resolver. mResolverRankerService = new ResolverRankerServiceResolverComparator( - mContext, mIntent, mReferrerPackage, - () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT)); + mContext, mIntent, mReferrerPackage, + () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT), + getChooserActivityLogger()); mResolverRankerService.compute(targets); } else { Log.i(TAG, "AppPredictionService response received"); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 28c2774cabb0..08db74f5e351 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -2591,7 +2591,8 @@ public class ChooserActivity extends ResolverActivity implements boolean filterLastUsed, ResolverListController resolverListController) { return new ChooserListAdapter(context, payloadIntents, initialIntents, rList, filterLastUsed, resolverListController, this, - this, context.getPackageManager()); + this, context.getPackageManager(), + getChooserActivityLogger()); } @VisibleForTesting @@ -2600,11 +2601,11 @@ public class ChooserActivity extends ResolverActivity implements AbstractResolverComparator resolverComparator; if (appPredictor != null) { resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(), - getReferrerPackageName(), appPredictor, userHandle); + getReferrerPackageName(), appPredictor, userHandle, getChooserActivityLogger()); } else { resolverComparator = new ResolverRankerServiceResolverComparator(this, getTargetIntent(), - getReferrerPackageName(), null); + getReferrerPackageName(), null, getChooserActivityLogger()); } return new ChooserListController( diff --git a/core/java/com/android/internal/app/ChooserActivityLogger.java b/core/java/com/android/internal/app/ChooserActivityLogger.java index 47d83346d038..321730786471 100644 --- a/core/java/com/android/internal/app/ChooserActivityLogger.java +++ b/core/java/com/android/internal/app/ChooserActivityLogger.java @@ -75,6 +75,20 @@ public interface ChooserActivityLogger { } /** + * Logs a UiEventReported event for the system sharesheet app share ranking timing out. + */ + default void logSharesheetAppShareRankingTimeout() { + log(SharesheetStandardEvent.SHARESHEET_APP_SHARE_RANKING_TIMEOUT, getInstanceId()); + } + + /** + * Logs a UiEventReported event for the system sharesheet when direct share row is empty. + */ + default void logSharesheetEmptyDirectShareRow() { + log(SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW, getInstanceId()); + } + + /** * Logs a UiEventReported event for a given share activity * @param event * @param instanceId @@ -168,7 +182,11 @@ public interface ChooserActivityLogger { @UiEvent(doc = "Sharesheet direct targets is fully populated.") SHARESHEET_DIRECT_LOAD_COMPLETE(323), @UiEvent(doc = "Sharesheet direct targets timed out.") - SHARESHEET_DIRECT_LOAD_TIMEOUT(324); + SHARESHEET_DIRECT_LOAD_TIMEOUT(324), + @UiEvent(doc = "Sharesheet app share ranking timed out.") + SHARESHEET_APP_SHARE_RANKING_TIMEOUT(831), + @UiEvent(doc = "Sharesheet empty direct share row.") + SHARESHEET_EMPTY_DIRECT_SHARE_ROW(828); private final int mId; SharesheetStandardEvent(int id) { diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index cc2b12a99d79..87737ca9c3af 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -79,6 +79,7 @@ public class ChooserListAdapter extends ResolverListAdapter { private final ChooserListCommunicator mChooserListCommunicator; private final SelectableTargetInfo.SelectableTargetInfoCommunicator mSelectableTargetInfoCommunicator; + private final ChooserActivityLogger mChooserActivityLogger; private int mNumShortcutResults = 0; private Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>(); @@ -104,7 +105,8 @@ public class ChooserListAdapter extends ResolverListAdapter { boolean filterLastUsed, ResolverListController resolverListController, ChooserListCommunicator chooserListCommunicator, SelectableTargetInfo.SelectableTargetInfoCommunicator selectableTargetInfoCommunicator, - PackageManager packageManager) { + PackageManager packageManager, + ChooserActivityLogger chooserActivityLogger) { // Don't send the initial intents through the shared ResolverActivity path, // we want to separate them into a different section. super(context, payloadIntents, null, rList, filterLastUsed, @@ -115,6 +117,7 @@ public class ChooserListAdapter extends ResolverListAdapter { mChooserListCommunicator = chooserListCommunicator; createPlaceHolders(); mSelectableTargetInfoCommunicator = selectableTargetInfoCommunicator; + mChooserActivityLogger = chooserActivityLogger; if (initialIntents != null) { for (int i = 0; i < initialIntents.length; i++) { @@ -590,6 +593,7 @@ public class ChooserListAdapter extends ResolverListAdapter { mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo); if (mServiceTargets.isEmpty()) { mServiceTargets.add(new ChooserActivity.EmptyTargetInfo()); + mChooserActivityLogger.logSharesheetEmptyDirectShareRow(); } notifyDataSetChanged(); } diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 2b59907cf86b..10ac1bc6ac3d 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -70,7 +70,7 @@ public class ResolverListController { UserHandle userHandle) { this(context, pm, targetIntent, referrerPackage, launchedFromUid, userHandle, new ResolverRankerServiceResolverComparator( - context, targetIntent, referrerPackage, null)); + context, targetIntent, referrerPackage, null, null)); } public ResolverListController( diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java index 286945037ab7..cb946c0dcf99 100644 --- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java +++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java @@ -85,7 +85,8 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator private CountDownLatch mConnectSignal; public ResolverRankerServiceResolverComparator(Context context, Intent intent, - String referrerPackage, AfterCompute afterCompute) { + String referrerPackage, AfterCompute afterCompute, + ChooserActivityLogger chooserActivityLogger) { super(context, intent); mCollator = Collator.getInstance(context.getResources().getConfiguration().locale); mReferrerPackage = referrerPackage; @@ -97,6 +98,7 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator mAction = intent.getAction(); mRankerServiceName = new ComponentName(mContext, this.getClass()); setCallBack(afterCompute); + setChooserActivityLogger(chooserActivityLogger); } @Override diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl index b01e4a83a438..76aa7a015d2f 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -63,8 +63,10 @@ oneway interface IKeyguardService { * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the reason we're waking up, * such as WAKE_REASON_POWER_BUTTON or WAKE_REASON_GESTURE. + * @param cameraGestureTriggered Whether we're waking up due to a power button double tap + * gesture. */ - void onStartedWakingUp(int pmWakeReason); + void onStartedWakingUp(int pmWakeReason, boolean cameraGestureTriggered); /** * Called when the device has finished waking up. diff --git a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java index a1d202e3a39f..a360f63e97d4 100644 --- a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java +++ b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java @@ -125,10 +125,19 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou view.getScrollX() - contentView.getLeft(), view.getScrollY() - contentView.getTop()); + Rect input = new Rect(requestedContentBounds); + + // Expand input rect to get the requested rect to be in the center + int remainingHeight = view.getHeight() - view.getPaddingTop() + - view.getPaddingBottom() - input.height(); + if (remainingHeight > 0) { + input.inset(0, -remainingHeight / 2); + } + // requestRect is now local to contentView as requestedContentBounds // contentView (and each parent in turn if possible) will be scrolled // (if necessary) to make all of requestedContent visible, (if possible!) - contentView.requestRectangleOnScreen(new Rect(requestedContentBounds), true); + contentView.requestRectangleOnScreen(input, true); // update new offset between starting and current scroll position scrollDelta = view.getScrollY() - mStartScrollY; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 1ddff3272b11..e5f458849b9a 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1792,6 +1792,9 @@ <!-- Boolean indicating if placing the phone face down will result in a screen off. --> <bool name="config_flipToScreenOffEnabled">true</bool> + <!-- Integer to set a max latency the accelerometer will batch sensor requests with. --> + <integer name="config_flipToScreenOffMaxLatencyMicros">2000000</integer> + <!-- Boolean indicating if current platform supports bluetooth SCO for off call use cases --> <bool name="config_bluetooth_sco_off_call">true</bool> @@ -1968,6 +1971,9 @@ <string name="config_systemActivityRecognizer" translatable="false"></string> <!-- The name of the package that will hold the system ui role --> <string name="config_systemUi" translatable="false">com.android.systemui</string> + <!-- The name of the package that will hold the television remote service role. + TODO(b/189347385) make this a @SystemAPI --> + <string name="config_systemTelevisionRemoteService" translatable="false">@string/config_tvRemoteServicePackage</string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index d3343060a819..ea93520502d5 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -939,4 +939,8 @@ <!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. @removed --> <dimen name="__removed_system_app_widget_internal_padding">16dp</dimen> + <!-- The width/height of the icon view on staring surface. --> + <dimen name="starting_surface_icon_size">160dp</dimen> + <!-- The default width/height of the icon on the spec of adaptive icon drawable. --> + <dimen name="starting_surface_default_icon_size">108dp</dimen> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4da5859fa49b..374cea77f6a0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -276,6 +276,7 @@ <java-symbol type="bool" name="config_avoidGfxAccel" /> <java-symbol type="bool" name="config_bluetooth_address_validation" /> <java-symbol type="bool" name="config_flipToScreenOffEnabled" /> + <java-symbol type="integer" name="config_flipToScreenOffMaxLatencyMicros" /> <java-symbol type="bool" name="config_bluetooth_sco_off_call" /> <java-symbol type="bool" name="config_bluetooth_le_peripheral_mode_supported" /> <java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" /> @@ -4388,4 +4389,7 @@ <java-symbol type="bool" name="config_supportsMicToggle" /> <java-symbol type="bool" name="config_supportsCamToggle" /> + + <java-symbol type="dimen" name="starting_surface_icon_size" /> + <java-symbol type="dimen" name="starting_surface_default_icon_size" /> </resources> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 97acd5b9bf1e..9d0a42d8ecd4 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -232,6 +232,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -261,6 +264,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -307,6 +313,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -355,6 +364,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -402,6 +414,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -464,6 +479,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -503,6 +521,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -548,6 +569,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -594,6 +618,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -656,6 +683,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -703,6 +733,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -748,6 +781,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -795,6 +831,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -841,6 +880,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -887,6 +929,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -933,6 +978,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -979,6 +1027,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1029,6 +1080,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -1076,6 +1130,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -1120,6 +1177,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -1318,6 +1378,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1348,6 +1411,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1393,6 +1459,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1439,6 +1508,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1487,6 +1559,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1534,6 +1609,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1598,6 +1676,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1636,6 +1717,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1684,6 +1768,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1733,6 +1820,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1781,6 +1871,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1811,6 +1904,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1842,6 +1938,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1892,6 +1991,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1940,6 +2042,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -1987,6 +2092,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -2033,6 +2141,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -2079,6 +2190,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -2123,6 +2237,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -2285,6 +2402,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> @@ -2330,6 +2450,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -2385,6 +2508,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> @@ -2433,6 +2559,9 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item> <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> <item name="colorForeground">@color/foreground_device_default_light</item> <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java index 009665fa1f53..6cbfffc96116 100644 --- a/core/tests/coretests/src/android/os/VibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java @@ -102,7 +102,9 @@ public class VibrationEffectTest { assertThrows(IllegalArgumentException.class, () -> VibrationEffect.createOneShot(1, -2).validate()); assertThrows(IllegalArgumentException.class, - () -> VibrationEffect.createOneShot(1, 256).validate()); + () -> VibrationEffect.createOneShot(1, 0).validate()); + assertThrows(IllegalArgumentException.class, + () -> VibrationEffect.createOneShot(-1, 255).validate()); } @Test @@ -117,6 +119,7 @@ public class VibrationEffectTest { @Test public void testValidateWaveform() { VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1).validate(); + VibrationEffect.createWaveform(new long[]{10, 10}, new int[] {0, 0}, -1).validate(); VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0).validate(); VibrationEffect.startWaveform() .addStep(/* amplitude= */ 1, /* duration= */ 10) diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index 19c27639a00d..6e34a3322e35 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -1857,6 +1857,57 @@ public class ChooserActivityTest { } @Test + public void testEmptyDirectRowLogging() throws InterruptedException { + Intent sendIntent = createSendTextIntent(); + // We need app targets for direct targets to get displayed + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + + // Start activity + final ChooserWrapperActivity activity = mActivityRule + .launchActivity(Intent.createChooser(sendIntent, null)); + + // Thread.sleep shouldn't be a thing in an integration test but it's + // necessary here because of the way the code is structured + Thread.sleep(3000); + + assertThat("Chooser should have 2 app targets", + activity.getAdapter().getCount(), is(2)); + assertThat("Chooser should have no direct targets", + activity.getAdapter().getSelectableServiceTargetCount(), is(0)); + + ChooserActivityLoggerFake logger = + (ChooserActivityLoggerFake) activity.getChooserActivityLogger(); + assertThat(logger.numCalls(), is(6)); + // first one should be SHARESHEET_TRIGGERED uievent + assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(0).event.getId(), + is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId())); + // second one should be SHARESHEET_STARTED event + assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); + assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); + assertThat(logger.get(1).mimeType, is("text/plain")); + assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).appProvidedApp, is(0)); + assertThat(logger.get(1).appProvidedDirect, is(0)); + assertThat(logger.get(1).isWorkprofile, is(false)); + assertThat(logger.get(1).previewType, is(3)); + // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent + assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(2).event.getId(), + is(ChooserActivityLogger + .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId())); + // fourth and fifth are just artifacts of test set-up + // sixth one should be ranking atom with SHARESHEET_EMPTY_DIRECT_SHARE_ROW event + assertThat(logger.get(5).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(5).event.getId(), + is(ChooserActivityLogger + .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId())); + } + + @Test public void testCopyTextToClipboardLogging() throws Exception { Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index 16a2fbd6465e..9fcab0923f2d 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -68,7 +68,8 @@ public class ChooserWrapperActivity extends ChooserActivity { : sOverrides.packageManager; return new ChooserListAdapter(context, payloadIntents, initialIntents, rList, filterLastUsed, resolverListController, - this, this, packageManager); + this, this, packageManager, + getChooserActivityLogger()); } ChooserListAdapter getAdapter() { diff --git a/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java b/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java index ab13fd7d81e0..fc02ea9aee22 100644 --- a/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java +++ b/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java @@ -164,7 +164,8 @@ public class ScrollViewCaptureHelperTest { assertRectEquals(request, scrollResult.requestedArea); assertRectEquals(request, scrollResult.availableArea); assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); - assertEquals(CAPTURE_HEIGHT, scrollResult.scrollDelta); + assertEquals(CAPTURE_HEIGHT + (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2, + scrollResult.scrollDelta); } @Test @@ -182,7 +183,8 @@ public class ScrollViewCaptureHelperTest { assertRectEquals(request, scrollResult.requestedArea); assertRectEquals(request, scrollResult.availableArea); assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); - assertEquals(-CAPTURE_HEIGHT, scrollResult.scrollDelta); + assertEquals(-CAPTURE_HEIGHT - (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2, + scrollResult.scrollDelta); } @Test @@ -201,7 +203,8 @@ public class ScrollViewCaptureHelperTest { assertRectEquals(request, scrollResult.requestedArea); assertRectEquals(request, scrollResult.availableArea); assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); - assertEquals(CAPTURE_HEIGHT, scrollResult.scrollDelta); + assertEquals(CAPTURE_HEIGHT + (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2, + scrollResult.scrollDelta); } @@ -220,7 +223,8 @@ public class ScrollViewCaptureHelperTest { assertRectEquals(request, scrollResult.requestedArea); assertRectEquals(request, scrollResult.availableArea); assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); - assertEquals(-CAPTURE_HEIGHT, scrollResult.scrollDelta); + assertEquals(-CAPTURE_HEIGHT - (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2, + scrollResult.scrollDelta); } @Test diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 8d4739dbc255..99d71675ffa9 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -153,7 +153,6 @@ applications that come with the platform <permission name="android.permission.PACKAGE_USAGE_STATS" /> <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> <permission name="android.permission.MODIFY_AUDIO_ROUTING" /> - <permission name="android.permission.GET_RUNTIME_PERMISSION_GROUP_MAPPING" /> </privapp-permissions> <privapp-permissions package="com.android.phone"> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index ce8ce51b1d5e..3caff35c8a9d 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -180,11 +180,6 @@ individual_bubble_size + some padding. --> <dimen name="bubble_stack_user_education_side_inset">72dp</dimen> - <!-- The width/height of the icon view on staring surface. --> - <dimen name="starting_surface_icon_size">160dp</dimen> - <!-- The default width/height of the icon on the spec of adaptive icon drawable. --> - <dimen name="default_icon_size">108dp</dimen> - <!-- The width/height of the size compat restart button. --> <dimen name="size_compat_button_size">48dp</dimen> 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 536165b4d29f..7e48a7e13920 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 @@ -938,6 +938,8 @@ public class BubbleStackView extends FrameLayout if (ev.getAction() == MotionEvent.ACTION_DOWN) { if (mShowingManage) { showManageMenu(false /* show */); + } else if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) { + mStackEduView.hide(false); } else if (mBubbleData.isExpanded()) { mBubbleData.setExpanded(false); } @@ -1152,6 +1154,7 @@ public class BubbleStackView extends FrameLayout mStackEduView = new StackEducationView(mContext); addView(mStackEduView); } + mBubbleContainer.bringToFront(); return mStackEduView.show(mPositioner.getDefaultStartPosition()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 093c272193a3..1d37a128da0c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -124,9 +124,9 @@ public class SplashscreenContentDrawer { private void updateDensity() { mIconSize = mContext.getResources().getDimensionPixelSize( - com.android.wm.shell.R.dimen.starting_surface_icon_size); + com.android.internal.R.dimen.starting_surface_icon_size); mDefaultIconSize = mContext.getResources().getDimensionPixelSize( - com.android.wm.shell.R.dimen.default_icon_size); + com.android.internal.R.dimen.starting_surface_default_icon_size); mBrandingImageWidth = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.starting_surface_brand_image_width); mBrandingImageHeight = mContext.getResources().getDimensionPixelSize( @@ -135,7 +135,7 @@ public class SplashscreenContentDrawer { com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length); } - private int getSystemBGColor() { + private static int getSystemBGColor() { final Context systemContext = ActivityThread.currentApplication(); if (systemContext == null) { Slog.e(TAG, "System context does not exist!"); @@ -145,17 +145,18 @@ public class SplashscreenContentDrawer { return res.getColor(com.android.wm.shell.R.color.splash_window_background_default); } - private Drawable createDefaultBackgroundDrawable() { + private static Drawable createDefaultBackgroundDrawable() { return new ColorDrawable(getSystemBGColor()); } - private @ColorInt int peekWindowBGColor(Context context) { + /** Extract the window background color from {@code attrs}. */ + public static int peekWindowBGColor(Context context, SplashScreenWindowAttrs attrs) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor"); final Drawable themeBGDrawable; - if (mTmpAttrs.mWindowBgColor != 0) { - themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor); - } else if (mTmpAttrs.mWindowBgResId != 0) { - themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId); + if (attrs.mWindowBgColor != 0) { + themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor); + } else if (attrs.mWindowBgResId != 0) { + themeBGDrawable = context.getDrawable(attrs.mWindowBgResId); } else { themeBGDrawable = createDefaultBackgroundDrawable(); Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable); @@ -165,7 +166,7 @@ public class SplashscreenContentDrawer { return estimatedWindowBGColor; } - private int estimateWindowBGColor(Drawable themeBGDrawable) { + private static int estimateWindowBGColor(Drawable themeBGDrawable) { final DrawableColorTester themeBGTester = new DrawableColorTester(themeBGDrawable, true /* filterTransparent */); if (themeBGTester.nonTransparentRatio() == 0) { @@ -183,7 +184,7 @@ public class SplashscreenContentDrawer { getWindowAttrs(context, mTmpAttrs); final StartingWindowViewBuilder builder = new StartingWindowViewBuilder(); - final int themeBGColor = peekWindowBGColor(context); + final int themeBGColor = peekWindowBGColor(context, this.mTmpAttrs); // TODO (b/173975965) Tracking the performance on improved splash screen. return builder .setContext(context) @@ -193,7 +194,11 @@ public class SplashscreenContentDrawer { .build(); } - private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) { + /** + * Get the {@link SplashScreenWindowAttrs} from {@code context} and fill them into + * {@code attrs}. + */ + public static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) { final TypedArray typedArray = context.obtainStyledAttributes( com.android.internal.R.styleable.Window); attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0); @@ -216,7 +221,8 @@ public class SplashscreenContentDrawer { } } - private static class SplashScreenWindowAttrs { + /** The configuration of the splash screen window. */ + public static class SplashScreenWindowAttrs { private int mWindowBgResId = 0; private int mWindowBgColor = Color.TRANSPARENT; private Drawable mReplaceIcon = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index bdc8ff1f9882..ff91d827b280 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -29,6 +29,7 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.IBinder; @@ -215,6 +216,7 @@ public class StartingSurfaceDrawer { WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); params.setFitInsetsSides(0); params.setFitInsetsTypes(0); + params.format = PixelFormat.TRANSLUCENT; int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN diff --git a/libs/androidfw/TEST_MAPPING b/libs/androidfw/TEST_MAPPING index 8a5c06d1eef9..9ebc9969a730 100644 --- a/libs/androidfw/TEST_MAPPING +++ b/libs/androidfw/TEST_MAPPING @@ -2,9 +2,6 @@ "presubmit": [ { "name": "CtsResourcesLoaderTests" - }, - { - "name": "ResourcesHardeningTest" } ] -}
\ No newline at end of file +} diff --git a/packages/CtsShim/apk/arm/CtsShim.apk b/packages/CtsShim/apk/arm/CtsShim.apk Binary files differindex ca69a28a04b4..bb6dfa3a4ede 100644 --- a/packages/CtsShim/apk/arm/CtsShim.apk +++ b/packages/CtsShim/apk/arm/CtsShim.apk diff --git a/packages/CtsShim/apk/arm/CtsShimPriv.apk b/packages/CtsShim/apk/arm/CtsShimPriv.apk Binary files differindex d7cfb969c728..2835d57474d9 100644 --- a/packages/CtsShim/apk/arm/CtsShimPriv.apk +++ b/packages/CtsShim/apk/arm/CtsShimPriv.apk diff --git a/packages/CtsShim/apk/x86/CtsShim.apk b/packages/CtsShim/apk/x86/CtsShim.apk Binary files differindex ca69a28a04b4..bb6dfa3a4ede 100644 --- a/packages/CtsShim/apk/x86/CtsShim.apk +++ b/packages/CtsShim/apk/x86/CtsShim.apk diff --git a/packages/CtsShim/apk/x86/CtsShimPriv.apk b/packages/CtsShim/apk/x86/CtsShimPriv.apk Binary files differindex 84c34010a212..2e1a78989b70 100644 --- a/packages/CtsShim/apk/x86/CtsShimPriv.apk +++ b/packages/CtsShim/apk/x86/CtsShimPriv.apk diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml index f0cdaf603ba1..626fd3aa49f5 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml @@ -16,10 +16,12 @@ --> <resources> <!-- Collapsing toolbar layout dimensions --> - <dimen name="toolbar_one_line_height">226dp</dimen> - <dimen name="toolbar_two_lines_height">270dp</dimen> - <dimen name="toolbar_three_lines_height">314dp</dimen> + <dimen name="toolbar_one_line_height">216dp</dimen> + <dimen name="toolbar_two_lines_height">260dp</dimen> + <dimen name="toolbar_three_lines_height">304dp</dimen> <dimen name="scrim_visible_height_trigger">174dp</dimen> + <dimen name="scrim_visible_height_trigger_two_lines">218dp</dimen> + <dimen name="scrim_visible_height_trigger_three_lines">262dp</dimen> <dimen name="expanded_title_margin_start">24dp</dimen> <dimen name="expanded_title_margin_end">24dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/AdjustableToolbarLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/AdjustableToolbarLayout.java index b3053ac76b55..0e7e595a3f04 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/AdjustableToolbarLayout.java +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/AdjustableToolbarLayout.java @@ -26,12 +26,9 @@ import androidx.annotation.Nullable; import com.google.android.material.appbar.CollapsingToolbarLayout; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - /** - * A customized version of CollapsingToolbarLayout that can apply different font size based on - * the line count of its title. + * A customized version of CollapsingToolbarLayout that can apply different font size based on the + * line count of its title. */ public class AdjustableToolbarLayout extends CollapsingToolbarLayout { @@ -59,43 +56,25 @@ public class AdjustableToolbarLayout extends CollapsingToolbarLayout { public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { v.removeOnLayoutChangeListener(this); - final int count = getLineCountWithReflection(); + final int count = getLineCount(); if (count > TOOLBAR_MAX_LINE_NUMBER) { final ViewGroup.LayoutParams lp = getLayoutParams(); lp.height = getResources() .getDimensionPixelSize(R.dimen.toolbar_three_lines_height); + setScrimVisibleHeightTrigger( + getResources().getDimensionPixelSize( + R.dimen.scrim_visible_height_trigger_three_lines)); setLayoutParams(lp); } else if (count == TOOLBAR_MAX_LINE_NUMBER) { final ViewGroup.LayoutParams lp = getLayoutParams(); lp.height = getResources() .getDimensionPixelSize(R.dimen.toolbar_two_lines_height); + setScrimVisibleHeightTrigger( + getResources().getDimensionPixelSize( + R.dimen.scrim_visible_height_trigger_two_lines)); setLayoutParams(lp); } } }); } - - /** - * Returns a number of title line count for CollapsingToolbarLayout so that facilitates the - * determination to apply what kind of font size. Since the title of CollapsingToolbarLayout is - * drawn in a canvas and the text process is wrapped in a CollapsingTextHelper, the way we used - * here is to get the line count from the CollapsingTextHelper via Java Reflection. - */ - private int getLineCountWithReflection() { - try { - final Field textHelperField = - this.getClass().getSuperclass().getDeclaredField("collapsingTextHelper"); - textHelperField.setAccessible(true); - final Object textHelperObj = textHelperField.get(this); - - final Field layoutField = textHelperObj.getClass().getDeclaredField("textLayout"); - layoutField.setAccessible(true); - final Object layoutObj = layoutField.get(textHelperObj); - - final Method method = layoutObj.getClass().getDeclaredMethod("getLineCount"); - return (int) method.invoke(layoutObj); - } catch (Exception e) { - return 0; - } - } } diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml index 010b9ba4324b..2d214fe65c64 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml @@ -48,7 +48,6 @@ android:layout_width="@dimen/settingslib_restricted_icon_size" android:layout_height="@dimen/settingslib_restricted_icon_size" android:tint="?android:attr/colorAccent" - android:theme="@android:style/Theme.Material" android:layout_gravity="center_vertical" android:layout_marginEnd="@dimen/settingslib_restricted_icon_margin_end" android:src="@*android:drawable/ic_info" diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml index c9bc583b209f..89e9a00513df 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml @@ -15,18 +15,18 @@ limitations under the License. --> -<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> +<resources> <!-- Material next state on color--> - <color name="settingslib_state_on_color">?androidprv:attr/colorAccentPrimary</color> + <color name="settingslib_state_on_color">@android:color/system_accent1_100</color> <!-- Material next state off color--> - <color name="settingslib_state_off_color">?androidprv:attr/colorAccentSecondary</color> + <color name="settingslib_state_off_color">@android:color/system_accent2_100</color> <!-- Material next thumb off color--> <color name="settingslib_thumb_off_color">@android:color/system_neutral2_100</color> <!-- Material next track on color--> - <color name="settingslib_track_on_color">?androidprv:attr/colorAccentPrimaryVariant</color> + <color name="settingslib_track_on_color">@android:color/system_accent1_600</color> <!-- Material next track off color--> <color name="settingslib_track_off_color">@android:color/system_neutral2_600</color> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 2dbfef039197..83a6973ffec6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -139,6 +139,17 @@ public class RestrictedPreferenceHelper { } /** + * @return EnforcedAdmin if we have been passed the restriction in the xml. + */ + public EnforcedAdmin checkRestrictionEnforced() { + if (mAttrUserRestriction == null) { + return null; + } + return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, + mAttrUserRestriction, UserHandle.myUserId()); + } + + /** * Disable this preference based on the enforce admin. * * @param admin details of the admin who enforced the restriction. If it is diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java index 6d7f8df8aeb0..a5373944474c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java @@ -16,6 +16,7 @@ package com.android.settingslib.enterprise; +import android.annotation.UserIdInt; import android.content.Context; import androidx.annotation.Nullable; @@ -28,10 +29,15 @@ import com.android.settingslib.RestrictedLockUtils; public interface ActionDisabledByAdminController { /** + * Sets the {@link ActionDisabledLearnMoreButtonLauncher}. + */ + void initialize(ActionDisabledLearnMoreButtonLauncher launcher); + + /** * Handles the adding and setting up of the learn more button. If button is not needed, then * this method can be left empty. */ - void setupLearnMoreButton(Context context, Object alertDialogBuilder); + void setupLearnMoreButton(Context context); /** * Returns the admin support dialog's title resource id. @@ -41,11 +47,11 @@ public interface ActionDisabledByAdminController { /** * Returns the admin support dialog's content string. */ - CharSequence getAdminSupportContentString( - Context context, @Nullable CharSequence supportMessage); + CharSequence getAdminSupportContentString(Context context, + @Nullable CharSequence supportMessage); /** * Updates the enforced admin */ - void updateEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin, int adminUserId); + void updateEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin, @UserIdInt int adminUserId); } diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java index 7eecd1994d0d..da42e330b8b4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java @@ -19,29 +19,30 @@ package com.android.settingslib.enterprise; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import android.app.admin.DevicePolicyManager; +import android.content.Context; /** * A factory that returns the relevant instance of {@link ActionDisabledByAdminController}. */ -public class ActionDisabledByAdminControllerFactory { +public final class ActionDisabledByAdminControllerFactory { /** * Returns the relevant instance of {@link ActionDisabledByAdminController}. */ - public static ActionDisabledByAdminController createInstance( - DevicePolicyManager dpm, - ActionDisabledLearnMoreButtonLauncher helper, - DeviceAdminStringProvider deviceAdminStringProvider) { - if (isFinancedDevice(dpm)) { - return new FinancedDeviceActionDisabledByAdminController( - helper, deviceAdminStringProvider); - } - return new ManagedDeviceActionDisabledByAdminController( - helper, deviceAdminStringProvider); + public static ActionDisabledByAdminController createInstance(Context context, + DeviceAdminStringProvider stringProvider) { + return isFinancedDevice(context) + ? new FinancedDeviceActionDisabledByAdminController(stringProvider) + : new ManagedDeviceActionDisabledByAdminController(stringProvider); } - private static boolean isFinancedDevice(DevicePolicyManager dpm) { + private static boolean isFinancedDevice(Context context) { + DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); return dpm.isDeviceManaged() && dpm.getDeviceOwnerType( dpm.getDeviceOwnerComponentOnAnyUser()) == DEVICE_OWNER_TYPE_FINANCED; } + + private ActionDisabledByAdminControllerFactory() { + throw new UnsupportedOperationException("provides only static methods"); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java index 65b91f129c95..78a42bef2d7b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java @@ -16,29 +16,100 @@ package com.android.settingslib.enterprise; +import static java.util.Objects.requireNonNull; + +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.UserHandle; +import android.os.UserManager; -import com.android.settingslib.RestrictedLockUtils; +import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; /** - * Helper interface meant to set up the "Learn more" button in the action disabled dialog. + * Helper class meant to set up the "Learn more" button in the action disabled dialog. */ -public interface ActionDisabledLearnMoreButtonLauncher { +public abstract class ActionDisabledLearnMoreButtonLauncher { /** * Sets up a "learn more" button which shows a screen with device policy settings */ - void setupLearnMoreButtonToShowAdminPolicies( - Context context, - Object alertDialogBuilder, - int enforcementAdminUserId, - RestrictedLockUtils.EnforcedAdmin enforcedAdmin); + public final void setupLearnMoreButtonToShowAdminPolicies(Context context, + int enforcementAdminUserId, EnforcedAdmin enforcedAdmin) { + requireNonNull(context, "context cannot be null"); + requireNonNull(enforcedAdmin, "enforcedAdmin cannot be null"); + + // The "Learn more" button appears only if the restriction is enforced by an admin in the + // same profile group. Otherwise the admin package and its policies are not accessible to + // the current user. + if (isSameProfileGroup(context, enforcementAdminUserId)) { + setLearnMoreButton(() -> showAdminPolicies(context, enforcedAdmin)); + } + } /** * Sets up a "learn more" button which launches a help page */ - void setupLearnMoreButtonToLaunchHelpPage( - Context context, - Object alertDialogBuilder, - String url); + public final void setupLearnMoreButtonToLaunchHelpPage(Context context, String url) { + requireNonNull(context, "context cannot be null"); + requireNonNull(url, "url cannot be null"); + + setLearnMoreButton(() -> showHelpPage(context, url)); + } + + /** + * Sets the "learning more" button. + * + * @param action action to be run when the button is tapped. + */ + public abstract void setLearnMoreButton(Runnable action); + + /** + * Launches the settings page with info about the given admin. + */ + protected abstract void launchShowAdminPolicies(Context context, UserHandle user, + ComponentName admin); + + /** + * Launches the settings page that shows all admins. + */ + protected abstract void launchShowAdminSettings(Context context); + + /** + * Callback to finish the activity associated with the launcher. + */ + protected void finishSelf() { + } + + @VisibleForTesting + protected boolean isSameProfileGroup(Context context, int enforcementAdminUserId) { + UserManager um = context.getSystemService(UserManager.class); + + return um.isSameProfileGroup(enforcementAdminUserId, um.getUserHandle()); + } + + /** + * Shows the help page using the given {@code url}. + */ + @VisibleForTesting + public void showHelpPage(Context context, String url) { + context.startActivityAsUser(createLearnMoreIntent(url), UserHandle.of(context.getUserId())); + finishSelf(); + } + + private void showAdminPolicies(Context context, EnforcedAdmin enforcedAdmin) { + if (enforcedAdmin.component != null) { + launchShowAdminPolicies(context, enforcedAdmin.user, enforcedAdmin.component); + } else { + launchShowAdminSettings(context); + } + finishSelf(); + } + + private static Intent createLearnMoreIntent(String url) { + return new Intent(Intent.ACTION_VIEW, Uri.parse(url)).setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/BaseActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/BaseActionDisabledByAdminController.java new file mode 100644 index 000000000000..dd71557ef148 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/BaseActionDisabledByAdminController.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.enterprise; + +import static java.util.Objects.requireNonNull; + +import android.annotation.UserIdInt; + +import com.android.internal.util.Preconditions; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + +/** + * Base class for {@link ActionDisabledByAdminController} implementations. + */ +abstract class BaseActionDisabledByAdminController + implements ActionDisabledByAdminController { + + protected @UserIdInt int mEnforcementAdminUserId; + protected EnforcedAdmin mEnforcedAdmin; + protected ActionDisabledLearnMoreButtonLauncher mLauncher; + protected final DeviceAdminStringProvider mStringProvider; + + BaseActionDisabledByAdminController(DeviceAdminStringProvider stringProvider) { + mStringProvider = stringProvider; + } + + @Override + public final void initialize(ActionDisabledLearnMoreButtonLauncher launcher) { + mLauncher = requireNonNull(launcher, "launcher cannot be null"); + } + + @Override + public final void updateEnforcedAdmin(EnforcedAdmin admin, int adminUserId) { + assertInitialized(); + mEnforcementAdminUserId = adminUserId; + mEnforcedAdmin = requireNonNull(admin, "admin cannot be null"); + } + + protected final void assertInitialized() { + Preconditions.checkState(mLauncher != null, "must call initialize() first"); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminController.java index cd816e88f080..2ed0dc46b340 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminController.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminController.java @@ -16,52 +16,31 @@ package com.android.settingslib.enterprise; -import static java.util.Objects.requireNonNull; - -import android.annotation.UserIdInt; import android.content.Context; import androidx.annotation.Nullable; -import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; - /** * An {@link ActionDisabledByAdminController} to be used with financed devices. */ -public class FinancedDeviceActionDisabledByAdminController - implements ActionDisabledByAdminController { - - private @UserIdInt int mEnforcementAdminUserId; - private EnforcedAdmin mEnforcedAdmin; - private final ActionDisabledLearnMoreButtonLauncher mHelper; - private final DeviceAdminStringProvider mDeviceAdminStringProvider; +final class FinancedDeviceActionDisabledByAdminController + extends BaseActionDisabledByAdminController { - FinancedDeviceActionDisabledByAdminController( - ActionDisabledLearnMoreButtonLauncher helper, - DeviceAdminStringProvider deviceAdminStringProvider) { - mHelper = requireNonNull(helper, "helper cannot be null"); - mDeviceAdminStringProvider = requireNonNull(deviceAdminStringProvider, - "deviceAdminStringProvider cannot be null"); + FinancedDeviceActionDisabledByAdminController(DeviceAdminStringProvider stringProvider) { + super(stringProvider); } @Override - public void updateEnforcedAdmin(EnforcedAdmin admin, int adminUserId) { - mEnforcementAdminUserId = adminUserId; - mEnforcedAdmin = requireNonNull(admin, "admin cannot be null"); - } + public void setupLearnMoreButton(Context context) { + assertInitialized(); - @Override - public void setupLearnMoreButton(Context context, Object alertDialogBuilder) { - mHelper.setupLearnMoreButtonToShowAdminPolicies( - context, - alertDialogBuilder, - mEnforcementAdminUserId, + mLauncher.setupLearnMoreButtonToShowAdminPolicies(context, mEnforcementAdminUserId, mEnforcedAdmin); } @Override public String getAdminSupportTitle(@Nullable String restriction) { - return mDeviceAdminStringProvider.getDisabledByPolicyTitleForFinancedDevice(); + return mStringProvider.getDisabledByPolicyTitleForFinancedDevice(); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java index 70e19f97028b..df6bab74e014 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java @@ -16,55 +16,39 @@ package com.android.settingslib.enterprise; -import static java.util.Objects.requireNonNull; - -import android.annotation.UserIdInt; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.UserManager; import android.text.TextUtils; -import com.android.settingslib.RestrictedLockUtils; +import androidx.annotation.Nullable; + /** * An {@link ActionDisabledByAdminController} to be used with managed devices. */ -class ManagedDeviceActionDisabledByAdminController implements - ActionDisabledByAdminController { - private @UserIdInt int mEnforcementAdminUserId; - private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin; - private final ActionDisabledLearnMoreButtonLauncher mHelper; - private final DeviceAdminStringProvider mStringProvider; +final class ManagedDeviceActionDisabledByAdminController + extends BaseActionDisabledByAdminController { - ManagedDeviceActionDisabledByAdminController( - ActionDisabledLearnMoreButtonLauncher helper, - DeviceAdminStringProvider stringProvider) { - mHelper = requireNonNull(helper); - mStringProvider = requireNonNull(stringProvider); + ManagedDeviceActionDisabledByAdminController(DeviceAdminStringProvider stringProvider) { + super(stringProvider); } @Override - public void updateEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin, int adminUserId) { - mEnforcementAdminUserId = adminUserId; - mEnforcedAdmin = requireNonNull(admin, "admin cannot be null"); - } + public void setupLearnMoreButton(Context context) { + assertInitialized(); - @Override - public void setupLearnMoreButton(Context context, Object alertDialogBuilder) { String url = mStringProvider.getLearnMoreHelpPageUrl(); if (TextUtils.isEmpty(url)) { - mHelper.setupLearnMoreButtonToShowAdminPolicies( - context, - alertDialogBuilder, - mEnforcementAdminUserId, + mLauncher.setupLearnMoreButtonToShowAdminPolicies(context, mEnforcementAdminUserId, mEnforcedAdmin); } else { - mHelper.setupLearnMoreButtonToLaunchHelpPage(context, alertDialogBuilder, url); + mLauncher.setupLearnMoreButtonToLaunchHelpPage(context, url); } } @Override - public String getAdminSupportTitle(String restriction) { + public String getAdminSupportTitle(@Nullable String restriction) { if (restriction == null) { return mStringProvider.getDefaultDisabledByPolicyTitle(); } @@ -88,9 +72,8 @@ class ManagedDeviceActionDisabledByAdminController implements @Override public CharSequence getAdminSupportContentString(Context context, CharSequence supportMessage) { - if (supportMessage != null) { - return supportMessage; - } - return mStringProvider.getDefaultDisabledByPolicyContent(); + return supportMessage != null + ? supportMessage + : mStringProvider.getDefaultDisabledByPolicyContent(); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java index b1234f291b74..51e533abebd2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java @@ -73,6 +73,7 @@ public class DataUsageUtils { private static NetworkTemplate getMobileTemplateForSubId( TelephonyManager telephonyManager, int subId) { - return NetworkTemplate.buildTemplateMobileAll(telephonyManager.getSubscriberId(subId)); + return NetworkTemplate.buildTemplateCarrierMetered( + telephonyManager.getSubscriberId(subId)); } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/NetworkPolicyEditorTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/NetworkPolicyEditorTest.java index 37f2600e9812..7e389a19e403 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/NetworkPolicyEditorTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/NetworkPolicyEditorTest.java @@ -44,7 +44,7 @@ public class NetworkPolicyEditorTest { @Before public void setUp() { - mNetworkTemplate = NetworkTemplate.buildTemplateMobileAll("123456789123456"); + mNetworkTemplate = NetworkTemplate.buildTemplateCarrierMetered("123456789123456"); NetworkPolicyManager policyManager = NetworkPolicyManager.from(InstrumentationRegistry .getContext()); mNetworkPolicyEditor = new NetworkPolicyEditor(policyManager); diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp index 63cfe59a1051..2d1a516f4f11 100644 --- a/packages/SettingsLib/tests/robotests/Android.bp +++ b/packages/SettingsLib/tests/robotests/Android.bp @@ -45,6 +45,7 @@ android_robolectric_test { "SettingsLib-robo-testutils", "androidx.test.core", "androidx.core_core", + "testng", // TODO: remove once JUnit on Android provides assertThrows ], java_resource_dirs: ["config"], instrumentation_for: "SettingsLibShell", diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerTestUtils.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerTestUtils.java index 4b517906aaa7..e57335fbd3c5 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerTestUtils.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerTestUtils.java @@ -16,51 +16,81 @@ package com.android.settingslib.enterprise; -import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; -import android.app.Activity; +import android.content.ComponentName; import android.content.Context; +import android.os.UserHandle; +import android.util.DebugUtils; -import androidx.appcompat.app.AlertDialog; - -import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; /** * Utils related to the action disabled by admin dialogs. */ -class ActionDisabledByAdminControllerTestUtils { - static final int LEARN_MORE_ACTION_NONE = 0; - static final int LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES = 1; - static final int LEARN_MORE_ACTION_LAUNCH_HELP_PAGE = 2; +// NOTE: must be public because of DebugUtils.constantToString() call +public final class ActionDisabledByAdminControllerTestUtils { + + static final int ENFORCEMENT_ADMIN_USER_ID = 123; + static final UserHandle ENFORCEMENT_ADMIN_USER = UserHandle.of(ENFORCEMENT_ADMIN_USER_ID); + + static final String SUPPORT_MESSAGE = "support message"; + + static final ComponentName ADMIN_COMPONENT = + new ComponentName("some.package.name", "some.package.name.SomeClass"); + static final EnforcedAdmin ENFORCED_ADMIN = new EnforcedAdmin( + ADMIN_COMPONENT, UserHandle.of(ENFORCEMENT_ADMIN_USER_ID)); + static final EnforcedAdmin ENFORCED_ADMIN_WITHOUT_COMPONENT = new EnforcedAdmin( + /* component= */ null, UserHandle.of(ENFORCEMENT_ADMIN_USER_ID)); + + static final String URL = "https://testexample.com"; + + // NOTE: fields below must be public because of DebugUtils.constantToString() call + public static final int LEARN_MORE_ACTION_NONE = 0; + public static final int LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES = 1; + public static final int LEARN_MORE_ACTION_SHOW_ADMIN_SETTINGS = 2; + public static final int LEARN_MORE_ACTION_LAUNCH_HELP_PAGE = 3; private int mLearnMoreButtonAction = LEARN_MORE_ACTION_NONE; ActionDisabledLearnMoreButtonLauncher createLearnMoreButtonLauncher() { return new ActionDisabledLearnMoreButtonLauncher() { + + @Override + public void setLearnMoreButton(Runnable action) { + action.run(); + } + @Override - public void setupLearnMoreButtonToShowAdminPolicies(Context context, - Object alertDialogBuilder, int enforcementAdminUserId, - RestrictedLockUtils.EnforcedAdmin enforcedAdmin) { + protected void launchShowAdminPolicies(Context context, UserHandle user, + ComponentName admin) { mLearnMoreButtonAction = LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES; } @Override - public void setupLearnMoreButtonToLaunchHelpPage(Context context, - Object alertDialogBuilder, String url) { + protected void launchShowAdminSettings(Context context) { + mLearnMoreButtonAction = LEARN_MORE_ACTION_SHOW_ADMIN_SETTINGS; + } + + @Override + public void showHelpPage(Context context, String url) { mLearnMoreButtonAction = LEARN_MORE_ACTION_LAUNCH_HELP_PAGE; } + + @Override + protected boolean isSameProfileGroup(Context context, int enforcementAdminUserId) { + return true; + } }; } void assertLearnMoreAction(int learnMoreActionShowAdminPolicies) { - assertThat(learnMoreActionShowAdminPolicies).isEqualTo(mLearnMoreButtonAction); + assertWithMessage("action").that(actionToString(mLearnMoreButtonAction)) + .isEqualTo(actionToString(learnMoreActionShowAdminPolicies)); } - AlertDialog createAlertDialog(ActionDisabledByAdminController mController, Activity activity) { - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - mController.setupLearnMoreButton(activity, builder); - AlertDialog alertDialog = builder.create(); - alertDialog.show(); - return alertDialog; + private static String actionToString(int action) { + return DebugUtils.constantToString(ActionDisabledByAdminControllerTestUtils.class, + "LEARN_MORE_ACTION_", action); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncherTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncherTest.java new file mode 100644 index 000000000000..7014da0df0bf --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncherTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.enterprise; + +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ADMIN_COMPONENT; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN_WITHOUT_COMPONENT; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.URL; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertThrows; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.UserHandle; +import android.os.UserManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class)// NOTE: this test doesn't need RoboElectric... +public final class ActionDisabledLearnMoreButtonLauncherTest { + + private static final int CONTEXT_USER_ID = -ENFORCEMENT_ADMIN_USER_ID; + private static final UserHandle CONTEXT_USER = UserHandle.of(CONTEXT_USER_ID); + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock + private Context mContext; + + @Mock + private UserManager mUserManager; + + @Spy + private ActionDisabledLearnMoreButtonLauncher mLauncher; + + @Captor + private ArgumentCaptor<Runnable> mLearnMoreActionCaptor; + + @Captor + private ArgumentCaptor<Intent> mIntentCaptor; + + @Before + public void setUp() { + when(mContext.getUserId()).thenReturn(CONTEXT_USER_ID); + when(mUserManager.getUserHandle()).thenReturn(CONTEXT_USER_ID); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); + } + + @Test + public void testSetupLearnMoreButtonToShowAdminPolicies_nullContext() { + assertThrows(NullPointerException.class, + () -> mLauncher.setupLearnMoreButtonToShowAdminPolicies(/* context= */ null, + ENFORCEMENT_ADMIN_USER_ID, ENFORCED_ADMIN)); + } + + @Test + public void testSetupLearnMoreButtonToShowAdminPolicies_nullEnforcedAdmin() { + assertThrows(NullPointerException.class, + () -> mLauncher.setupLearnMoreButtonToShowAdminPolicies(/* context= */ null, + ENFORCEMENT_ADMIN_USER_ID, /* enforcedAdmin= */ null)); + } + + @Test + public void testSetupLearnMoreButtonToShowAdminPolicies_differentProfileGroup() { + mockDifferentProfileGroup(); + + mLauncher.setupLearnMoreButtonToShowAdminPolicies(mContext, ENFORCEMENT_ADMIN_USER_ID, + ENFORCED_ADMIN); + + verify(mLauncher, never()).setLearnMoreButton(any()); + } + + @Test + public void testSetupLearnMoreButtonToShowAdminPolicies_sameProfileGroup_noComponent() { + mockSameProfileGroup(); + + mLauncher.setupLearnMoreButtonToShowAdminPolicies(mContext, ENFORCEMENT_ADMIN_USER_ID, + ENFORCED_ADMIN_WITHOUT_COMPONENT); + tapLearnMore(); + + verify(mLauncher, never()).launchShowAdminPolicies(any(), any(), any()); + verify(mLauncher).launchShowAdminSettings(mContext); + verifyFinishSelf(); + } + + @Test + public void testSetupLearnMoreButtonToShowAdminPolicies_sameProfileGroup_withComponent() { + mockSameProfileGroup(); + + mLauncher.setupLearnMoreButtonToShowAdminPolicies(mContext, ENFORCEMENT_ADMIN_USER_ID, + ENFORCED_ADMIN); + tapLearnMore(); + + verify(mLauncher).launchShowAdminPolicies(mContext, ENFORCEMENT_ADMIN_USER, + ADMIN_COMPONENT); + verify(mLauncher, never()).launchShowAdminSettings(any()); + verifyFinishSelf(); + } + + @Test + public void testSetupLearnMoreButtonToLaunchHelpPage_nullContext() { + assertThrows(NullPointerException.class, + () -> mLauncher.setupLearnMoreButtonToLaunchHelpPage(/* context= */ null, URL)); + } + + @Test + public void testSetupLearnMoreButtonToLaunchHelpPage_nullUrl() { + assertThrows(NullPointerException.class, + () -> mLauncher.setupLearnMoreButtonToLaunchHelpPage(mContext, /* url= */ null)); + } + + @Test + public void testSetupLearnMoreButtonToLaunchHelpPage() { + mLauncher.setupLearnMoreButtonToLaunchHelpPage(mContext, URL); + tapLearnMore(); + + verify(mContext).startActivityAsUser(mIntentCaptor.capture(), eq(CONTEXT_USER)); + Intent intent = mIntentCaptor.getValue(); + assertWithMessage("wrong url on intent %s", intent).that(intent.getData()) + .isEqualTo(Uri.parse(URL)); + verifyFinishSelf(); + } + + private void mockDifferentProfileGroup() { + // No need to mock anything - isSameProfileGroup() will return false by default + } + + private void mockSameProfileGroup() { + when(mUserManager.isSameProfileGroup(ENFORCEMENT_ADMIN_USER_ID, CONTEXT_USER_ID)) + .thenReturn(true); + } + + private void tapLearnMore() { + verify(mLauncher).setLearnMoreButton(mLearnMoreActionCaptor.capture()); + mLearnMoreActionCaptor.getValue().run(); + } + + private void verifyFinishSelf() { + verify(mLauncher).finishSelf(); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java index 8c07d7535810..be3e9fcf45ea 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java @@ -30,6 +30,8 @@ class FakeDeviceAdminStringProvider implements DeviceAdminStringProvider { static final String DEFAULT_DISABLED_BY_POLICY_CONTENT = "default_disabled_by_policy_content"; static final String DEFAULT_DISABLED_BY_POLICY_TITLE_FINANCED_DEVICE = "default_disabled_by_policy_title_financed_device"; + static final DeviceAdminStringProvider DEFAULT_DEVICE_ADMIN_STRING_PROVIDER = + new FakeDeviceAdminStringProvider(/* url = */ null); private final String mUrl; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java index 2fe2262edd2c..7b08fee9ac91 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java @@ -16,22 +16,21 @@ package com.android.settingslib.enterprise; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID; import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.SUPPORT_MESSAGE; +import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DEVICE_ADMIN_STRING_PROVIDER; import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_TITLE_FINANCED_DEVICE; import static com.google.common.truth.Truth.assertThat; import android.app.Activity; -import android.app.Dialog; -import android.content.ComponentName; import android.content.Context; -import android.os.UserHandle; -import androidx.appcompat.app.AlertDialog; import androidx.test.core.app.ApplicationProvider; import com.android.settingslib.R; -import com.android.settingslib.RestrictedLockUtils; import org.junit.Before; import org.junit.Test; @@ -41,73 +40,46 @@ import org.robolectric.android.controller.ActivityController; @RunWith(RobolectricTestRunner.class) public class FinancedDeviceActionDisabledByAdminControllerTest { - private static final int ENFORCEMENT_ADMIN_USER_ID = 123; - private static final ComponentName ADMIN_COMPONENT = - new ComponentName("some.package.name", "some.package.name.SomeClass"); - private static final String SUPPORT_MESSAGE = "support message"; - private static final DeviceAdminStringProvider DEVICE_ADMIN_STRING_PROVIDER = - new FakeDeviceAdminStringProvider(/* url = */ null); - private static final RestrictedLockUtils.EnforcedAdmin ENFORCED_ADMIN = - new RestrictedLockUtils.EnforcedAdmin( - ADMIN_COMPONENT, UserHandle.of(ENFORCEMENT_ADMIN_USER_ID)); private final Context mContext = ApplicationProvider.getApplicationContext(); private final Activity mActivity = ActivityController.of(new Activity()).get(); private final ActionDisabledByAdminControllerTestUtils mTestUtils = new ActionDisabledByAdminControllerTestUtils(); - private final ActionDisabledLearnMoreButtonLauncher mLauncher = - mTestUtils.createLearnMoreButtonLauncher(); + private final FinancedDeviceActionDisabledByAdminController mController = + new FinancedDeviceActionDisabledByAdminController( + DEFAULT_DEVICE_ADMIN_STRING_PROVIDER); @Before public void setUp() { mActivity.setTheme(R.style.Theme_AppCompat_DayNight); + + mController.initialize(mTestUtils.createLearnMoreButtonLauncher()); + mController.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID); + } @Test public void setupLearnMoreButton_negativeButtonSet() { - FinancedDeviceActionDisabledByAdminController mController = createController(mLauncher); - AlertDialog alertDialog = mTestUtils.createAlertDialog(mController, mActivity); - - alertDialog.getButton(Dialog.BUTTON_NEUTRAL).performClick(); + mController.setupLearnMoreButton(mContext); mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES); } @Test public void getAdminSupportTitleResource_works() { - FinancedDeviceActionDisabledByAdminController mController = createController(); - assertThat(mController.getAdminSupportTitle(null)) .isEqualTo(DEFAULT_DISABLED_BY_POLICY_TITLE_FINANCED_DEVICE); } @Test public void getAdminSupportContentString_withSupportMessage_returnsSupportMessage() { - FinancedDeviceActionDisabledByAdminController mController = createController(); - assertThat(mController.getAdminSupportContentString(mContext, SUPPORT_MESSAGE)) .isEqualTo(SUPPORT_MESSAGE); } @Test public void getAdminSupportContentString_noSupportMessage_returnsNull() { - FinancedDeviceActionDisabledByAdminController mController = createController(); - assertThat(mController.getAdminSupportContentString(mContext, /* supportMessage= */ null)) .isNull(); } - - private FinancedDeviceActionDisabledByAdminController createController() { - return createController(mLauncher); - } - - private FinancedDeviceActionDisabledByAdminController createController( - ActionDisabledLearnMoreButtonLauncher buttonHelper) { - FinancedDeviceActionDisabledByAdminController controller = - new FinancedDeviceActionDisabledByAdminController( - buttonHelper, - DEVICE_ADMIN_STRING_PROVIDER); - controller.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID); - return controller; - } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java index eb1dc963f62f..19f6aa1d55a6 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java @@ -16,8 +16,12 @@ package com.android.settingslib.enterprise; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID; import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_LAUNCH_HELP_PAGE; import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.SUPPORT_MESSAGE; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.URL; import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_CONTENT; import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_TITLE; import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DISALLOW_ADJUST_VOLUME_TITLE; @@ -25,15 +29,12 @@ import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.D import static com.google.common.truth.Truth.assertThat; import android.app.Activity; -import android.app.Dialog; -import android.content.ComponentName; -import android.os.UserHandle; +import android.content.Context; import android.os.UserManager; -import androidx.appcompat.app.AlertDialog; +import androidx.test.core.app.ApplicationProvider; import com.android.settingslib.R; -import com.android.settingslib.RestrictedLockUtils; import org.junit.Before; import org.junit.Test; @@ -43,23 +44,15 @@ import org.robolectric.android.controller.ActivityController; @RunWith(RobolectricTestRunner.class) public class ManagedDeviceActionDisabledByAdminControllerTest { - private static final int ENFORCEMENT_ADMIN_USER_ID = 123; - private static final ComponentName ADMIN_COMPONENT = - new ComponentName("some.package.name", "some.package.name.SomeClass"); - private static final String SUPPORT_MESSAGE = "support message"; + private static final String RESTRICTION = UserManager.DISALLOW_ADJUST_VOLUME; - private static final String URL = "https://testexample.com"; private static final String EMPTY_URL = ""; - private static final RestrictedLockUtils.EnforcedAdmin ENFORCED_ADMIN = - new RestrictedLockUtils.EnforcedAdmin( - ADMIN_COMPONENT, UserHandle.of(ENFORCEMENT_ADMIN_USER_ID)); private static final String SUPPORT_TITLE_FOR_RESTRICTION = DISALLOW_ADJUST_VOLUME_TITLE; + private final Context mContext = ApplicationProvider.getApplicationContext(); private final Activity mActivity = ActivityController.of(new Activity()).get(); private final ActionDisabledByAdminControllerTestUtils mTestUtils = new ActionDisabledByAdminControllerTestUtils(); - private final ActionDisabledLearnMoreButtonLauncher mLauncher = - mTestUtils.createLearnMoreButtonLauncher(); @Before public void setUp() { @@ -68,68 +61,63 @@ public class ManagedDeviceActionDisabledByAdminControllerTest { @Test public void setupLearnMoreButton_validUrl_negativeButtonSet() { - ManagedDeviceActionDisabledByAdminController mController = - createController(mLauncher, URL); - AlertDialog alertDialog = mTestUtils.createAlertDialog(mController, mActivity); + ManagedDeviceActionDisabledByAdminController controller = createController(URL); - alertDialog.getButton(Dialog.BUTTON_NEUTRAL).performClick(); + controller.setupLearnMoreButton(mContext); mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_LAUNCH_HELP_PAGE); } @Test public void setupLearnMoreButton_noUrl_negativeButtonSet() { - ManagedDeviceActionDisabledByAdminController mController = - createController(mLauncher, EMPTY_URL); - AlertDialog alertDialog = mTestUtils.createAlertDialog(mController, mActivity); + ManagedDeviceActionDisabledByAdminController controller = createController(EMPTY_URL); - alertDialog.getButton(Dialog.BUTTON_NEUTRAL).performClick(); + controller.setupLearnMoreButton(mContext); mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES); } @Test public void getAdminSupportTitleResource_noRestriction_works() { - ManagedDeviceActionDisabledByAdminController mController = createController(); + ManagedDeviceActionDisabledByAdminController controller = createController(); - assertThat(mController.getAdminSupportTitle(null)) + assertThat(controller.getAdminSupportTitle(null)) .isEqualTo(DEFAULT_DISABLED_BY_POLICY_TITLE); } @Test public void getAdminSupportTitleResource_withRestriction_works() { - ManagedDeviceActionDisabledByAdminController mController = createController(); + ManagedDeviceActionDisabledByAdminController controller = createController(); - assertThat(mController.getAdminSupportTitle(RESTRICTION)) + assertThat(controller.getAdminSupportTitle(RESTRICTION)) .isEqualTo(SUPPORT_TITLE_FOR_RESTRICTION); } @Test public void getAdminSupportContentString_withSupportMessage_returnsSupportMessage() { - ManagedDeviceActionDisabledByAdminController mController = createController(); + ManagedDeviceActionDisabledByAdminController controller = createController(); - assertThat(mController.getAdminSupportContentString(mActivity, SUPPORT_MESSAGE)) + assertThat(controller.getAdminSupportContentString(mActivity, SUPPORT_MESSAGE)) .isEqualTo(SUPPORT_MESSAGE); } @Test public void getAdminSupportContentString_noSupportMessage_returnsDefault() { - ManagedDeviceActionDisabledByAdminController mController = createController(); + ManagedDeviceActionDisabledByAdminController controller = createController(); - assertThat(mController.getAdminSupportContentString(mActivity, /* supportMessage= */ null)) + assertThat(controller.getAdminSupportContentString(mActivity, /* supportMessage= */ null)) .isEqualTo(DEFAULT_DISABLED_BY_POLICY_CONTENT); } private ManagedDeviceActionDisabledByAdminController createController() { - return createController(mLauncher, /* url= */ null); + return createController(/* url= */ null); } - private ManagedDeviceActionDisabledByAdminController createController( - ActionDisabledLearnMoreButtonLauncher buttonHelper, String url) { + private ManagedDeviceActionDisabledByAdminController createController(String url) { ManagedDeviceActionDisabledByAdminController controller = new ManagedDeviceActionDisabledByAdminController( - buttonHelper, new FakeDeviceAdminStringProvider(url)); + controller.initialize(mTestUtils.createLearnMoreButtonLauncher()); controller.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID); return controller; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java index 27d877db4952..9be783d61ea8 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java @@ -87,8 +87,8 @@ public class DataUsageControllerTest { ShadowSubscriptionManager.setDefaultDataSubscriptionId(mDefaultSubscriptionId); doReturn(SUB_ID).when(mTelephonyManager).getSubscriberId(); - mNetworkTemplate = NetworkTemplate.buildTemplateMobileAll(SUB_ID); - mNetworkTemplate2 = NetworkTemplate.buildTemplateMobileAll(SUB_ID_2); + mNetworkTemplate = NetworkTemplate.buildTemplateCarrierMetered(SUB_ID); + mNetworkTemplate2 = NetworkTemplate.buildTemplateCarrierMetered(SUB_ID_2); mWifiNetworkTemplate = NetworkTemplate.buildTemplateWifi( NetworkTemplate.WIFI_NETWORKID_ALL, null); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java index 877eb615b196..e8d584486746 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java @@ -62,7 +62,7 @@ public class NetworkCycleDataForUidLoaderTest { when(mContext.getSystemService(Context.NETWORK_POLICY_SERVICE)) .thenReturn(mNetworkPolicyManager); when(mNetworkPolicyManager.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]); - mNetworkTemplate = NetworkTemplate.buildTemplateMobileAll(SUB_ID); + mNetworkTemplate = NetworkTemplate.buildTemplateCarrierMetered(SUB_ID); } @Test diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index bce576d1e1d8..dd9a6eee7327 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -310,20 +310,29 @@ public class SettingsProvider extends ContentProvider { private static final Set<String> sAllSecureSettings = new ArraySet<>(); private static final Set<String> sReadableSecureSettings = new ArraySet<>(); + private static final ArrayMap<String, Integer> sReadableSecureSettingsWithMaxTargetSdk = + new ArrayMap<>(); static { - Settings.Secure.getPublicSettings(sAllSecureSettings, sReadableSecureSettings); + Settings.Secure.getPublicSettings(sAllSecureSettings, sReadableSecureSettings, + sReadableSecureSettingsWithMaxTargetSdk); } private static final Set<String> sAllSystemSettings = new ArraySet<>(); private static final Set<String> sReadableSystemSettings = new ArraySet<>(); + private static final ArrayMap<String, Integer> sReadableSystemSettingsWithMaxTargetSdk = + new ArrayMap<>(); static { - Settings.System.getPublicSettings(sAllSystemSettings, sReadableSystemSettings); + Settings.System.getPublicSettings(sAllSystemSettings, sReadableSystemSettings, + sReadableSystemSettingsWithMaxTargetSdk); } private static final Set<String> sAllGlobalSettings = new ArraySet<>(); private static final Set<String> sReadableGlobalSettings = new ArraySet<>(); + private static final ArrayMap<String, Integer> sReadableGlobalSettingsWithMaxTargetSdk = + new ArrayMap<>(); static { - Settings.Global.getPublicSettings(sAllGlobalSettings, sReadableGlobalSettings); + Settings.Global.getPublicSettings(sAllGlobalSettings, sReadableGlobalSettings, + sReadableGlobalSettingsWithMaxTargetSdk); } private final Object mLock = new Object(); @@ -2065,7 +2074,7 @@ public class SettingsProvider extends ContentProvider { } if ((ai.flags & ApplicationInfo.FLAG_TEST_ONLY) == 0) { // Skip checking readable annotations for test_only apps - checkReadableAnnotation(settingsType, settingName); + checkReadableAnnotation(settingsType, settingName, ai.targetSandboxVersion); } /** * some settings need additional permission check, this is to have a matching security @@ -2101,35 +2110,55 @@ public class SettingsProvider extends ContentProvider { /** * Check if the target settings key is readable. Reject if the caller app is trying to access a * settings key defined in the Settings.Secure, Settings.System or Settings.Global and is not - * annotated as @Readable. + * annotated as @Readable. Reject if the caller app is targeting an SDK level that is higher + * than the maxTargetSdk specified in the @Readable annotation. * Notice that a key string that is not defined in any of the Settings.* classes will still be * regarded as readable. */ - private void checkReadableAnnotation(int settingsType, String settingName) { + private void checkReadableAnnotation(int settingsType, String settingName, + int targetSdkVersion) { final Set<String> allFields; final Set<String> readableFields; + final ArrayMap<String, Integer> readableFieldsWithMaxTargetSdk; switch (settingsType) { case SETTINGS_TYPE_GLOBAL: allFields = sAllGlobalSettings; readableFields = sReadableGlobalSettings; + readableFieldsWithMaxTargetSdk = sReadableGlobalSettingsWithMaxTargetSdk; break; case SETTINGS_TYPE_SYSTEM: allFields = sAllSystemSettings; readableFields = sReadableSystemSettings; + readableFieldsWithMaxTargetSdk = sReadableSystemSettingsWithMaxTargetSdk; break; case SETTINGS_TYPE_SECURE: allFields = sAllSecureSettings; readableFields = sReadableSecureSettings; + readableFieldsWithMaxTargetSdk = sReadableSecureSettingsWithMaxTargetSdk; break; default: throw new IllegalArgumentException("Invalid settings type: " + settingsType); } - if (allFields.contains(settingName) && !readableFields.contains(settingName)) { - throw new SecurityException( - "Settings key: <" + settingName + "> is not readable. From S+, settings keys " - + "annotated with @hide are restricted to system_server and system " - + "apps only, unless they are annotated with @Readable."); + if (allFields.contains(settingName)) { + if (!readableFields.contains(settingName)) { + throw new SecurityException( + "Settings key: <" + settingName + "> is not readable. From S+, settings " + + "keys annotated with @hide are restricted to system_server and " + + "system apps only, unless they are annotated with @Readable." + ); + } else { + if (readableFieldsWithMaxTargetSdk.containsKey(settingName)) { + final int maxTargetSdk = readableFieldsWithMaxTargetSdk.get(settingName); + if (targetSdkVersion > maxTargetSdk) { + throw new SecurityException( + "Settings key: <" + settingName + "> is only readable to apps with " + + "targetSdkVersion lower than or equal to: " + + maxTargetSdk + ); + } + } + } } } diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp index 1b15d20d2c52..761b1f460bb5 100644 --- a/packages/SystemUI/animation/Android.bp +++ b/packages/SystemUI/animation/Android.bp @@ -36,6 +36,7 @@ android_library { static_libs: [ "PluginCoreLib", + "WindowManager-Shell", ], manifest: "AndroidManifest.xml", diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 4f179c46a3d9..d185ba3615a9 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -5,13 +5,18 @@ import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.app.ActivityManager import android.app.ActivityTaskManager +import android.app.AppGlobals import android.app.PendingIntent import android.content.Context import android.graphics.Matrix +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.graphics.RectF +import android.graphics.drawable.GradientDrawable import android.os.Looper import android.os.RemoteException +import android.os.UserHandle import android.util.Log import android.util.MathUtils import android.view.IRemoteAnimationFinishedCallback @@ -26,6 +31,8 @@ import android.view.animation.AnimationUtils import android.view.animation.PathInterpolator import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils +import com.android.wm.shell.startingsurface.SplashscreenContentDrawer +import com.android.wm.shell.startingsurface.SplashscreenContentDrawer.SplashScreenWindowAttrs import kotlin.math.roundToInt /** @@ -40,9 +47,9 @@ class ActivityLaunchAnimator( companion object { const val ANIMATION_DURATION = 500L - const val ANIMATION_DURATION_FADE_OUT_CONTENT = 183L - const val ANIMATION_DURATION_FADE_IN_WINDOW = 217L - const val ANIMATION_DELAY_FADE_IN_WINDOW = 167L + private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L + private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L + private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT private const val ANIMATION_DURATION_NAV_FADE_IN = 266L private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L private const val ANIMATION_DELAY_NAV_FADE_IN = @@ -54,6 +61,8 @@ class ActivityLaunchAnimator( private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f) private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f) + private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC) + /** * Given the [linearProgress] of a launch animation, return the linear progress of the * sub-animation starting [delay] ms after the launch animation and that lasts [duration]. @@ -68,6 +77,8 @@ class ActivityLaunchAnimator( } } + private val packageManager = AppGlobals.getPackageManager() + /** The interpolator used for the width, height, Y position and corner radius. */ private val animationInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.launch_animation_interpolator_y) @@ -76,6 +87,8 @@ class ActivityLaunchAnimator( private val animationInterpolatorX = AnimationUtils.loadInterpolator(context, R.interpolator.launch_animation_interpolator_x) + private val cornerRadii = FloatArray(8) + /** * Start an intent and animate the opening window. The intent will be started by running * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch @@ -288,10 +301,7 @@ class ActivityLaunchAnimator( var right: Int, var topCornerRadius: Float = 0f, - var bottomCornerRadius: Float = 0f, - - var contentAlpha: Float = 1f, - var backgroundAlpha: Float = 1f + var bottomCornerRadius: Float = 0f ) { private val startTop = top private val startBottom = bottom @@ -331,6 +341,9 @@ class ActivityLaunchAnimator( val centerY: Float get() = top + height / 2f + + /** Whether the expanded view should be visible or hidden. */ + var visible: Boolean = true } @VisibleForTesting @@ -452,22 +465,39 @@ class ActivityLaunchAnimator( 0f } + // We add an extra layer with the same color as the app splash screen background color, + // which is usually the same color of the app background. We first fade in this layer + // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the + // launch container and reveal the opening window. + val windowBackgroundColor = extractSplashScreenBackgroundColor(window) + val windowBackgroundLayer = GradientDrawable().apply { + setColor(windowBackgroundColor) + alpha = 0 + } + // Update state. val animator = ValueAnimator.ofFloat(0f, 1f) this.animator = animator animator.duration = ANIMATION_DURATION animator.interpolator = Interpolators.LINEAR + val launchContainerOverlay = launchContainer.overlay animator.addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?, isReverse: Boolean) { Log.d(TAG, "Animation started") controller.onLaunchAnimationStart(isExpandingFullyAbove) + + // Add the drawable to the launch container overlay. Overlays always draw + // drawables after views, so we know that it will be drawn above any view added + // by the controller. + launchContainerOverlay.add(windowBackgroundLayer) } override fun onAnimationEnd(animation: Animator?) { Log.d(TAG, "Animation ended") iCallback?.invoke() controller.onLaunchAnimationEnd(isExpandingFullyAbove) + launchContainerOverlay.remove(windowBackgroundLayer) } }) @@ -491,24 +521,61 @@ class ActivityLaunchAnimator( state.bottomCornerRadius = MathUtils.lerp(startBottomCornerRadius, endRadius, progress) - val contentAlphaProgress = getProgress(linearProgress, 0, - ANIMATION_DURATION_FADE_OUT_CONTENT) - state.contentAlpha = - 1 - CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(contentAlphaProgress) - - val backgroundAlphaProgress = getProgress(linearProgress, - ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW) - state.backgroundAlpha = - 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(backgroundAlphaProgress) + // The expanding view can/should be hidden once it is completely coverred by the + // windowBackgroundLayer. + state.visible = + getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1 applyStateToWindow(window, state) + applyStateToWindowBackgroundLayer(windowBackgroundLayer, state, linearProgress) navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } + + // If we started expanding the view, we make it 1 pixel smaller on all sides to + // avoid artefacts on the corners caused by anti-aliasing of the view background and + // the window background layer. + if (state.top != startTop && state.left != startLeft && + state.bottom != startBottom && state.right != startRight) { + state.top += 1 + state.left += 1 + state.right -= 1 + state.bottom -= 1 + } controller.onLaunchAnimationProgress(state, progress, linearProgress) } animator.start() } + /** Extract the background color of the app splash screen. */ + private fun extractSplashScreenBackgroundColor(window: RemoteAnimationTarget): Int { + val taskInfo = window.taskInfo + val windowPackage = taskInfo.topActivity.packageName + val userId = taskInfo.userId + val windowContext = context.createPackageContextAsUser( + windowPackage, Context.CONTEXT_RESTRICTED, UserHandle.of(userId)) + val activityInfo = taskInfo.topActivityInfo + val splashScreenThemeName = packageManager.getSplashScreenTheme(windowPackage, userId) + val splashScreenThemeId = if (splashScreenThemeName != null) { + windowContext.resources.getIdentifier(splashScreenThemeName, null, null) + } else { + 0 + } + + val themeResId = when { + splashScreenThemeId != 0 -> splashScreenThemeId + activityInfo.themeResource != 0 -> activityInfo.themeResource + else -> com.android.internal.R.style.Theme_DeviceDefault_DayNight + } + + if (themeResId != windowContext.themeResId) { + windowContext.setTheme(themeResId) + } + + val windowAttrs = SplashScreenWindowAttrs() + SplashscreenContentDrawer.getWindowAttrs(windowContext, windowAttrs) + return SplashscreenContentDrawer.peekWindowBGColor(windowContext, windowAttrs) + } + private fun applyStateToWindow(window: RemoteAnimationTarget, state: State) { val screenBounds = window.screenSpaceBounds val centerX = (screenBounds.left + screenBounds.right) / 2f @@ -563,6 +630,41 @@ class ActivityLaunchAnimator( transactionApplier.scheduleApply(params) } + private fun applyStateToWindowBackgroundLayer( + drawable: GradientDrawable, + state: State, + linearProgress: Float + ) { + // Update position. + drawable.setBounds(state.left, state.top, state.right, state.bottom) + + // Update radius. + cornerRadii[0] = state.topCornerRadius + cornerRadii[1] = state.topCornerRadius + cornerRadii[2] = state.topCornerRadius + cornerRadii[3] = state.topCornerRadius + cornerRadii[4] = state.bottomCornerRadius + cornerRadii[5] = state.bottomCornerRadius + cornerRadii[6] = state.bottomCornerRadius + cornerRadii[7] = state.bottomCornerRadius + drawable.cornerRadii = cornerRadii + + // We first fade in the background layer to hide the expanding view, then fade it out + // with SRC mode to draw a hole punch in the status bar and reveal the opening window. + val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) + if (fadeInProgress < 1) { + val alpha = CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(fadeInProgress) + drawable.alpha = (alpha * 0xFF).roundToInt() + drawable.setXfermode(null) + } else { + val fadeOutProgress = getProgress(linearProgress, + ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW) + val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress) + drawable.alpha = (alpha * 0xFF).roundToInt() + drawable.setXfermode(SRC_MODE) + } + } + private fun applyStateToNavigationBar( navigationBar: RemoteAnimationTarget, state: State, diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index ce9feede8759..4b655a1a1b02 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -4,8 +4,6 @@ import android.graphics.Canvas import android.graphics.ColorFilter import android.graphics.Matrix import android.graphics.PixelFormat -import android.graphics.PorterDuff -import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable @@ -111,9 +109,7 @@ open class GhostedViewLaunchAnimatorController( } override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { - backgroundView = FrameLayout(launchContainer.context).apply { - forceHasOverlappingRendering(false) - } + backgroundView = FrameLayout(launchContainer.context) launchContainerOverlay.add(backgroundView) // We wrap the ghosted view background and use it to draw the expandable background. Its @@ -125,9 +121,7 @@ open class GhostedViewLaunchAnimatorController( // Create a ghost of the view that will be moving and fading out. This allows to fade out // the content before fading out the background. - ghostView = GhostView.addGhost(ghostedView, launchContainer).apply { - setLayerType(View.LAYER_TYPE_HARDWARE, null) - } + ghostView = GhostView.addGhost(ghostedView, launchContainer) val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX matrix.getValues(initialGhostViewMatrixValues) @@ -139,7 +133,18 @@ open class GhostedViewLaunchAnimatorController( linearProgress: Float ) { val ghostView = this.ghostView!! - ghostView.alpha = state.contentAlpha + val backgroundView = this.backgroundView!! + + if (!state.visible) { + if (ghostView.visibility == View.VISIBLE) { + // Making the ghost view invisible will make the ghosted view visible, so order is + // important here. + ghostView.visibility = View.INVISIBLE + ghostedView.visibility = View.INVISIBLE + backgroundView.visibility = View.INVISIBLE + } + return + } val scale = min(state.widthRatio, state.heightRatio) ghostViewMatrix.setValues(initialGhostViewMatrixValues) @@ -150,14 +155,12 @@ open class GhostedViewLaunchAnimatorController( ) ghostView.animationMatrix = ghostViewMatrix - val backgroundView = this.backgroundView!! backgroundView.top = state.top backgroundView.bottom = state.bottom backgroundView.left = state.left backgroundView.right = state.right val backgroundDrawable = backgroundDrawable!! - backgroundDrawable.alpha = (0xFF * state.backgroundAlpha).toInt() backgroundDrawable.wrapped?.let { setBackgroundCornerRadius(it, state.topCornerRadius, state.bottomCornerRadius) } @@ -168,6 +171,7 @@ open class GhostedViewLaunchAnimatorController( GhostView.removeGhost(ghostedView) launchContainerOverlay.remove(backgroundView) + ghostedView.visibility = View.VISIBLE ghostedView.invalidate() } @@ -203,10 +207,6 @@ open class GhostedViewLaunchAnimatorController( } private class WrappedDrawable(val wrapped: Drawable?) : Drawable() { - companion object { - private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC) - } - private var currentAlpha = 0xFF private var previousBounds = Rect() @@ -220,7 +220,6 @@ open class GhostedViewLaunchAnimatorController( wrapped.alpha = currentAlpha wrapped.bounds = bounds - setXfermode(wrapped, SRC_MODE) applyBackgroundRadii() wrapped.draw(canvas) @@ -230,7 +229,6 @@ open class GhostedViewLaunchAnimatorController( // background. wrapped.alpha = 0 wrapped.bounds = previousBounds - setXfermode(wrapped, null) restoreBackgroundRadii() } @@ -257,27 +255,6 @@ open class GhostedViewLaunchAnimatorController( wrapped?.colorFilter = filter } - private fun setXfermode(background: Drawable, mode: PorterDuffXfermode?) { - if (background is InsetDrawable) { - background.drawable?.let { setXfermode(it, mode) } - return - } - - if (background !is LayerDrawable) { - background.setXfermode(mode) - return - } - - // We set the xfermode on the first layer that is not a mask. Most of the time it will - // be the "background layer". - for (i in 0 until background.numberOfLayers) { - if (background.getId(i) != android.R.id.mask) { - background.getDrawable(i).setXfermode(mode) - break - } - } - } - fun setBackgroundRadius(topCornerRadius: Float, bottomCornerRadius: Float) { updateRadii(cornerRadii, topCornerRadius, bottomCornerRadius) invalidateSelf() diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md index 521620935ddc..89c28a074c76 100644 --- a/packages/SystemUI/docs/qs-tiles.md +++ b/packages/SystemUI/docs/qs-tiles.md @@ -305,6 +305,7 @@ This section describes necessary and recommended steps when implementing a Quick * Inject a `Provider` for the tile created before. * Add a case to the `switch` with a unique String spec for the chosen tile. 5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles. +6. In [SystemUI/res/values/tiles_states_strings.xml](/packages/SystemUI/res/values/tiles_states_strings.xml), add a new array for your tile. The name has to be `tile_states_<spec>`. Use a good description to help the translators. #### Abstract methods in QSTileImpl diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index f1c1477c5b0a..77018d736163 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -166,11 +166,13 @@ public interface QSTile { public boolean handlesLongClick = true; public boolean showRippleEffect = true; public Drawable sideViewCustomDrawable; + public String spec; public boolean copyTo(State other) { if (other == null) throw new IllegalArgumentException(); if (!other.getClass().equals(getClass())) throw new IllegalArgumentException(); - final boolean changed = !Objects.equals(other.icon, icon) + final boolean changed = !Objects.equals(other.spec, spec) + || !Objects.equals(other.icon, icon) || !Objects.equals(other.iconSupplier, iconSupplier) || !Objects.equals(other.label, label) || !Objects.equals(other.secondaryLabel, secondaryLabel) @@ -188,6 +190,7 @@ public interface QSTile { || !Objects.equals(other.handlesLongClick, handlesLongClick) || !Objects.equals(other.showRippleEffect, showRippleEffect) || !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable); + other.spec = spec; other.icon = icon; other.iconSupplier = iconSupplier; other.label = label; @@ -216,6 +219,7 @@ public interface QSTile { // This string may be used for CTS testing of tiles, so removing elements is discouraged. protected StringBuilder toStringBuilder() { final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('['); + sb.append("spec=").append(spec); sb.append(",icon=").append(icon); sb.append(",iconSupplier=").append(iconSupplier); sb.append(",label=").append(label); diff --git a/packages/SystemUI/res/drawable/screenshot_edit_background.xml b/packages/SystemUI/res/drawable/screenshot_edit_background.xml new file mode 100644 index 000000000000..5c3c12c572f6 --- /dev/null +++ b/packages/SystemUI/res/drawable/screenshot_edit_background.xml @@ -0,0 +1,33 @@ +<?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. + --> +<!-- Long screenshot edit FAB background --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:color="?android:textColorPrimary"> + <item android:id="@android:id/background"> + <shape android:shape="rectangle"> + <solid android:color="?androidprv:attr/colorAccentSecondary"/> + <corners android:radius="16dp"/> + </shape> + </item> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="?android:textColorPrimary"/> + <corners android:radius="16dp"/> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/screenshot_save_background.xml b/packages/SystemUI/res/drawable/screenshot_save_background.xml new file mode 100644 index 000000000000..cfd2dc214477 --- /dev/null +++ b/packages/SystemUI/res/drawable/screenshot_save_background.xml @@ -0,0 +1,33 @@ +<?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. + --> +<!-- Long screenshot save button background --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:color="?android:textColorPrimary"> + <item android:id="@android:id/background"> + <shape android:shape="rectangle"> + <solid android:color="?androidprv:attr/colorAccentSecondary"/> + <corners android:radius="20dp"/> + </shape> + </item> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="?android:textColorPrimary"/> + <corners android:radius="20dp"/> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml index f7357b27af5f..2067f85249a2 100644 --- a/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml +++ b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml @@ -19,8 +19,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingStart="@dimen/accessibility_floating_menu_padding" - android:paddingEnd="@dimen/accessibility_floating_menu_padding" + android:paddingStart="@dimen/accessibility_floating_menu_small_padding" + android:paddingEnd="@dimen/accessibility_floating_menu_small_padding" android:orientation="vertical" android:gravity="center"> @@ -29,9 +29,4 @@ android:layout_width="@dimen/accessibility_floating_menu_small_width_height" android:layout_height="@dimen/accessibility_floating_menu_small_width_height"/> - <View - android:id="@+id/transparent_divider" - android:layout_width="match_parent" - android:layout_height="@dimen/accessibility_floating_menu_padding"/> - </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 0dc147324008..80b7d1f8970f 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -53,6 +53,7 @@ android:paddingEnd="@dimen/keyguard_indication_text_padding" android:textAppearance="@style/TextAppearance.Keyguard.BottomArea" android:alpha=".8" + android:accessibilityLiveRegion="polite" android:visibility="gone"/> </LinearLayout> diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml index de3384657d9d..8e30aecd6a27 100644 --- a/packages/SystemUI/res/layout/long_screenshot.xml +++ b/packages/SystemUI/res/layout/long_screenshot.xml @@ -28,11 +28,11 @@ android:id="@+id/save" style="@android:style/Widget.DeviceDefault.Button.Colored" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="40dp" android:text="@string/save" android:layout_marginStart="8dp" android:layout_marginTop="4dp" - android:backgroundTint="?androidprv:attr/colorAccentSecondary" + android:background="@drawable/screenshot_save_background" android:textColor="?android:textColorSecondary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -114,7 +114,7 @@ android:layout_marginBottom="16dp" android:layout_marginEnd="16dp" style="@android:style/Widget.DeviceDefault.Button.Colored" - android:backgroundTint="?androidprv:attr/colorAccentSecondary" + android:background="@drawable/screenshot_edit_background" android:src="@drawable/ic_screenshot_edit" android:contentDescription="@string/screenshot_edit_label" android:tint="?android:textColorSecondary" diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml index 720ae8dbf4d8..459fb6611fd7 100644 --- a/packages/SystemUI/res/layout/privacy_dialog.xml +++ b/packages/SystemUI/res/layout/privacy_dialog.xml @@ -24,8 +24,6 @@ android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins" android:layout_marginTop="8dp" android:orientation="vertical" - android:paddingLeft="@dimen/ongoing_appops_dialog_side_padding" - android:paddingRight="@dimen/ongoing_appops_dialog_side_padding" android:paddingBottom="12dp" android:paddingTop="8dp" android:background="@drawable/qs_dialog_bg" diff --git a/packages/SystemUI/res/layout/privacy_dialog_item.xml b/packages/SystemUI/res/layout/privacy_dialog_item.xml index b91fb29c6f51..7c8945eddc70 100644 --- a/packages/SystemUI/res/layout/privacy_dialog_item.xml +++ b/packages/SystemUI/res/layout/privacy_dialog_item.xml @@ -23,6 +23,9 @@ android:orientation="horizontal" android:layout_marginTop="4dp" android:importantForAccessibility="yes" + android:background="?android:attr/selectableItemBackground" + android:paddingLeft="@dimen/ongoing_appops_dialog_side_padding" + android:paddingRight="@dimen/ongoing_appops_dialog_side_padding" android:focusable="true" > <!-- 4dp marginTop makes 20dp minimum between icons --> diff --git a/packages/SystemUI/res/layout/qs_paged_page.xml b/packages/SystemUI/res/layout/qs_paged_page.xml index a4f0a0c85d41..98804fc80b50 100644 --- a/packages/SystemUI/res/layout/qs_paged_page.xml +++ b/packages/SystemUI/res/layout/qs_paged_page.xml @@ -18,4 +18,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/tile_page" android:layout_width="match_parent" - android:layout_height="match_parent"/> + android:layout_height="match_parent" + android:paddingStart="@dimen/notification_side_paddings" + android:paddingEnd="@dimen/notification_side_paddings" + android:clipChildren="false" + android:clipToPadding="false" /> diff --git a/packages/SystemUI/res/layout/qs_tile_side_icon.xml b/packages/SystemUI/res/layout/qs_tile_side_icon.xml index 1ae0a1c7c5a8..f1b7259f4c1d 100644 --- a/packages/SystemUI/res/layout/qs_tile_side_icon.xml +++ b/packages/SystemUI/res/layout/qs_tile_side_icon.xml @@ -23,7 +23,7 @@ <ImageView android:id="@+id/customDrawable" android:layout_width="wrap_content" - android:layout_height="@dimen/qs_icon_size" + android:layout_height="@dimen/qs_side_view_size" android:layout_marginEnd="@dimen/qs_drawable_end_margin" android:adjustViewBounds="true" android:scaleType="fitCenter" @@ -39,4 +39,4 @@ android:visibility="gone" android:importantForAccessibility="no" /> -</FrameLayout>
\ No newline at end of file +</FrameLayout> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index fb39d3e4027d..dbbf641e4bf7 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -64,18 +64,6 @@ android:clipToPadding="false" android:clipChildren="false"> - <com.android.systemui.scrim.ScrimView - android:id="@+id/scrim_notifications" - android:layout_width="0dp" - android:layout_height="0dp" - android:importantForAccessibility="no" - systemui:ignoreRightInset="true" - systemui:layout_constraintStart_toStartOf="parent" - systemui:layout_constraintEnd_toEndOf="parent" - systemui:layout_constraintTop_toTopOf="parent" - systemui:layout_constraintBottom_toBottomOf="parent" - /> - <include layout="@layout/keyguard_status_view" android:visibility="gone"/> diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 08284a0a2e3a..bea50e87a29a 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -51,6 +51,14 @@ sysui:ignoreRightInset="true" /> + <com.android.systemui.scrim.ScrimView + android:id="@+id/scrim_notifications" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:importantForAccessibility="no" + sysui:ignoreRightInset="true" + /> + <com.android.systemui.statusbar.LightRevealScrim android:id="@+id/light_reveal_scrim" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ab606babe804..0be648f1b9bd 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -561,6 +561,7 @@ <dimen name="qs_tile_icon_background_stroke_width">-1dp</dimen> <dimen name="qs_tile_background_size">56dp</dimen> <dimen name="qs_icon_size">20dp</dimen> + <dimen name="qs_side_view_size">28dp</dimen> <dimen name="qs_label_container_margin">10dp</dimen> <dimen name="qs_quick_tile_size">60dp</dimen> <dimen name="qs_tile_padding">12dp</dimen> @@ -1467,16 +1468,17 @@ <dimen name="medium_content_padding_above_name">4dp</dimen> <!-- Accessibility floating menu --> - <dimen name="accessibility_floating_menu_elevation">5dp</dimen> + <dimen name="accessibility_floating_menu_elevation">3dp</dimen> <dimen name="accessibility_floating_menu_stroke_width">1dp</dimen> <dimen name="accessibility_floating_menu_stroke_inset">-2dp</dimen> <dimen name="accessibility_floating_menu_margin">16dp</dimen> - <dimen name="accessibility_floating_menu_padding">6dp</dimen> + <dimen name="accessibility_floating_menu_small_padding">6dp</dimen> <dimen name="accessibility_floating_menu_small_width_height">36dp</dimen> <dimen name="accessibility_floating_menu_small_single_radius">25dp</dimen> <dimen name="accessibility_floating_menu_small_multiple_radius">20dp</dimen> + <dimen name="accessibility_floating_menu_large_padding">8dp</dimen> <dimen name="accessibility_floating_menu_large_width_height">56dp</dimen> - <dimen name="accessibility_floating_menu_large_single_radius">33dp</dimen> + <dimen name="accessibility_floating_menu_large_single_radius">35dp</dimen> <dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen> <dimen name="accessibility_floating_tooltip_arrow_width">8dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index aadcaba09402..46511682464b 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -199,11 +199,11 @@ <item name="android:textSize">@dimen/qs_tile_text_size</item> <item name="android:letterSpacing">0.01</item> <item name="android:lineHeight">20sp</item> - <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> </style> <style name="TextAppearance.QS.TileLabel.Secondary"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> </style> <style name="TextAppearance.QS.UserSwitcher"> @@ -223,7 +223,7 @@ </style> <style name="TextAppearance.QS.Status"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textSize">14sp</item> <item name="android:letterSpacing">0.01</item> @@ -231,20 +231,19 @@ </style> <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> <item name="android:textColor">?android:attr/textColorSecondary</item> </style> - <style name="TextAppearance.QS.Status.Carriers"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - </style> + <style name="TextAppearance.QS.Status.Carriers" /> <style name="TextAppearance.QS.Status.Carriers.NoCarrierText"> + <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> <item name="android:textColor">?android:attr/textColorSecondary</item> </style> <style name="TextAppearance.QS.Status.Build"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> <item name="android:textColor">?android:attr/textColorSecondary</item> </style> @@ -643,7 +642,7 @@ <item name="android:tint">?android:attr/textColorPrimary</item> <item name="android:stateListAnimator">@anim/media_button_state_list_animator</item> <item name="android:padding">12dp</item> - <item name="android:scaleType">fitCenter</item> + <item name="android:scaleType">centerInside</item> </style> <style name="MediaPlayer.OutlineButton"> @@ -663,7 +662,8 @@ <style name="MediaPlayer.AppIcon"> <item name="android:background">@drawable/qs_media_icon_background</item> - <item name="android:backgroundTint">?android:attr/textColorPrimary</item> + <item name="android:backgroundTint">@color/media_player_solid_button_bg</item> + <item name="android:padding">4dp</item> </style> <style name="MediaPlayer.Album"> diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml new file mode 100644 index 000000000000..5ac7c1d3c65b --- /dev/null +++ b/packages/SystemUI/res/values/tiles_states_strings.xml @@ -0,0 +1,284 @@ +<!-- + ~ 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. + --> + +<!-- This resources set the default subtitle for tiles. This way, each tile can be translated + separately. + The indices in the array correspond to the state values in QSTile: + * STATE_UNAVAILABLE + * STATE_INACTIVE + * STATE_ACTIVE + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as if + they could appear. +--> +<resources> + <!-- Default names for tiles states: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_default"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for internet tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_internet"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for wifi tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_wifi"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for cell (data) tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear.[CHAR LIMIT=32] --> + <string-array name="tile_states_cell"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for battery (saver) tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_battery"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for dnd (Do not disturb) tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_dnd"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for flashlight tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_flashlight"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for rotation (lock) tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_rotation"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for bt (bluetooth) tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_bt"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for airplane tile: unavailable, off, on [CHAR LIMIT=32] --> + <string-array name="tile_states_airplane"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for location tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_location"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for hotspot tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_hotspot"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for (color) inversion tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_inversion"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for (data) saver tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_saver"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for dark (mode) tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_dark"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for work (mode) tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_work"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for cast tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_cast"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for night (light) tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_night"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for screenrecord tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_screenrecord"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for reverse (charging) tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_reverse"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for reduce_brightness tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_reduce_brightness"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for cameratoggle tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear.[CHAR LIMIT=32] --> + <string-array name="tile_states_cameratoggle"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for mictoggle tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_mictoggle"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for (home) controls tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_controls"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for (quick access) wallet tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_wallet"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> + + <!-- State names for alarm tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_alarm"> + <item>@string/tile_unavailable</item> + <item>@string/switch_bar_off</item> + <item>@string/switch_bar_on</item> + </string-array> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index 7cb4846cf7e7..92af58eb9af4 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -107,6 +107,21 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie } }; + private final StatusBarStateController.StateListener mStatusBarStatePersistentListener = + new StatusBarStateController.StateListener() { + @Override + public void onDozeAmountChanged(float linear, float eased) { + boolean noAnimation = (mDozeAmount == 0f && linear == 1f) + || (mDozeAmount == 1f && linear == 0f); + boolean isDozing = linear > mDozeAmount; + mDozeAmount = linear; + if (mIsDozing != isDozing) { + mIsDozing = isDozing; + mView.animateDoze(mIsDozing, !noAnimation); + } + } + }; + private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { @Override @@ -133,14 +148,15 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie updateLocale(); mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED)); - mStatusBarStateController.addCallback(mStatusBarStateListener); - mIsDozing = mStatusBarStateController.isDozing(); mDozeAmount = mStatusBarStateController.getDozeAmount(); mBatteryController.addCallback(mBatteryCallback); mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); mKeyguardShowing = true; + mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener); + mStatusBarStateController.addCallback(mStatusBarStatePersistentListener); + refreshTime(); initColors(); mView.animateDoze(mIsDozing, false); @@ -149,9 +165,11 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie @Override protected void onViewDetached() { mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver); - mStatusBarStateController.removeCallback(mStatusBarStateListener); mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); mBatteryController.removeCallback(mBatteryCallback); + if (!mView.isAttachedToWindow()) { + mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener); + } } /** Animate the clock appearance */ @@ -199,19 +217,4 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie mView.setColors(mDozingColor, mLockScreenColor); mView.animateDoze(mIsDozing, false); } - - private final StatusBarStateController.StateListener mStatusBarStateListener = - new StatusBarStateController.StateListener() { - @Override - public void onDozeAmountChanged(float linear, float eased) { - boolean noAnimation = (mDozeAmount == 0f && linear == 1f) - || (mDozeAmount == 1f && linear == 0f); - boolean isDozing = linear > mDozeAmount; - mDozeAmount = linear; - if (mIsDozing != isDozing) { - mIsDozing = isDozing; - mView.animateDoze(mIsDozing, !noAnimation); - } - } - }; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 3e084b7d692a..fbea1e9a594d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -305,10 +305,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS // know that it should re-position our SmartSpace. if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) { mKeyguardUnlockAnimationController.updateLockscreenSmartSpacePosition(); - } else { - // Otherwise, reset Y translation in case it's still offset from a previous shared - // element transition. - ((View) mSmartspaceView).setTranslationY(0f); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 7b6514a63195..e8cc5c8a49f3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; @@ -66,7 +67,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV ConfigurationController configurationController, DozeParameters dozeParameters, KeyguardUnlockAnimationController keyguardUnlockAnimationController, - SmartspaceTransitionController smartspaceTransitionController) { + SmartspaceTransitionController smartspaceTransitionController, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { super(keyguardStatusView); mKeyguardSliceViewController = keyguardSliceViewController; mKeyguardClockSwitchController = keyguardClockSwitchController; @@ -75,7 +77,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mDozeParameters = dozeParameters; mKeyguardStateController = keyguardStateController; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, - dozeParameters); + dozeParameters, unlockedScreenOffAnimationController); mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; mSmartspaceTransitionController = smartspaceTransitionController; @@ -87,7 +89,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV // element transition. if (keyguardStateController.isShowing()) { mView.setChildrenAlphaExcludingClockView(1f); - mKeyguardClockSwitchController.setChildrenAlphaExcludingSmartspace(1f); } } }); @@ -238,13 +239,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } /** - * @return {@code true} if we are currently animating the screen off from unlock - */ - public boolean isAnimatingScreenOffFromUnlocked() { - return mKeyguardVisibilityHelper.isAnimatingScreenOffFromUnlocked(); - } - - /** * Set the visibility of the keyguard status view based on some new state. */ public void setKeyguardStatusViewVisibility( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index b6a58dc7cb91..7edecc80a27e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; /** @@ -38,16 +39,19 @@ public class KeyguardVisibilityHelper { private View mView; private final KeyguardStateController mKeyguardStateController; private final DozeParameters mDozeParameters; + private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private boolean mKeyguardViewVisibilityAnimating; private boolean mLastOccludedState = false; - private boolean mAnimatingScreenOff; private final AnimationProperties mAnimationProperties = new AnimationProperties(); - public KeyguardVisibilityHelper(View view, KeyguardStateController keyguardStateController, - DozeParameters dozeParameters) { + public KeyguardVisibilityHelper(View view, + KeyguardStateController keyguardStateController, + DozeParameters dozeParameters, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { mView = view; mKeyguardStateController = keyguardStateController; mDozeParameters = dozeParameters; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; } public boolean isVisibilityAnimating() { @@ -122,32 +126,14 @@ public class KeyguardVisibilityHelper { .alpha(1f) .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable) .start(); - } else if (mDozeParameters.shouldControlUnlockedScreenOff()) { + } else if (mUnlockedScreenOffAnimationController + .isScreenOffLightRevealAnimationPlaying()) { mKeyguardViewVisibilityAnimating = true; - mAnimatingScreenOff = true; - - mView.setVisibility(View.VISIBLE); - mView.setAlpha(0f); - float currentY = mView.getY(); - mView.setY(currentY - mView.getHeight() * 0.1f); - int duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP; - int delay = (int) (duration * .6f); - // We animate the Y properly separately using the PropertyAnimator, as the panel - // view als needs to update the end position. - mAnimationProperties.setDuration(duration).setDelay(delay); - PropertyAnimator.cancelAnimation(mView, AnimatableProperty.Y); - PropertyAnimator.setProperty(mView, AnimatableProperty.Y, currentY, - mAnimationProperties, - true /* animate */); - - mView.animate() - .setStartDelay(delay) - .setDuration(duration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .alpha(1f) - .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable) - .start(); + // Ask the screen off animation controller to animate the keyguard visibility for us + // since it may need to be cancelled due to keyguard lifecycle events. + mUnlockedScreenOffAnimationController.animateInKeyguard( + mView, mAnimateKeyguardStatusViewVisibleEndRunnable); } else { mView.setVisibility(View.VISIBLE); mView.setAlpha(1f); @@ -172,13 +158,5 @@ public class KeyguardVisibilityHelper { private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> { mKeyguardViewVisibilityAnimating = false; - mAnimatingScreenOff = false; }; - - /** - * @return {@code true} if we are currently animating the screen off from unlock - */ - public boolean isAnimatingScreenOffFromUnlocked() { - return mAnimatingScreenOff; - } } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index 2167876d9cb6..eff412e49836 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -87,6 +87,11 @@ public class LockIconView extends ImageView { updateSensorRect(h, w); } + @Override + public boolean hasOverlappingRendering() { + return false; + } + float getLocationTop() { return mLockIconCenter.y - mRadius; } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 6b85ba8420fc..ccc487925fe4 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -451,4 +451,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private final AccessibilityManager.TouchExplorationStateChangeListener mTouchExplorationStateChangeListener = enabled -> updateClickListener(); + + /** + * Set the alpha of this view. + */ + public void setAlpha(float alpha) { + mView.setAlpha(alpha); + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index 964b135d7b50..2e6c9e48d61a 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -18,6 +18,7 @@ package com.android.systemui.accessibility; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import android.annotation.NonNull; import android.annotation.UiContext; @@ -36,6 +37,7 @@ import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import android.view.WindowMetrics; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -106,16 +108,41 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL R.string.magnification_mode_switch_click_label)); info.addAction(clickAction); info.setClickable(true); + info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up, + mContext.getString(R.string.accessibility_control_move_up))); + info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down, + mContext.getString(R.string.accessibility_control_move_down))); + info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left, + mContext.getString(R.string.accessibility_control_move_left))); + info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right, + mContext.getString(R.string.accessibility_control_move_right))); } @Override public boolean performAccessibilityAction(View host, int action, Bundle args) { - if (action == AccessibilityAction.ACTION_CLICK.getId()) { - handleSingleTap(); + if (performA11yAction(action)) { return true; } return super.performAccessibilityAction(host, action, args); } + + private boolean performA11yAction(int action) { + final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); + if (action == AccessibilityAction.ACTION_CLICK.getId()) { + handleSingleTap(); + } else if (action == R.id.accessibility_action_move_up) { + moveButton(0, -windowBounds.height()); + } else if (action == R.id.accessibility_action_move_down) { + moveButton(0, windowBounds.height()); + } else if (action == R.id.accessibility_action_move_left) { + moveButton(-windowBounds.width(), 0); + } else if (action == R.id.accessibility_action_move_right) { + moveButton(windowBounds.width(), 0); + } else { + return false; + } + return true; + } }); mWindowInsetChangeRunnable = this::onWindowInsetChanged; mImageView.setOnApplyWindowInsetsListener((v, insets) -> { @@ -362,21 +389,21 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL PixelFormat.TRANSPARENT); params.gravity = Gravity.TOP | Gravity.LEFT; params.accessibilityTitle = getAccessibilityWindowTitle(context); + params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; return params; } private Rect getDraggableWindowBounds() { final int layoutMargin = mContext.getResources().getDimensionPixelSize( R.dimen.magnification_switch_button_margin); - final Rect boundRect = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); - final Insets systemBars = - mWindowManager.getCurrentWindowMetrics().getWindowInsets() - .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()); - final Rect insets = new Rect(layoutMargin, - systemBars.top + layoutMargin, - mParams.width + layoutMargin, - mParams.height + layoutMargin + systemBars.bottom); - boundRect.inset(insets); + final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); + final Insets windowInsets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility( + WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()); + final Rect boundRect = new Rect(windowMetrics.getBounds()); + boundRect.offsetTo(0, 0); + boundRect.inset(0, 0, mParams.width, mParams.height); + boundRect.inset(windowInsets); + boundRect.inset(layoutMargin, layoutMargin); return boundRect; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java index e85bd885c88a..259a9f7c6f3b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java @@ -18,6 +18,8 @@ package com.android.systemui.accessibility.floatingmenu; import static android.util.MathUtils.constrain; import static android.util.MathUtils.sq; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.navigationBars; import static java.util.Objects.requireNonNull; @@ -29,6 +31,7 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -42,7 +45,9 @@ import android.view.Gravity; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; +import android.view.WindowMetrics; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.animation.Animation; @@ -90,6 +95,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout private boolean mIsShowing; private boolean mIsDownInEnlargedTouchArea; private boolean mIsDragging = false; + private boolean mImeVisibility; @Alignment private int mAlignment = Alignment.RIGHT; @SizeType @@ -369,6 +375,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout mIsShowing = true; mWindowManager.addView(this, mCurrentLayoutParams); + + setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets)); setSystemGestureExclusion(); } @@ -379,6 +387,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout mIsShowing = false; mWindowManager.removeView(this); + + setOnApplyWindowInsetsListener(null); setSystemGestureExclusion(); } @@ -409,11 +419,13 @@ public class AccessibilityFloatingMenuView extends FrameLayout mSizeType = newSizeType; - updateIconSizeWith(newSizeType); + updateItemViewWith(newSizeType); updateRadiusWith(newSizeType, mRadiusType, mTargets.size()); // When the icon sized changed, the menu size and location will be impacted. updateLocationWith(mAlignment, mPercentageY); + updateScrollModeWith(hasExceededMaxLayoutHeight()); + updateOffsetWith(mShapeType, mAlignment); setSystemGestureExclusion(); fadeOut(); @@ -565,6 +577,16 @@ public class AccessibilityFloatingMenuView extends FrameLayout return mListView.dispatchTouchEvent(event); } + private WindowInsets onWindowInsetsApplied(WindowInsets insets) { + final boolean currentImeVisibility = insets.isVisible(ime()); + if (currentImeVisibility != mImeVisibility) { + mImeVisibility = currentImeVisibility; + updateLocationWith(mAlignment, mPercentageY); + } + + return insets; + } + private boolean isMovingTowardsScreenEdge(@Alignment int side, int currentRawX, int downX) { return (side == Alignment.RIGHT && currentRawX > downX) || (side == Alignment.LEFT && downX > currentRawX); @@ -601,24 +623,35 @@ public class AccessibilityFloatingMenuView extends FrameLayout mScreenHeight = dm.heightPixels; mMargin = res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin); - mPadding = - res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_padding); mInset = res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset); mSquareScaledTouchSlop = sq(ViewConfiguration.get(getContext()).getScaledTouchSlop()); + + updateItemViewDimensionsWith(mSizeType); } - private void updateIconSizeWith(@SizeType int sizeType) { + private void updateItemViewDimensionsWith(@SizeType int sizeType) { final Resources res = getResources(); + final int paddingResId = + sizeType == SizeType.SMALL + ? R.dimen.accessibility_floating_menu_small_padding + : R.dimen.accessibility_floating_menu_large_padding; + mPadding = res.getDimensionPixelSize(paddingResId); + final int iconResId = sizeType == SizeType.SMALL ? R.dimen.accessibility_floating_menu_small_width_height : R.dimen.accessibility_floating_menu_large_width_height; mIconWidth = res.getDimensionPixelSize(iconResId); mIconHeight = mIconWidth; + } + + private void updateItemViewWith(@SizeType int sizeType) { + updateItemViewDimensionsWith(sizeType); + mAdapter.setItemPadding(mPadding); mAdapter.setIconWidthHeight(mIconWidth); mAdapter.notifyDataSetChanged(); } @@ -630,7 +663,6 @@ public class AccessibilityFloatingMenuView extends FrameLayout final LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - layoutParams.setMargins(mMargin, mMargin, mMargin, mMargin); mListView.setLayoutParams(layoutParams); final InstantInsetLayerDrawable layerDrawable = new InstantInsetLayerDrawable(new Drawable[]{background}); @@ -645,6 +677,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout } private void updateListView() { + final LayoutParams layoutParams = (FrameLayout.LayoutParams) mListView.getLayoutParams(); + layoutParams.setMargins(mMargin, mMargin, mMargin, mMargin); + mListView.setLayoutParams(layoutParams); + final int elevation = getResources().getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation); mListView.setElevation(elevation); @@ -658,6 +694,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, PixelFormat.TRANSLUCENT); + params.receiveInsetsIgnoringZOrder = true; params.windowAnimations = android.R.style.Animation_Translucent; params.gravity = Gravity.START | Gravity.TOP; params.x = getMaxWindowX(); @@ -676,11 +713,13 @@ public class AccessibilityFloatingMenuView extends FrameLayout updateDimensions(); updateListView(); - updateIconSizeWith(mSizeType); + updateItemViewWith(mSizeType); updateColor(); updateStrokeWith(newConfig.uiMode, mAlignment); updateLocationWith(mAlignment, mPercentageY); + updateRadiusWith(mSizeType, mRadiusType, mTargets.size()); updateScrollModeWith(hasExceededMaxLayoutHeight()); + setSystemGestureExclusion(); mLastConfiguration.setTo(newConfig); } @@ -728,10 +767,30 @@ public class AccessibilityFloatingMenuView extends FrameLayout */ private void updateLocationWith(@Alignment int side, float percentageCurrentY) { mCurrentLayoutParams.x = (side == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX(); - mCurrentLayoutParams.y = (int) (percentageCurrentY * getMaxWindowY()); + final int currentLayoutY = (int) (percentageCurrentY * getMaxWindowY()); + mCurrentLayoutParams.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval()); mWindowManager.updateViewLayout(this, mCurrentLayoutParams); } + /** + * Gets the moving interval to not overlap between the keyboard and menu view. + * + * @return the moving interval if they overlap each other, otherwise 0. + */ + private int getInterval() { + if (!mImeVisibility) { + return 0; + } + + final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); + final Insets imeInsets = windowMetrics.getWindowInsets().getInsets( + ime() | navigationBars()); + final int imeY = mScreenHeight - imeInsets.bottom; + final int layoutBottomY = mCurrentLayoutParams.y + getWindowHeight(); + + return layoutBottomY > imeY ? (layoutBottomY - imeY) : 0; + } + private void updateOffsetWith(@ShapeType int shapeType, @Alignment int side) { final float halfWidth = getLayoutWidth() / 2.0f; final float offset = (shapeType == ShapeType.OVAL) ? 0 : halfWidth; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java index 76106e7c2cf1..63cfd5123c96 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java @@ -16,8 +16,6 @@ package com.android.systemui.accessibility.floatingmenu; -import static android.view.View.GONE; - import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -43,6 +41,7 @@ import java.util.List; */ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { private int mIconWidthHeight; + private int mItemPadding; private final List<AccessibilityTarget> mTargets; @IntDef({ @@ -84,6 +83,7 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { final AccessibilityTarget target = mTargets.get(position); holder.mIconView.setBackground(target.getIcon()); holder.updateIconWidthHeight(mIconWidthHeight); + holder.updateItemPadding(mItemPadding, getItemCount()); holder.itemView.setOnClickListener((v) -> target.onSelected()); holder.itemView.setStateDescription(target.getStateDescription()); holder.itemView.setContentDescription(target.getLabel()); @@ -120,14 +120,16 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { mIconWidthHeight = iconWidthHeight; } + public void setItemPadding(int itemPadding) { + mItemPadding = itemPadding; + } + static class ViewHolder extends RecyclerView.ViewHolder { final View mIconView; - final View mDivider; ViewHolder(View itemView) { super(itemView); mIconView = itemView.findViewById(R.id.icon_view); - mDivider = itemView.findViewById(R.id.transparent_divider); } void updateIconWidthHeight(int newValue) { @@ -139,21 +141,31 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { layoutParams.height = newValue; mIconView.setLayoutParams(layoutParams); } + + void updateItemPadding(int padding, int size) { + itemView.setPaddingRelative(padding, padding, padding, padding); + } } static class TopViewHolder extends ViewHolder { TopViewHolder(View itemView) { super(itemView); - final int padding = itemView.getPaddingStart(); - itemView.setPaddingRelative(padding, padding, padding, 0); + } + + @Override + void updateItemPadding(int padding, int size) { + final int paddingBottom = size <= 2 ? padding : 0; + itemView.setPaddingRelative(padding, padding, padding, paddingBottom); } } static class BottomViewHolder extends ViewHolder { BottomViewHolder(View itemView) { super(itemView); - mDivider.setVisibility(GONE); - final int padding = itemView.getPaddingStart(); + } + + @Override + void updateItemPadding(int padding, int size) { itemView.setPaddingRelative(padding, 0, padding, padding); } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java index 308585491aef..61fc7edcd9a0 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java @@ -214,6 +214,8 @@ class BaseTooltipView extends FrameLayout { final GradientDrawable gradientDrawable = (GradientDrawable) mTextView.getBackground(); gradientDrawable.setCornerRadius(mTextViewCornerRadius); + gradientDrawable.setColor(Utils.getColorAttrDefaultColor(getContext(), + com.android.internal.R.attr.colorAccentPrimary)); } private void updateArrowWith(Rect anchorViewLocation) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index 1ac1df148000..29cd76da23a3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -596,10 +596,6 @@ public abstract class AuthBiometricView extends LinearLayout { Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this); } - private void setText(TextView view, CharSequence charSequence) { - view.setText(charSequence); - } - // Remove all pending icon and text animations private void removePendingAnimations() { mHandler.removeCallbacks(mResetHelpRunnable); @@ -688,7 +684,7 @@ public abstract class AuthBiometricView extends LinearLayout { */ @VisibleForTesting void onAttachedToWindowInternal() { - setText(mTitleView, mPromptInfo.getTitle()); + mTitleView.setText(mPromptInfo.getTitle()); if (isDeviceCredentialAllowed()) { final CharSequence credentialButtonText; @@ -718,7 +714,7 @@ public abstract class AuthBiometricView extends LinearLayout { mUseCredentialButton.setText(credentialButtonText); mUseCredentialButton.setVisibility(View.VISIBLE); } else { - setText(mNegativeButton, mPromptInfo.getNegativeButtonText()); + mNegativeButton.setText(mPromptInfo.getNegativeButtonText()); } setTextOrHide(mSubtitleView, mPromptInfo.getSubtitle()); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 6f1a387db496..9ec7bd0c1549 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -575,7 +575,7 @@ public class AuthContainerView extends LinearLayout if (mBiometricView != null) { mBiometricView.restoreState(savedState); } - wm.addView(this, getLayoutParams(mWindowToken)); + wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle())); } @Override @@ -728,11 +728,9 @@ public class AuthContainerView extends LinearLayout } } - /** - * @param windowToken token for the window - * @return - */ - public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) { + @VisibleForTesting + static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, + CharSequence title) { final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_SECURE; final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( @@ -744,15 +742,11 @@ public class AuthContainerView extends LinearLayout lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~WindowInsets.Type.ime()); lp.setTitle("BiometricPrompt"); + lp.accessibilityTitle = title; lp.token = windowToken; return lp; } - private boolean hasFaceAndFingerprintSensors() { - final int[] ids = findFaceAndFingerprintSensors(); - return ids[0] >= 0 && ids[1] >= 0; - } - // returns [face, fingerprint] sensor ids (id is -1 if not present) private int[] findFaceAndFingerprintSensors() { int faceSensorId = -1; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 5c360a649af0..3726ae132b64 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -123,6 +123,11 @@ public class UdfpsController implements DozeReceiver { private int mActivePointerId = -1; // The timestamp of the most recent touch log. private long mTouchLogTime; + // Sensor has a good capture for this touch. Do not need to illuminate for this particular + // touch event anymore. In other words, do not illuminate until user lifts and touches the + // sensor area again. + // TODO: We should probably try to make touch/illumination things more of a FSM + private boolean mGoodCaptureReceived; @Nullable private UdfpsView mView; // The current request from FingerprintService. Null if no current request. @@ -225,48 +230,69 @@ public class UdfpsController implements DozeReceiver { @Override public void showUdfpsOverlay(int sensorId, int reason, @NonNull IUdfpsOverlayControllerCallback callback) { - final UdfpsEnrollHelper enrollHelper; - if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR - || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) { - enrollHelper = new UdfpsEnrollHelper(mContext, reason); - } else { - enrollHelper = null; - } - - mServerRequest = new ServerRequest(reason, callback, enrollHelper); - updateOverlay(); + mFgExecutor.execute(() -> { + final UdfpsEnrollHelper enrollHelper; + if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR + || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) { + enrollHelper = new UdfpsEnrollHelper(mContext, reason); + } else { + enrollHelper = null; + } + mServerRequest = new ServerRequest(reason, callback, enrollHelper); + updateOverlay(); + }); } @Override public void hideUdfpsOverlay(int sensorId) { - mServerRequest = null; - updateOverlay(); + mFgExecutor.execute(() -> { + mServerRequest = null; + updateOverlay(); + }); + } + + @Override + public void onAcquiredGood(int sensorId) { + mFgExecutor.execute(() -> { + if (mView == null) { + Log.e(TAG, "Null view when onAcquiredGood for sensorId: " + sensorId); + return; + } + mGoodCaptureReceived = true; + mView.stopIllumination(); + }); } @Override public void onEnrollmentProgress(int sensorId, int remaining) { - if (mServerRequest == null) { - Log.e(TAG, "onEnrollProgress received but serverRequest is null"); - return; - } - mServerRequest.onEnrollmentProgress(remaining); + mFgExecutor.execute(() -> { + if (mServerRequest == null) { + Log.e(TAG, "onEnrollProgress received but serverRequest is null"); + return; + } + mServerRequest.onEnrollmentProgress(remaining); + }); } @Override public void onEnrollmentHelp(int sensorId) { - if (mServerRequest == null) { - Log.e(TAG, "onEnrollmentHelp received but serverRequest is null"); - return; - } - mServerRequest.onEnrollmentHelp(); + mFgExecutor.execute(() -> { + if (mServerRequest == null) { + Log.e(TAG, "onEnrollmentHelp received but serverRequest is null"); + return; + } + mServerRequest.onEnrollmentHelp(); + }); } @Override public void setDebugMessage(int sensorId, String message) { - if (mView == null) { - return; - } - mView.setDebugMessage(message); + mFgExecutor.execute(() -> { + if (mView == null) { + return; + } + mView.setDebugMessage(message); + }); } } @@ -333,7 +359,7 @@ public class UdfpsController implements DozeReceiver { private boolean onTouch(View view, MotionEvent event, boolean fromUdfpsView) { UdfpsView udfpsView = (UdfpsView) view; - final boolean isFingerDown = udfpsView.isIlluminationRequested(); + final boolean isIlluminationRequested = udfpsView.isIlluminationRequested(); boolean handled = false; switch (event.getActionMasked()) { case MotionEvent.ACTION_OUTSIDE: @@ -388,7 +414,8 @@ public class UdfpsController implements DozeReceiver { "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b", minor, major, v, exceedsVelocityThreshold); final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime; - if (!isFingerDown && !exceedsVelocityThreshold) { + if (!isIlluminationRequested && !mGoodCaptureReceived && + !exceedsVelocityThreshold) { onFingerDown((int) x, (int) y, minor, major); Log.v(TAG, "onTouch | finger down: " + touchInfo); mTouchLogTime = SystemClock.elapsedRealtime(); @@ -423,7 +450,7 @@ public class UdfpsController implements DozeReceiver { Log.v(TAG, "onTouch | finger move: " + touchInfo); mTouchLogTime = SystemClock.elapsedRealtime(); } - } else if (isFingerDown) { + } else { Log.v(TAG, "onTouch | finger outside"); onFingerUp(); } @@ -438,10 +465,8 @@ public class UdfpsController implements DozeReceiver { mVelocityTracker.recycle(); mVelocityTracker = null; } - if (isFingerDown) { - Log.v(TAG, "onTouch | finger up"); - onFingerUp(); - } + Log.v(TAG, "onTouch | finger up"); + onFingerUp(); mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); break; @@ -795,13 +820,16 @@ public class UdfpsController implements DozeReceiver { // This method can be called from the UI thread. private void onFingerUp() { mActivePointerId = -1; + mGoodCaptureReceived = false; mMainHandler.removeCallbacks(mAcquiredVibration); if (mView == null) { Log.w(TAG, "Null view in onFingerUp"); return; } mFingerprintManager.onPointerUp(mSensorProps.sensorId); - mView.stopIllumination(); + if (mView.isIlluminationRequested()) { + mView.stopIllumination(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 2b4c1aba59b6..9a32412ba241 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -108,6 +108,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mStatusBarStateController.addCallback(mStateListener); mUdfpsRequested = false; + mStatusBarState = mStatusBarStateController.getState(); mQsExpanded = mKeyguardViewManager.isQsExpanded(); mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN; @@ -125,7 +126,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mFaceDetectRunning = false; mStatusBarStateController.removeCallback(mStateListener); - mKeyguardViewManager.setAlternateAuthInterceptor(null); + mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor); mTransitioningFromHome = false; mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java index 076c7cbe3937..4d4e4dd0ad06 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java @@ -29,7 +29,6 @@ import android.content.pm.PackageManager; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorPropertiesInternal; import android.os.UserManager; -import android.util.DisplayMetrics; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -50,17 +49,6 @@ public class Utils { @IntDef({CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD}) @interface CredentialType {} - - static float dpToPixels(Context context, float dp) { - return dp * ((float) context.getResources().getDisplayMetrics().densityDpi - / DisplayMetrics.DENSITY_DEFAULT); - } - - static float pixelsToDp(Context context, float pixels) { - return pixels / ((float) context.getResources().getDisplayMetrics().densityDpi - / DisplayMetrics.DENSITY_DEFAULT); - } - static void notifyAccessibilityContentChanged(AccessibilityManager am, ViewGroup view) { if (!am.isEnabled()) { return; diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java index f01ac68ed5a2..508262d4ddec 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java @@ -28,6 +28,9 @@ import android.util.Slog; import android.view.Gravity; import android.view.WindowManager; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; + /** * A WirelessChargingAnimation is a view containing view + animation for wireless charging. * @hide @@ -54,9 +57,10 @@ public class WirelessChargingAnimation { * @hide */ public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, - int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing) { + int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing, + UiEventLogger uiEventLogger) { mCurrentWirelessChargingView = new WirelessChargingView(context, looper, - transmittingBatteryLevel, batteryLevel, callback, isDozing); + transmittingBatteryLevel, batteryLevel, callback, isDozing, uiEventLogger); } /** @@ -66,9 +70,9 @@ public class WirelessChargingAnimation { */ public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, - Callback callback, boolean isDozing) { + Callback callback, boolean isDozing, UiEventLogger uiEventLogger) { return new WirelessChargingAnimation(context, looper, transmittingBatteryLevel, - batteryLevel, callback, isDozing); + batteryLevel, callback, isDozing, uiEventLogger); } /** @@ -95,6 +99,7 @@ public class WirelessChargingAnimation { private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); private final Handler mHandler; + private final UiEventLogger mUiEventLogger; private int mGravity; private WirelessChargingLayout mView; @@ -104,11 +109,12 @@ public class WirelessChargingAnimation { public WirelessChargingView(Context context, @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, Callback callback, - boolean isDozing) { + boolean isDozing, UiEventLogger uiEventLogger) { mCallback = callback; mNextView = new WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel, isDozing); mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER; + mUiEventLogger = uiEventLogger; final WindowManager.LayoutParams params = mParams; params.height = WindowManager.LayoutParams.MATCH_PARENT; @@ -195,6 +201,7 @@ public class WirelessChargingAnimation { mCallback.onAnimationStarting(); } mWM.addView(mView, mParams); + mUiEventLogger.log(WirelessChargingRippleEvent.WIRELESS_RIPPLE_PLAYED); } catch (WindowManager.BadTokenException e) { Slog.d(TAG, "Unable to add wireless charging view. " + e); } @@ -215,5 +222,19 @@ public class WirelessChargingAnimation { mView = null; } } + + enum WirelessChargingRippleEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "Wireless charging ripple effect played") + WIRELESS_RIPPLE_PLAYED(830); + + private final int mInt; + WirelessChargingRippleEvent(int id) { + mInt = id; + } + + @Override public int getId() { + return mInt; + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index 06fbf734d1c4..8a47a36de8c4 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -102,6 +102,10 @@ class DetailDialog( override fun onReleased() { removeDetailTask() } + + override fun onBackPressedOnTaskRoot(taskId: Int) { + dismiss() + } } init { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 9b372b8bdec6..5a5cce8f5093 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -232,10 +232,11 @@ public class KeyguardService extends Service { } @Override // Binder interface - public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) { + public void onStartedWakingUp( + @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) { Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp"); checkPermission(); - mKeyguardViewMediator.onStartedWakingUp(); + mKeyguardViewMediator.onStartedWakingUp(cameraGestureTriggered); mKeyguardLifecyclesDispatcher.dispatch( KeyguardLifecyclesDispatcher.STARTED_WAKING_UP, pmWakeReason); Trace.endSection(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 387f24f88b63..da8a3b9a7b6d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -106,12 +106,14 @@ import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.DeviceConfigProxy; @@ -234,6 +236,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private StatusBarManager mStatusBarManager; private final SysuiStatusBarStateController mStatusBarStateController; private final Executor mUiBgExecutor; + private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private boolean mSystemReady; private boolean mBootCompleted; @@ -387,6 +390,19 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private boolean mPendingLock; /** + * Whether a power button gesture (such as double tap for camera) has been detected. This is + * delivered directly from {@link KeyguardService}, immediately upon the gesture being detected. + * This is used in {@link #onStartedWakingUp} to decide whether to execute the pending lock, or + * ignore and reset it because we are actually launching an activity. + * + * This needs to be delivered directly to us, rather than waiting for + * {@link CommandQueue#onCameraLaunchGestureDetected}, because that call is asynchronous and is + * often delivered after the call to {@link #onStartedWakingUp}, which results in us locking the + * keyguard and then launching the activity behind it. + */ + private boolean mPowerGestureIntercepted = false; + + /** * Controller for showing individual "work challenge" lock screen windows inside managed profile * tasks when the current user has been unlocked but the profile is still locked. */ @@ -790,7 +806,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, DozeParameters dozeParameters, SysuiStatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, - Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy) { + Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { super(context); mFalsingCollector = falsingCollector; mLockPatternUtils = lockPatternUtils; @@ -822,6 +839,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mKeyguardStateController = keyguardStateController; mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; } public void userActivity() { @@ -941,6 +959,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + offReason + ")"); synchronized (this) { mDeviceInteractive = false; + mPowerGestureIntercepted = false; mGoingToSleep = true; // Reset keyguard going away state so we can start listening for fingerprint. We @@ -1010,7 +1029,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, notifyFinishedGoingToSleep(); if (cameraGestureTriggered) { - Log.i(TAG, "Camera gesture was triggered, preventing Keyguard locking."); // Just to make sure, make sure the device is awake. mContext.getSystemService(PowerManager.class).wakeUp(SystemClock.uptimeMillis(), @@ -1025,10 +1043,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mPendingReset = false; } - if (mPendingLock) { - doKeyguardLocked(null); - mPendingLock = false; - } + maybeHandlePendingLock(); // We do not have timeout and power button instant lock setting for profile lock. // So we use the personal setting if there is any. But if there is no device @@ -1041,6 +1056,20 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mUpdateMonitor.dispatchFinishedGoingToSleep(offReason); } + /** + * Locks the keyguard if {@link #mPendingLock} is true, unless we're playing the screen off + * animation. + * + * If we are, we will lock the keyguard either when the screen off animation ends, or in + * {@link #onStartedWakingUp} if the animation is cancelled. + */ + public void maybeHandlePendingLock() { + if (mPendingLock && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) { + doKeyguardLocked(null); + mPendingLock = false; + } + } + private boolean isKeyguardServiceEnabled() { try { return mContext.getPackageManager().getServiceInfo( @@ -1149,12 +1178,15 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, /** * Let's us know when the device is waking up. */ - public void onStartedWakingUp() { + public void onStartedWakingUp(boolean cameraGestureTriggered) { Trace.beginSection("KeyguardViewMediator#onStartedWakingUp"); // TODO: Rename all screen off/on references to interactive/sleeping synchronized (this) { mDeviceInteractive = true; + if (mPendingLock && !cameraGestureTriggered) { + doKeyguardLocked(null); + } mAnimatingScreenOff = false; cancelDoKeyguardLaterLocked(); cancelDoKeyguardForChildProfilesLocked(); @@ -1971,6 +2003,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mHiding = false; mWakeAndUnlocking = false; + mPendingLock = false; setShowingLocked(true); mKeyguardViewControllerLazy.get().show(options); resetKeyguardDonePendingLocked(); @@ -2620,7 +2653,12 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, if (!dozing) { mAnimatingScreenOff = false; } - setShowingLocked(mShowing); + + // Don't hide the keyguard due to a doze change if there's a lock pending, because we're + // just going to show it again. + if (mShowing || !mPendingLock) { + setShowingLocked(mShowing); + } } @Override @@ -2677,14 +2715,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mAodShowing = aodShowing; if (notifyDefaultDisplayCallbacks) { notifyDefaultDisplayCallbacks(showing); - - if (!showing || !mAnimatingScreenOff) { - // Update the activity lock screen state unless we're animating in the keyguard - // for a screen off animation. In that case, we want the activity to remain visible - // until the animation completes. setShowingLocked is called again when the - // animation ends, so the activity lock screen will be shown at that time. - updateActivityLockScreenState(showing, aodShowing); - } + updateActivityLockScreenState(showing, aodShowing); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 119e9c433f67..b071b943d8f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardLiftController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.sensors.AsyncSensorManager; @@ -97,7 +98,8 @@ public class KeyguardModule { DozeParameters dozeParameters, SysuiStatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, - Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController) { + Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { return new KeyguardViewMediator( context, falsingCollector, @@ -116,7 +118,8 @@ public class KeyguardModule { dozeParameters, statusBarStateController, keyguardStateController, - keyguardUnlockAnimationController + keyguardUnlockAnimationController, + unlockedScreenOffAnimationController ); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 55feea970e84..d698142674ce 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -315,8 +315,7 @@ public class MediaControlPanel { appIconView.clearColorFilter(); if (data.getAppIcon() != null && !data.getResumption()) { appIconView.setImageIcon(data.getAppIcon()); - int color = Utils.getColorAttrDefaultColor(mContext, - com.android.internal.R.attr.colorAccentTertiary); + int color = mContext.getColor(android.R.color.system_accent2_900); appIconView.setColorFilter(color); } else { appIconView.setColorFilter(getGrayscaleFilter()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index e0c8af6b8977..ce1066ee41c2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -80,8 +80,22 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @Override public void setPageMargin(int marginPixels) { - if (marginPixels != getPageMargin()) { - super.setPageMargin(marginPixels); + // Using page margins creates some rounding issues that interfere with the correct position + // in the onPageChangedListener and therefore present bad positions to the PageIndicator. + // Instead, we use negative margins in the container and positive padding in the pages, + // matching the margin set from QSContainerImpl (note that new pages will always be inflated + // with the correct value. + // QSContainerImpl resources are set onAttachedView, so this view will always have the right + // values when attached. + MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); + lp.setMarginStart(-marginPixels); + lp.setMarginEnd(-marginPixels); + setLayoutParams(lp); + + int nPages = mPages.size(); + for (int i = 0; i < nPages; i++) { + View v = mPages.get(i); + v.setPadding(marginPixels, v.getPaddingTop(), marginPixels, v.getPaddingBottom()); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index cd97f976540d..edfbed04f70d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -295,7 +295,7 @@ public class QSContainerImpl extends FrameLayout { qsPanelController.setContentMargins(mContentPadding, mContentPadding); // Set it as double the side margin (to simulate end margin of current page + // start margin of next page). - qsPanelController.setPageMargin(2 * mSideMargins); + qsPanelController.setPageMargin(mSideMargins); } else if (view == mHeader) { // No content padding for the header. } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 12c6906d87f7..ba65d5163881 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -291,6 +291,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta @Override public boolean onFailedToRecycleView(Holder holder) { + holder.stopDrag(); holder.clearDrag(); return true; } @@ -565,12 +566,6 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta public void clearDrag() { itemView.clearAnimation(); - if (mTileView instanceof CustomizeTileView) { - mTileView.findViewById(R.id.tile_label).clearAnimation(); - mTileView.findViewById(R.id.tile_label).setAlpha(1); - mTileView.getSecondaryLabel().clearAnimation(); - mTileView.getSecondaryLabel().setAlpha(.6f); - } } public void startDrag() { @@ -578,14 +573,6 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .scaleX(DRAG_SCALE) .scaleY(DRAG_SCALE); - if (mTileView instanceof CustomizeTileView) { - mTileView.findViewById(R.id.tile_label).animate() - .setDuration(DRAG_LENGTH) - .alpha(0); - mTileView.getSecondaryLabel().animate() - .setDuration(DRAG_LENGTH) - .alpha(0); - } } public void stopDrag() { @@ -593,14 +580,6 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .scaleX(1) .scaleY(1); - if (mTileView instanceof CustomizeTileView) { - mTileView.findViewById(R.id.tile_label).animate() - .setDuration(DRAG_LENGTH) - .alpha(1); - mTileView.getSecondaryLabel().animate() - .setDuration(DRAG_LENGTH) - .alpha(.6f); - } } boolean canRemove() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 47d80bb0d16f..a938821a343f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -181,14 +181,15 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mStatusBarStateController = statusBarStateController; mActivityStarter = activityStarter; - mState = newTileState(); - mTmpState = newTileState(); + resetStates(); mUiHandler.post(() -> mLifecycle.setCurrentState(CREATED)); } protected final void resetStates() { mState = newTileState(); mTmpState = newTileState(); + mState.spec = mTileSpec; + mTmpState.spec = mTileSpec; } @NonNull @@ -225,6 +226,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy public void setTileSpec(String tileSpec) { mTileSpec = tileSpec; + mState.spec = tileSpec; + mTmpState.spec = tileSpec; } public QSHost getHost() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index cd76b4df3c92..8aed0a88fa35 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -38,6 +38,7 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.Switch import android.widget.TextView +import androidx.annotation.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.FontSizeUtils import com.android.systemui.R @@ -62,6 +63,8 @@ open class QSTileViewImpl @JvmOverloads constructor( private const val SECONDARY_LABEL_NAME = "secondaryLabel" private const val CHEVRON_NAME = "chevron" const val UNAVAILABLE_ALPHA = 0.3f + @VisibleForTesting + internal const val TILE_STATE_RES_PREFIX = "tile_states_" } override var heightOverride: Int = HeightOverrideable.NO_OVERRIDE @@ -484,16 +487,18 @@ open class QSTileViewImpl @JvmOverloads constructor( } private fun getStateText(state: QSTile.State): String { - return if (state.disabledByPolicy) { - context.getString(R.string.tile_disabled) - } else if (state.state == Tile.STATE_UNAVAILABLE) { - context.getString(R.string.tile_unavailable) - } else if (state is BooleanState) { - if (state.state == Tile.STATE_INACTIVE) { - context.getString(R.string.switch_bar_off) - } else { - context.getString(R.string.switch_bar_on) + if (state.disabledByPolicy) { + return context.getString(R.string.tile_disabled) + } + + return if (state.state == Tile.STATE_UNAVAILABLE || state is BooleanState) { + val resName = "$TILE_STATE_RES_PREFIX${state.spec}" + var arrayResId = resources.getIdentifier(resName, "array", context.packageName) + if (arrayResId == 0) { + arrayResId = R.array.tile_states_default } + val array = resources.getStringArray(arrayResId) + array[state.state] } else { "" } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 74d3425edd22..7cb1421e3f0f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -45,7 +45,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSIconView; -import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.SignalState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.AlphaControlledSignalTileView; @@ -72,7 +71,6 @@ public class InternetTile extends QSTileImpl<SignalState> { protected final NetworkController mController; private final DataUsageController mDataController; - private final QSTile.SignalState mStateBeforeClick = newTileState(); // The last updated tile state, 0: mobile, 1: wifi, 2: ethernet. private int mLastTileState = -1; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index efac1414aeb6..41a3020191a6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -94,6 +94,7 @@ public class WifiTile extends QSTileImpl<SignalState> { mWifiController = accessPointController; mDetailAdapter = (WifiDetailAdapter) createDetailAdapter(); mController.observe(getLifecycle(), mSignalCallback); + mStateBeforeClick.spec = "wifi"; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index 7ca82774f737..060d7b1a8ab8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -33,7 +33,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.policy.CallbackController; -import java.util.ArrayList; +import java.util.concurrent.CopyOnWriteArrayList; import javax.inject.Inject; @@ -58,7 +58,8 @@ public class RecordingController "com.android.systemui.screenrecord.UPDATE_STATE"; protected static final String EXTRA_STATE = "extra_state"; - private ArrayList<RecordingStateChangeCallback> mListeners = new ArrayList<>(); + private CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners = + new CopyOnWriteArrayList<>(); @VisibleForTesting protected final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index af8d80887474..4ed376ace8c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -287,6 +287,8 @@ class LockscreenShadeTransitionController @Inject constructor( // TODO: appear media also in split shade val mediaAmount = if (useSplitShade) 0f else field mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount) + // Fade out all content only visible on the lockscreen + notificationPanelController.setKeyguardOnlyContentAlpha(1.0f - scrimProgress) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt index 05afc57fbb59..22bbb81b44e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt @@ -25,6 +25,8 @@ import android.util.DisplayMetrics import android.view.View import android.view.WindowManager import com.android.internal.annotations.VisibleForTesting +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger import com.android.settingslib.Utils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.FeatureFlags @@ -55,7 +57,8 @@ class WiredChargingRippleController @Inject constructor( featureFlags: FeatureFlags, private val context: Context, private val windowManager: WindowManager, - private val systemClock: SystemClock + private val systemClock: SystemClock, + private val uiEventLogger: UiEventLogger ) { private var pluggedIn: Boolean? = null private val rippleEnabled: Boolean = featureFlags.isChargingRippleEnabled && @@ -164,6 +167,7 @@ class WiredChargingRippleController @Inject constructor( } }) windowManager.addView(rippleView, windowLayoutParams) + uiEventLogger.log(WiredChargingRippleEvent.CHARGING_RIPPLE_PLAYED) } private fun layoutRipple() { @@ -203,4 +207,11 @@ class WiredChargingRippleController @Inject constructor( pw.println("Usage: adb shell cmd statusbar charging-ripple") } } + + enum class WiredChargingRippleEvent(private val _id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "Wired charging ripple effect played") + CHARGING_RIPPLE_PLAYED(829); + + override fun getId() = _id + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt index 33aa7c720b22..8479b30c3a75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt @@ -315,7 +315,11 @@ class SystemStatusAnimationScheduler @Inject constructor( ) : AnimatorListenerAdapter() { override fun onAnimationEnd(p0: Animator?) { chipAnimationController.onChipAnimationEnd(animationState) - animationState = endState + animationState = if (endState == SHOWING_PERSISTENT_DOT && !hasPersistentDot) { + IDLE + } else { + endState + } } override fun onAnimationStart(p0: Animator?) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 7afb0151aeda..84728f699e91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.PanelExpansionListener +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener import javax.inject.Inject @@ -38,7 +39,8 @@ class NotificationWakeUpCoordinator @Inject constructor( private val mHeadsUpManager: HeadsUpManager, private val statusBarStateController: StatusBarStateController, private val bypassController: KeyguardBypassController, - private val dozeParameters: DozeParameters + private val dozeParameters: DozeParameters, + private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController ) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener { private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>( @@ -264,7 +266,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } override fun onStateChanged(newState: Int) { - if (dozeParameters.shouldControlUnlockedScreenOff()) { + if (unlockedScreenOffAnimationController.shouldPlayScreenOffAnimation()) { if (animatingScreenOff && state == StatusBarState.KEYGUARD && newState == StatusBarState.SHADE) { 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 c6753b3b42f9..d68271a19d04 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 @@ -73,7 +73,6 @@ import com.android.internal.widget.CachingIconView; import com.android.internal.widget.CallLayout; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.Interpolators; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.plugins.FalsingManager; @@ -2020,6 +2019,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (params == null) { return; } + + if (!params.getVisible()) { + if (getVisibility() == View.VISIBLE) { + setVisibility(View.INVISIBLE); + } + return; + } + float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( params.getProgress(0, 50)); float translationZ = MathUtils.lerp(params.getStartTranslationZ(), @@ -2077,10 +2084,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView contentView = mGuts; } if (expandAnimationRunning) { - contentView.animate() - .alpha(0f) - .setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_OUT_CONTENT) - .setInterpolator(ActivityLaunchAnimator.CONTENT_FADE_OUT_INTERPOLATOR); setAboveShelf(true); mExpandAnimationRunning = true; getViewState().cancelAnimations(this); @@ -2088,6 +2091,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } else { mExpandAnimationRunning = false; setAboveShelf(isAboveShelf()); + setVisibility(View.VISIBLE); if (mGuts != null) { mGuts.setAlpha(1.0f); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index f6ab409998bf..4b1f679b8851 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Canvas; import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.LayerDrawable; @@ -30,8 +29,6 @@ import android.view.View; import com.android.internal.util.ArrayUtils; import com.android.systemui.R; -import com.android.systemui.animation.ActivityLaunchAnimator; -import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.notification.ExpandAnimationParameters; /** @@ -281,11 +278,6 @@ public class NotificationBackgroundView extends View { public void setExpandAnimationParams(ExpandAnimationParameters params) { mActualHeight = params.getHeight(); mActualWidth = params.getWidth(); - float alphaProgress = Interpolators.ALPHA_IN.getInterpolation( - params.getProgress( - ActivityLaunchAnimator.ANIMATION_DELAY_FADE_IN_WINDOW /* delay */, - ActivityLaunchAnimator.ANIMATION_DURATION_FADE_IN_WINDOW /* duration */)); - mBackground.setAlpha((int) (mDrawableAlpha * (1.0f - alphaProgress))); invalidate(); } @@ -294,8 +286,6 @@ public class NotificationBackgroundView extends View { if (mBackground instanceof LayerDrawable) { GradientDrawable gradientDrawable = (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0); - gradientDrawable.setXfermode( - running ? new PorterDuffXfermode(PorterDuff.Mode.SRC) : null); // Speed optimization: disable AA if transfer mode is not SRC_OVER. AA is not easy to // spot during animation anyways. gradientDrawable.setAntiAlias(!running); 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 94edbd092a4d..64f228f41df9 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 @@ -43,6 +43,7 @@ import android.graphics.Paint; import android.graphics.PointF; import android.graphics.Rect; import android.os.Bundle; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.util.AttributeSet; @@ -129,7 +130,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; - private static final boolean DEBUG = false; + + // Usage: + // adb shell setprop persist.debug.nssl true && adb reboot + private static final boolean DEBUG = SystemProperties.getBoolean("persist.debug.nssl", + false /* default */); + private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f; private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f; private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f; @@ -4667,8 +4673,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s" - + " alpha:%f scrollY:%d maxTopPadding:%d showShelfOnly=%s" - + " qsExpandFraction=%f]", + + " alpha=%f scrollY:%d maxTopPadding=%d showShelfOnly=%s" + + " qsExpandFraction=%f" + + " hideAmount=%f]", this.getClass().getSimpleName(), mPulsing ? "T" : "f", mAmbientState.isQsCustomizerShowing() ? "T" : "f", @@ -4679,7 +4686,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAmbientState.getScrollY(), mMaxTopPadding, mShouldShowShelfOnly ? "T" : "f", - mQsExpansionFraction)); + mQsExpansionFraction, + mAmbientState.getHideAmount())); int childCount = getChildCount(); pw.println(" Number of children: " + childCount); pw.println(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 86465b6f6b1a..b60ef1d62ef5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -382,13 +382,15 @@ public class StackScrollAlgorithm { final boolean isHunGoingToShade = ambientState.isShadeExpanded() && view == ambientState.getTrackedHeadsUpRow(); - if (!isHunGoingToShade) { - if (ambientState.isExpansionChanging() && !ambientState.isOnKeyguard()) { - viewState.alpha = Interpolators.getNotificationScrimAlpha( - ambientState.getExpansionFraction(), true /* notification */); - } else { - viewState.alpha = 1f - ambientState.getHideAmount(); - } + if (isHunGoingToShade) { + // Keep 100% opacity for heads up notification going to shade. + } else if (ambientState.isOnKeyguard()) { + // Adjust alpha for wakeup to lockscreen. + viewState.alpha = 1f - ambientState.getHideAmount(); + } else if (ambientState.isExpansionChanging()) { + // Adjust alpha for shade open & close. + viewState.alpha = Interpolators.getNotificationScrimAlpha( + ambientState.getExpansionFraction(), true /* notification */); } if (view.mustStayOnScreen() && viewState.yTranslation >= 0) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 7737420cf026..a7b802ac4afc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -75,6 +75,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.util.LatencyTracker; import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; @@ -334,6 +335,8 @@ public class NotificationPanelViewController extends PanelViewController { private NotificationsQuickSettingsContainer mNotificationContainerParent; private boolean mAnimateNextPositionUpdate; private float mQuickQsOffsetHeight; + private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; + private int mTrackingPointer; private VelocityTracker mQsVelocityTracker; private boolean mQsTracking; @@ -577,6 +580,11 @@ public class NotificationPanelViewController extends PanelViewController { */ private ValueAnimator mQsClippingAnimation = null; private final Rect mKeyguardStatusAreaClipBounds = new Rect(); + + /** + * The alpha of the views which only show on the keyguard but not in shade / shade locked + */ + private float mKeyguardOnlyContentAlpha = 1.0f; private int mOldLayoutDirection; private NotificationShelfController mNotificationShelfController; private int mScrimCornerRadius; @@ -670,7 +678,8 @@ public class NotificationPanelViewController extends PanelViewController { FragmentService fragmentService, QuickAccessWalletController quickAccessWalletController, @Main Executor uiExecutor, - SecureSettings secureSettings) { + SecureSettings secureSettings, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { super(view, falsingManager, dozeLog, keyguardStateController, (SysuiStatusBarStateController) statusBarStateController, vibratorHelper, statusBarKeyguardViewManager, latencyTracker, flingAnimationUtilsBuilder.get(), @@ -765,6 +774,7 @@ public class NotificationPanelViewController extends PanelViewController { mConversationNotificationManager = conversationNotificationManager; mAuthController = authController; mLockIconViewController = lockIconViewController; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mView.setBackgroundColor(Color.TRANSPARENT); OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener(); @@ -897,8 +907,7 @@ public class NotificationPanelViewController extends PanelViewController { R.dimen.pulse_expansion_max_top_overshoot); mScrimCornerRadius = mResources.getDimensionPixelSize( R.dimen.notification_scrim_corner_radius); - mScreenCornerRadius = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.rounded_corner_radius); + mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(mResources); mNotificationScrimPadding = mResources.getDimensionPixelSize( R.dimen.notification_side_paddings); mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize( @@ -1240,10 +1249,11 @@ public class NotificationPanelViewController extends PanelViewController { int userIconHeight = mKeyguardQsUserSwitchController != null ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0; float expandedFraction = - mKeyguardStatusViewController.isAnimatingScreenOffFromUnlocked() ? 1.0f - : getExpandedFraction(); - float darkamount = mKeyguardStatusViewController.isAnimatingScreenOffFromUnlocked() ? 1.0f - : mInterpolatedDarkAmount; + mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() + ? 1.0f : getExpandedFraction(); + float darkamount = + mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() + ? 1.0f : mInterpolatedDarkAmount; mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard, totalHeight - bottomPadding, mNotificationStackScrollLayoutController.getIntrinsicContentHeight(), @@ -1412,12 +1422,13 @@ public class NotificationPanelViewController extends PanelViewController { } private void updateClock() { - mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha); + float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha; + mKeyguardStatusViewController.setAlpha(alpha); if (mKeyguardQsUserSwitchController != null) { - mKeyguardQsUserSwitchController.setAlpha(mClockPositionResult.clockAlpha); + mKeyguardQsUserSwitchController.setAlpha(alpha); } if (mKeyguardUserSwitcherController != null) { - mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha); + mKeyguardUserSwitcherController.setAlpha(alpha); } } @@ -2024,6 +2035,7 @@ public class NotificationPanelViewController extends PanelViewController { private void maybeAnimateBottomAreaAlpha() { mBottomAreaShadeAlphaAnimator.cancel(); if (mBarState == StatusBarState.SHADE_LOCKED) { + mBottomAreaShadeAlphaAnimator.setFloatValues(mBottomAreaShadeAlpha, 0.0f); mBottomAreaShadeAlphaAnimator.start(); } else { mBottomAreaShadeAlpha = 1f; @@ -2439,6 +2451,20 @@ public class NotificationPanelViewController extends PanelViewController { updateQsExpansion(); } + /** + * Set the alpha of the keyguard elements which only show on the lockscreen, but not in + * shade locked / shade. This is used when dragging down to the full shade. + */ + public void setKeyguardOnlyContentAlpha(float keyguardAlpha) { + mKeyguardOnlyContentAlpha = Interpolators.ALPHA_IN.getInterpolation(keyguardAlpha); + if (mBarState == KEYGUARD) { + // If the animator is running, it's already fading out the content and this is a reset + mBottomAreaShadeAlpha = mKeyguardOnlyContentAlpha; + updateKeyguardBottomAreaAlpha(); + } + updateClock(); + } + private void trackMovement(MotionEvent event) { if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event); } @@ -2897,6 +2923,7 @@ public class NotificationPanelViewController extends PanelViewController { if (ambientIndicationContainer != null) { ambientIndicationContainer.setAlpha(alpha); } + mLockIconViewController.setAlpha(alpha); } /** @@ -4209,7 +4236,9 @@ public class NotificationPanelViewController extends PanelViewController { int oldState = mBarState; boolean keyguardShowing = statusBarState == KEYGUARD; - if (mDozeParameters.shouldControlUnlockedScreenOff() && isDozing() && keyguardShowing) { + if (mUnlockedScreenOffAnimationController.shouldPlayScreenOffAnimation() + && oldState == StatusBarState.SHADE + && statusBarState == KEYGUARD) { // This means we're doing the screen off animation - position the keyguard status // view where it'll be on AOD, so we can animate it in. mKeyguardStatusViewController.updatePosition( @@ -4280,6 +4309,18 @@ public class NotificationPanelViewController extends PanelViewController { } /** + * Reconfigures the shade to show the AOD UI (clock, smartspace, etc). This is called by the + * screen off animation controller in order to animate in AOD without "actually" fully switching + * to the KEYGUARD state. + */ + public void showAodUi() { + setDozing(true /* dozing */, false /* animate */, null); + mStatusBarStateListener.onStateChanged(KEYGUARD); + mStatusBarStateListener.onDozeAmountChanged(1f, 1f); + setExpandedFraction(1f); + } + + /** * Sets the overstretch amount in raw pixels when dragging down. */ public void setOverStrechAmount(float amount) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index b92f7c034b57..1331829201b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -169,6 +169,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private final KeyguardVisibilityCallback mKeyguardVisibilityCallback; private final Handler mHandler; private final Executor mMainExecutor; + private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private GradientColors mColors; private boolean mNeedsDrawableColorUpdate; @@ -224,7 +225,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump AlarmManager alarmManager, KeyguardStateController keyguardStateController, DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, - ConfigurationController configurationController, @Main Executor mainExecutor) { + ConfigurationController configurationController, @Main Executor mainExecutor, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { mScrimStateListener = lightBarController::setScrimState; mDefaultScrimAlpha = BUSY_SCRIM_ALPHA; ScrimState.BUBBLE_EXPANDED.setBubbleAlpha(BUBBLE_SCRIM_ALPHA); @@ -235,6 +237,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mKeyguardVisibilityCallback = new KeyguardVisibilityCallback(); mHandler = handler; mMainExecutor = mainExecutor; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout, "hide_aod_wallpaper", mHandler); mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build(); @@ -640,17 +643,20 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } if (mState == ScrimState.UNLOCKED) { - // Darken scrim as you pull down the shade when unlocked - float behindFraction = getInterpolatedFraction(); - behindFraction = (float) Math.pow(behindFraction, 0.8f); - if (mClipsQsScrim) { - mBehindAlpha = 1; - mNotificationsAlpha = behindFraction * mDefaultScrimAlpha; - } else { - mBehindAlpha = behindFraction * mDefaultScrimAlpha; - mNotificationsAlpha = mBehindAlpha; + // Darken scrim as you pull down the shade when unlocked, unless the shade is expanding + // because we're doing the screen off animation. + if (!mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) { + float behindFraction = getInterpolatedFraction(); + behindFraction = (float) Math.pow(behindFraction, 0.8f); + if (mClipsQsScrim) { + mBehindAlpha = 1; + mNotificationsAlpha = behindFraction * mDefaultScrimAlpha; + } else { + mBehindAlpha = behindFraction * mDefaultScrimAlpha; + mNotificationsAlpha = mBehindAlpha; + } + mInFrontAlpha = 0; } - mInFrontAlpha = 0; } else if (mState == ScrimState.BUBBLE_EXPANDED) { // Darken scrim as you pull down the shade when unlocked float behindFraction = getInterpolatedFraction(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 35dda4426849..e52e1fa5f39f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -189,9 +189,11 @@ public enum ScrimState { mBubbleAlpha = ScrimController.TRANSPARENT; mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG; - // DisplayPowerManager may blank the screen for us, - // in this case we just need to set our state. - mAnimateChange = mDozeParameters.shouldControlScreenOff(); + // DisplayPowerManager may blank the screen for us, or we might blank it for ourselves + // by animating the screen off via the LightRevelScrim. In either case we just need to + // set our state. + mAnimateChange = mDozeParameters.shouldControlScreenOff() + && !mDozeParameters.shouldControlUnlockedScreenOff(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 2396272e30e4..8e8dcbda51e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -468,6 +468,7 @@ public class StatusBar extends SystemUI implements DemoMode, private final BrightnessSlider.Factory mBrightnessSliderFactory; private final FeatureFlags mFeatureFlags; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; + private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private final List<ExpansionChangedListener> mExpansionChangedListeners; @@ -806,7 +807,8 @@ public class StatusBar extends SystemUI implements DemoMode, StatusBarLocationPublisher locationPublisher, LockscreenShadeTransitionController lockscreenShadeTransitionController, FeatureFlags featureFlags, - KeyguardUnlockAnimationController keyguardUnlockAnimationController) { + KeyguardUnlockAnimationController keyguardUnlockAnimationController, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { super(context); mNotificationsController = notificationsController; mLightBarController = lightBarController; @@ -890,6 +892,7 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarLocationPublisher = locationPublisher; mFeatureFlags = featureFlags; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; lockscreenShadeTransitionController.setStatusbar(this); @@ -1244,6 +1247,7 @@ public class StatusBar extends SystemUI implements DemoMode, mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront, scrimForBubble); mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); + mUnlockedScreenOffAnimationController.initialize(this, mLightRevealScrim); updateLightRevealScrimVisibility(); mNotificationPanelViewController.initDependencies( @@ -1474,7 +1478,9 @@ public class StatusBar extends SystemUI implements DemoMode, * @param why the reason for the wake up */ public void wakeUpIfDozing(long time, View where, String why) { - if (mDozing && !mKeyguardViewMediator.isAnimatingScreenOff()) { + if (mDozing && !(mKeyguardViewMediator.isAnimatingScreenOff() + || mUnlockedScreenOffAnimationController + .isScreenOffLightRevealAnimationPlaying())) { mPowerManager.wakeUp( time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why); mWakeUpComingFromTouch = true; @@ -2581,7 +2587,7 @@ public class StatusBar extends SystemUI implements DemoMode, public void onAnimationEnded() { mNotificationShadeWindowController.setRequestTopUi(false, TAG); } - }, false).show(animationDelay); + }, false, sUiEventLogger).show(animationDelay); } @Override @@ -3416,8 +3422,9 @@ public class StatusBar extends SystemUI implements DemoMode, updatePanelExpansionForKeyguard(); } if (shouldBeKeyguard) { - if (isGoingToSleep() - && mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_TURNING_OFF) { + if (mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() + || (isGoingToSleep() + && mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_TURNING_OFF)) { // Delay showing the keyguard until screen turned off. } else { showKeyguardImpl(); @@ -3588,6 +3595,7 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationPanelViewController.cancelAnimation(); mNotificationPanelViewController.setAlpha(1f); mNotificationPanelViewController.resetViewGroupFade(); + updateDozingState(); updateScrimController(); Trace.endSection(); return staying; @@ -4050,6 +4058,13 @@ public class StatusBar extends SystemUI implements DemoMode, mWakeUpCoordinator.setFullyAwake(false); mBypassHeadsUpNotifier.setFullyAwake(false); mKeyguardBypassController.onStartedGoingToSleep(); + + // The screen off animation uses our LightRevealScrim - we need to be expanded for it to + // be visible. + if (mUnlockedScreenOffAnimationController.shouldPlayScreenOffAnimation()) { + makeExpandedVisible(true); + } + DejankUtils.stopDetectingBlockingIpcs(tag); } @@ -4070,6 +4085,13 @@ public class StatusBar extends SystemUI implements DemoMode, // once we fully woke up. updateNotificationPanelTouchState(); mPulseExpansionHandler.onStartedWakingUp(); + + // If we are waking up during the screen off animation, we should undo making the + // expanded visible (we did that so the LightRevealScrim would be visible). + if (mUnlockedScreenOffAnimationController.isScreenOffLightRevealAnimationPlaying()) { + makeExpandedInvisible(); + } + DejankUtils.stopDetectingBlockingIpcs(tag); } @@ -4442,8 +4464,9 @@ public class StatusBar extends SystemUI implements DemoMode, } public boolean shouldIgnoreTouch() { - return mStatusBarStateController.isDozing() - && mDozeServiceHost.getIgnoreTouchWhilePulsing(); + return (mStatusBarStateController.isDozing() + && mDozeServiceHost.getIgnoreTouchWhilePulsing()) + || mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying(); } // Begin Extra BaseStatusBar methods. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 91d1bd73a403..c0957c0aff81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -35,6 +35,7 @@ import android.view.ViewGroup; import android.view.ViewRootImpl; import android.view.WindowManagerGlobal; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -63,6 +64,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Objects; import java.util.Optional; import javax.inject.Inject; @@ -268,10 +270,23 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb registerListeners(); } - public void setAlternateAuthInterceptor(@Nullable AlternateAuthInterceptor authInterceptor) { - final boolean newlyNull = authInterceptor == null && mAlternateAuthInterceptor != null; + /** + * Sets the given alt auth interceptor to null if it's the current auth interceptor. Else, + * does nothing. + */ + public void removeAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) { + if (Objects.equals(mAlternateAuthInterceptor, authInterceptor)) { + mAlternateAuthInterceptor = null; + resetAlternateAuth(true); + } + } + + /** + * Sets a new alt auth interceptor. + */ + public void setAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) { mAlternateAuthInterceptor = authInterceptor; - resetAlternateAuth(newlyNull); + resetAlternateAuth(false); } private void registerListeners() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt new file mode 100644 index 000000000000..e135cc51a7bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -0,0 +1,178 @@ +package com.android.systemui.statusbar.phone + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.os.Handler +import android.view.View +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.KeyguardViewMediator +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.statusbar.LightRevealScrim +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.StatusBarStateControllerImpl +import com.android.systemui.statusbar.notification.AnimatableProperty +import com.android.systemui.statusbar.notification.PropertyAnimator +import com.android.systemui.statusbar.notification.stack.AnimationProperties +import com.android.systemui.statusbar.notification.stack.StackStateAnimator +import javax.inject.Inject + +/** + * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely + * visible, because the transition to KEYGUARD causes brief jank. + */ +private const val ANIMATE_IN_KEYGUARD_DELAY = 600L + +/** + * Duration for the light reveal portion of the animation. + */ +private const val LIGHT_REVEAL_ANIMATION_DURATION = 750L + +/** + * Controller for the unlocked screen off animation, which runs when the device is going to sleep + * and we're unlocked. + * + * This animation uses a [LightRevealScrim] that lives in the status bar to hide the screen contents + * and then animates in the AOD UI. + */ +@SysUISingleton +class UnlockedScreenOffAnimationController @Inject constructor( + private val wakefulnessLifecycle: WakefulnessLifecycle, + private val statusBarStateControllerImpl: StatusBarStateControllerImpl, + private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>, + private val dozeParameters: DozeParameters +) : WakefulnessLifecycle.Observer { + private val handler = Handler() + + private lateinit var statusBar: StatusBar + private lateinit var lightRevealScrim: LightRevealScrim + + private var lightRevealAnimationPlaying = false + private var aodUiAnimationPlaying = false + + private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply { + duration = LIGHT_REVEAL_ANIMATION_DURATION + interpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE + addUpdateListener { lightRevealScrim.revealAmount = it.animatedValue as Float } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationCancel(animation: Animator?) { + lightRevealScrim.revealAmount = 1f + lightRevealAnimationPlaying = false + } + + override fun onAnimationEnd(animation: Animator?) { + lightRevealAnimationPlaying = false + } + }) + } + + fun initialize( + statusBar: StatusBar, + lightRevealScrim: LightRevealScrim + ) { + this.lightRevealScrim = lightRevealScrim + this.statusBar = statusBar + + wakefulnessLifecycle.addObserver(this) + } + + /** + * Animates in the provided keyguard view, ending in the same position that it will be in on + * AOD. + */ + fun animateInKeyguard(keyguardView: View, after: Runnable) { + keyguardView.alpha = 0f + keyguardView.visibility = View.VISIBLE + + val currentY = keyguardView.y + + // Move the keyguard up by 10% so we can animate it back down. + keyguardView.y = currentY - keyguardView.height * 0.1f + + val duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP + + // We animate the Y properly separately using the PropertyAnimator, as the panel + // view also needs to update the end position. + PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y) + PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY, + AnimationProperties().setDuration(duration.toLong()), + true /* animate */) + + keyguardView.animate() + .setDuration(duration.toLong()) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .alpha(1f) + .withEndAction { + aodUiAnimationPlaying = false + + // Lock the keyguard if it was waiting for the screen off animation to end. + keyguardViewMediatorLazy.get().maybeHandlePendingLock() + + // Tell the StatusBar to become keyguard for real - we waited on that since it + // is slow and would have caused the animation to jank. + statusBar.updateIsKeyguard() + + // Run the callback given to us by the KeyguardVisibilityHelper. + after.run() + } + .start() + } + + override fun onStartedWakingUp() { + lightRevealAnimator.cancel() + handler.removeCallbacksAndMessages(null) + } + + override fun onFinishedWakingUp() { + // Set this to false in onFinishedWakingUp rather than onStartedWakingUp so that other + // observers (such as StatusBar) can ask us whether we were playing the screen off animation + // and reset accordingly. + lightRevealAnimationPlaying = false + aodUiAnimationPlaying = false + + // Make sure the status bar is in the correct keyguard state, since we might have left it in + // the KEYGUARD state if this wakeup cancelled the screen off animation. + statusBar.updateIsKeyguard() + } + + override fun onStartedGoingToSleep() { + if (shouldPlayScreenOffAnimation()) { + lightRevealAnimationPlaying = true + lightRevealAnimator.start() + + handler.postDelayed({ + aodUiAnimationPlaying = true + + // Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard. + statusBar.notificationPanelViewController.showAodUi() + }, ANIMATE_IN_KEYGUARD_DELAY) + } + } + + /** + * Whether we should play the screen off animation when the phone starts going to sleep. We can + * do that if dozeParameters says we can control the unlocked screen off animation and we are in + * the SHADE state. If we're in KEYGUARD or SHADE_LOCKED, the regular + */ + fun shouldPlayScreenOffAnimation(): Boolean { + return dozeParameters.shouldControlUnlockedScreenOff() && + statusBarStateControllerImpl.state == StatusBarState.SHADE + } + + /** + * Whether we're doing the light reveal animation or we're done with that and animating in the + * AOD UI. + */ + fun isScreenOffAnimationPlaying(): Boolean { + return lightRevealAnimationPlaying || aodUiAnimationPlaying + } + + /** + * Whether the light reveal animation is playing. The second part of the screen off animation, + * where AOD animates in, might still be playing if this returns false. + */ + fun isScreenOffLightRevealAnimationPlaying(): Boolean { + return lightRevealAnimationPlaying + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 9722d6841c62..2611ab5f7016 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -94,6 +94,7 @@ import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -216,7 +217,8 @@ public interface StatusBarPhoneModule { StatusBarLocationPublisher locationPublisher, LockscreenShadeTransitionController transitionController, FeatureFlags featureFlags, - KeyguardUnlockAnimationController keyguardUnlockAnimationController) { + KeyguardUnlockAnimationController keyguardUnlockAnimationController, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { return new StatusBar( context, notificationsController, @@ -303,6 +305,7 @@ public interface StatusBarPhoneModule { locationPublisher, transitionController, featureFlags, - keyguardUnlockAnimationController); + keyguardUnlockAnimationController, + unlockedScreenOffAnimationController); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index f39acf9a40ed..2b79733f886b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -31,6 +31,7 @@ import android.util.Log; import androidx.annotation.NonNull; +import com.android.internal.annotations.GuardedBy; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -66,6 +67,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private final WeakHashMap<CachedBluetoothDevice, ActuallyCachedState> mCachedState = new WeakHashMap<>(); private final Handler mBgHandler; + @GuardedBy("mConnectedDevices") private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>(); private boolean mEnabled; @@ -118,7 +120,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa pw.print(" mConnectionState="); pw.println(stateToString(mConnectionState)); pw.print(" mAudioProfileOnly="); pw.println(mAudioProfileOnly); pw.print(" mIsActive="); pw.println(mIsActive); - pw.print(" mConnectedDevices="); pw.println(mConnectedDevices); + pw.print(" mConnectedDevices="); pw.println(getConnectedDevices()); pw.print(" mCallbacks.size="); pw.println(mHandler.mCallbacks.size()); pw.println(" Bluetooth Devices:"); for (CachedBluetoothDevice device : getDevices()) { @@ -151,7 +153,11 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @Override public List<CachedBluetoothDevice> getConnectedDevices() { - return mConnectedDevices; + List<CachedBluetoothDevice> out; + synchronized (mConnectedDevices) { + out = new ArrayList<>(mConnectedDevices); + } + return out; } @Override @@ -226,8 +232,10 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @Override public String getConnectedDeviceName() { - if (mConnectedDevices.size() == 1) { - return mConnectedDevices.get(0).getName(); + synchronized (mConnectedDevices) { + if (mConnectedDevices.size() == 1) { + return mConnectedDevices.get(0).getName(); + } } return null; } @@ -242,7 +250,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private void updateConnected() { // Make sure our connection state is up to date. int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState(); - mConnectedDevices.clear(); + List<CachedBluetoothDevice> newList = new ArrayList<>(); // If any of the devices are in a higher state than the adapter, move the adapter into // that state. for (CachedBluetoothDevice device : getDevices()) { @@ -251,15 +259,19 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa state = maxDeviceState; } if (device.isConnected()) { - mConnectedDevices.add(device); + newList.add(device); } } - if (mConnectedDevices.isEmpty() && state == BluetoothAdapter.STATE_CONNECTED) { + if (newList.isEmpty() && state == BluetoothAdapter.STATE_CONNECTED) { // If somehow we think we are connected, but have no connected devices, we aren't // connected. state = BluetoothAdapter.STATE_DISCONNECTED; } + synchronized (mConnectedDevices) { + mConnectedDevices.clear(); + mConnectedDevices.addAll(newList); + } if (state != mConnectionState) { mConnectionState = state; mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 9d667805fade..2ecd4b3db5a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationPanelViewController; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.phone.UserAvatarView; import com.android.systemui.util.ViewController; @@ -122,7 +123,8 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie ConfigurationController configurationController, SysuiStatusBarStateController statusBarStateController, DozeParameters dozeParameters, - Provider<UserDetailView.Adapter> userDetailViewAdapterProvider) { + Provider<UserDetailView.Adapter> userDetailViewAdapterProvider, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { super(view); if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController"); mContext = context; @@ -135,7 +137,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie mConfigurationController = configurationController; mStatusBarStateController = statusBarStateController; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, - keyguardStateController, dozeParameters); + keyguardStateController, dozeParameters, unlockedScreenOffAnimationController); mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index e2c52f9a3961..68f2a62a4fec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.util.ViewController; import java.util.ArrayList; @@ -159,7 +160,8 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - DozeParameters dozeParameters) { + DozeParameters dozeParameters, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { super(keyguardUserSwitcherView); if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController"); mContext = context; @@ -171,7 +173,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater, mUserSwitcherController, this); mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, - keyguardStateController, dozeParameters); + keyguardStateController, dozeParameters, unlockedScreenOffAnimationController); mBackground = new KeyguardUserSwitcherScrim(context); } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java index b1241b160d70..941cd7783ed0 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java @@ -68,7 +68,9 @@ public class UsbDebuggingActivity extends AlertActivity super.onCreate(icicle); - if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) { + // Emulator does not support reseating the usb cable to reshow the dialog. + boolean isEmulator = SystemProperties.get("ro.boot.qemu").equals("1"); + if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0 && !isEmulator) { mDisconnectedReceiver = new UsbDisconnectedReceiver(this); IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE); mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter); diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java index 6a648bdf8cd4..19ed2848ca6f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java @@ -21,8 +21,8 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.util.Assert; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.concurrency.Execution; import java.util.ArrayList; import java.util.List; @@ -62,6 +62,7 @@ public class ProximitySensor implements ThresholdSensor { private final ThresholdSensor mPrimaryThresholdSensor; private final ThresholdSensor mSecondaryThresholdSensor; private final DelayableExecutor mDelayableExecutor; + private final Execution mExecution; private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>(); private String mTag = null; @VisibleForTesting protected boolean mPaused; @@ -74,14 +75,10 @@ public class ProximitySensor implements ThresholdSensor { private boolean mInitializedListeners = false; private boolean mSecondarySafe = false; - private ThresholdSensor.Listener mPrimaryEventListener = new ThresholdSensor.Listener() { - @Override - public void onThresholdCrossed(ThresholdSensorEvent event) { - onPrimarySensorEvent(event); - } - }; + private final ThresholdSensor.Listener mPrimaryEventListener = this::onPrimarySensorEvent; - private ThresholdSensor.Listener mSecondaryEventListener = new ThresholdSensor.Listener() { + private final ThresholdSensor.Listener mSecondaryEventListener = + new ThresholdSensor.Listener() { @Override public void onThresholdCrossed(ThresholdSensorEvent event) { // If we no longer have a "below" signal and the secondary sensor is not @@ -110,12 +107,15 @@ public class ProximitySensor implements ThresholdSensor { }; @Inject - public ProximitySensor(@PrimaryProxSensor ThresholdSensor primary, + public ProximitySensor( + @PrimaryProxSensor ThresholdSensor primary, @SecondaryProxSensor ThresholdSensor secondary, - @Main DelayableExecutor delayableExecutor) { + @Main DelayableExecutor delayableExecutor, + Execution execution) { mPrimaryThresholdSensor = primary; mSecondaryThresholdSensor = secondary; mDelayableExecutor = delayableExecutor; + mExecution = execution; } @Override @@ -127,7 +127,7 @@ public class ProximitySensor implements ThresholdSensor { @Override public void setDelay(int delay) { - Assert.isMainThread(); + mExecution.assertIsMainThread(); mPrimaryThresholdSensor.setDelay(delay); mSecondaryThresholdSensor.setDelay(delay); } @@ -137,7 +137,7 @@ public class ProximitySensor implements ThresholdSensor { */ @Override public void pause() { - Assert.isMainThread(); + mExecution.assertIsMainThread(); mPaused = true; unregisterInternal(); } @@ -147,18 +147,23 @@ public class ProximitySensor implements ThresholdSensor { */ @Override public void resume() { - Assert.isMainThread(); + mExecution.assertIsMainThread(); mPaused = false; registerInternal(); } /** * Sets that it is safe to leave the secondary sensor on indefinitely. + * + * The secondary sensor will be turned on if there are any registered listeners, regardless + * of what is reported by the primary sensor. */ public void setSecondarySafe(boolean safe) { mSecondarySafe = safe; if (!mSecondarySafe) { mSecondaryThresholdSensor.pause(); + } else { + mSecondaryThresholdSensor.resume(); } } @@ -185,7 +190,7 @@ public class ProximitySensor implements ThresholdSensor { */ @Override public void register(ThresholdSensor.Listener listener) { - Assert.isMainThread(); + mExecution.assertIsMainThread(); if (!isLoaded()) { return; } @@ -199,13 +204,15 @@ public class ProximitySensor implements ThresholdSensor { } protected void registerInternal() { - Assert.isMainThread(); + mExecution.assertIsMainThread(); if (mRegistered || mPaused || mListeners.isEmpty()) { return; } if (!mInitializedListeners) { mPrimaryThresholdSensor.register(mPrimaryEventListener); - mSecondaryThresholdSensor.pause(); + if (!mSecondarySafe) { + mSecondaryThresholdSensor.pause(); + } mSecondaryThresholdSensor.register(mSecondaryEventListener); mInitializedListeners = true; } @@ -222,7 +229,7 @@ public class ProximitySensor implements ThresholdSensor { */ @Override public void unregister(ThresholdSensor.Listener listener) { - Assert.isMainThread(); + mExecution.assertIsMainThread(); mListeners.remove(listener); if (mListeners.size() == 0) { unregisterInternal(); @@ -230,7 +237,7 @@ public class ProximitySensor implements ThresholdSensor { } protected void unregisterInternal() { - Assert.isMainThread(); + mExecution.assertIsMainThread(); if (!mRegistered) { return; } @@ -252,7 +259,7 @@ public class ProximitySensor implements ThresholdSensor { /** Update all listeners with the last value this class received from the sensor. */ public void alertListeners() { - Assert.isMainThread(); + mExecution.assertIsMainThread(); if (mAlerting.getAndSet(true)) { return; } @@ -267,7 +274,7 @@ public class ProximitySensor implements ThresholdSensor { } private void onPrimarySensorEvent(ThresholdSensorEvent event) { - Assert.isMainThread(); + mExecution.assertIsMainThread(); if (mLastPrimaryEvent != null && event.getBelow() == mLastPrimaryEvent.getBelow()) { return; } @@ -290,7 +297,7 @@ public class ProximitySensor implements ThresholdSensor { } private void onSensorEvent(ThresholdSensorEvent event) { - Assert.isMainThread(); + mExecution.assertIsMainThread(); if (mLastEvent != null && event.getBelow() == mLastEvent.getBelow()) { return; } @@ -306,9 +313,9 @@ public class ProximitySensor implements ThresholdSensor { @Override public String toString() { return String.format("{registered=%s, paused=%s, near=%s, primarySensor=%s, " - + "secondarySensor=%s}", + + "secondarySensor=%s secondarySafe=%s}", isRegistered(), mPaused, isNear(), mPrimaryThresholdSensor, - mSecondaryThresholdSensor); + mSecondaryThresholdSensor, mSecondarySafe); } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java index 71b255229c8f..31c307297066 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java @@ -25,7 +25,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.util.Assert; +import com.android.systemui.util.concurrency.Execution; import java.util.ArrayList; import java.util.List; @@ -37,6 +37,7 @@ class ThresholdSensorImpl implements ThresholdSensor { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final AsyncSensorManager mSensorManager; + private final Execution mExecution; private final Sensor mSensor; private final float mThreshold; private boolean mRegistered; @@ -61,9 +62,10 @@ class ThresholdSensorImpl implements ThresholdSensor { } }; - private ThresholdSensorImpl(AsyncSensorManager sensorManager, - Sensor sensor, float threshold, float thresholdLatch, int sensorDelay) { + private ThresholdSensorImpl(AsyncSensorManager sensorManager, Sensor sensor, + Execution execution, float threshold, float thresholdLatch, int sensorDelay) { mSensorManager = sensorManager; + mExecution = execution; mSensor = sensor; mThreshold = threshold; mThresholdLatch = thresholdLatch; @@ -107,7 +109,7 @@ class ThresholdSensorImpl implements ThresholdSensor { */ @Override public void register(Listener listener) { - Assert.isMainThread(); + mExecution.assertIsMainThread(); if (!mListeners.contains(listener)) { mListeners.add(listener); } @@ -116,7 +118,7 @@ class ThresholdSensorImpl implements ThresholdSensor { @Override public void unregister(Listener listener) { - Assert.isMainThread(); + mExecution.assertIsMainThread(); mListeners.remove(listener); unregisterInternal(); } @@ -126,7 +128,7 @@ class ThresholdSensorImpl implements ThresholdSensor { */ @Override public void pause() { - Assert.isMainThread(); + mExecution.assertIsMainThread(); mPaused = true; unregisterInternal(); } @@ -136,7 +138,7 @@ class ThresholdSensorImpl implements ThresholdSensor { */ @Override public void resume() { - Assert.isMainThread(); + mExecution.assertIsMainThread(); mPaused = false; registerInternal(); } @@ -148,7 +150,7 @@ class ThresholdSensorImpl implements ThresholdSensor { } private void registerInternal() { - Assert.isMainThread(); + mExecution.assertIsMainThread(); if (mRegistered || mPaused || mListeners.isEmpty()) { return; } @@ -158,7 +160,7 @@ class ThresholdSensorImpl implements ThresholdSensor { } private void unregisterInternal() { - Assert.isMainThread(); + mExecution.assertIsMainThread(); if (!mRegistered) { return; } @@ -177,7 +179,7 @@ class ThresholdSensorImpl implements ThresholdSensor { * still appears entirely binary. */ private void onSensorEvent(boolean belowThreshold, boolean aboveThreshold, long timestampNs) { - Assert.isMainThread(); + mExecution.assertIsMainThread(); if (!mRegistered) { return; } @@ -212,6 +214,7 @@ class ThresholdSensorImpl implements ThresholdSensor { static class Builder { private final Resources mResources; private final AsyncSensorManager mSensorManager; + private final Execution mExecution; private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;; private float mThresholdValue; private float mThresholdLatchValue; @@ -221,9 +224,10 @@ class ThresholdSensorImpl implements ThresholdSensor { private boolean mThresholdLatchValueSet; @Inject - Builder(@Main Resources resources, AsyncSensorManager sensorManager) { + Builder(@Main Resources resources, AsyncSensorManager sensorManager, Execution execution) { mResources = resources; mSensorManager = sensorManager; + mExecution = execution; } @@ -302,7 +306,8 @@ class ThresholdSensorImpl implements ThresholdSensor { } return new ThresholdSensorImpl( - mSensorManager, mSensor, mThresholdValue, mThresholdLatchValue, mSensorDelay); + mSensorManager, mSensor, mExecution, + mThresholdValue, mThresholdLatchValue, mSensorDelay); } private Sensor findSensorByType(String sensorType) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index f9b6d4467e3c..83c2227ffc12 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -56,6 +57,8 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock SmartspaceTransitionController mSmartSpaceTransitionController; + @Mock + UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private KeyguardStatusViewController mController; @@ -72,7 +75,8 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { mConfigurationController, mDozeParameters, mKeyguardUnlockAnimationController, - mSmartSpaceTransitionController); + mSmartSpaceTransitionController, + mUnlockedScreenOffAnimationController); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java index 936ec8021f5e..49604ff49838 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java @@ -22,6 +22,7 @@ import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; +import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.systemBars; import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK; @@ -69,6 +70,7 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; @@ -88,8 +90,9 @@ import org.mockito.MockitoAnnotations; import java.util.List; @SmallTest +@FlakyTest(bugId = 188890599) @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class MagnificationModeSwitchTest extends SysuiTestCase { private static final float FADE_IN_ALPHA = 1f; @@ -223,7 +226,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { } @Test - public void onApplyWindowInsetsWithWindowInsetsChange_buttonIsShowing_draggableBoundsChanged() { + public void onSystemBarsInsetsChanged_buttonIsShowing_draggableBoundsChanged() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds); @@ -236,6 +239,19 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { } @Test + public void onDisplayCutoutInsetsChanged_buttonIsShowing_draggableBoundsChanged() { + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds); + + mWindowManager.setWindowInsets(new WindowInsets.Builder() + .setInsetsIgnoringVisibility(displayCutout(), Insets.of(20, 30, 20, 30)) + .build()); + mSpyImageView.onApplyWindowInsets(WindowInsets.CONSUMED); + + assertNotEquals(oldDraggableBounds, mMagnificationModeSwitch.mDraggableWindowBounds); + } + + @Test public void onDraggingGestureFinish_buttonIsShowing_stickToRightEdge() { final int windowHalfWidth = mWindowManager.getCurrentWindowMetrics().getBounds().width() / 2; @@ -378,10 +394,26 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { hasItems(new AccessibilityNodeInfo.AccessibilityAction( ACTION_CLICK.getId(), mContext.getResources().getString( R.string.magnification_mode_switch_click_label)))); + assertThat(nodeInfo.getActionList(), + hasItems(new AccessibilityNodeInfo.AccessibilityAction( + R.id.accessibility_action_move_up, mContext.getResources().getString( + R.string.accessibility_control_move_up)))); + assertThat(nodeInfo.getActionList(), + hasItems(new AccessibilityNodeInfo.AccessibilityAction( + R.id.accessibility_action_move_down, mContext.getResources().getString( + R.string.accessibility_control_move_down)))); + assertThat(nodeInfo.getActionList(), + hasItems(new AccessibilityNodeInfo.AccessibilityAction( + R.id.accessibility_action_move_left, mContext.getResources().getString( + R.string.accessibility_control_move_left)))); + assertThat(nodeInfo.getActionList(), + hasItems(new AccessibilityNodeInfo.AccessibilityAction( + R.id.accessibility_action_move_right, mContext.getResources().getString( + R.string.accessibility_control_move_right)))); } @Test - public void performA11yActions_showWindowModeButton_verifyTapAction() { + public void performClickA11yActions_showWindowModeButton_verifyTapAction() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); resetAndStubMockImageViewAndAnimator(); @@ -392,6 +424,16 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { } @Test + public void performMoveLeftA11yAction_showButtonAtRightEdge_moveToLeftEdge() { + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + + mSpyImageView.performAccessibilityAction( + R.id.accessibility_action_move_left, null); + + assertLayoutPosition(/* toLeftScreenEdge= */true); + } + + @Test public void showButton_showFadeOutAnimation_fadeOutAnimationCanceled() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); assertShowFadingAnimation(FADE_OUT_ALPHA); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java index 0de257ad424b..448211ed5274 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java @@ -18,6 +18,8 @@ package com.android.systemui.accessibility.floatingmenu; import static android.view.View.OVER_SCROLL_ALWAYS; import static android.view.View.OVER_SCROLL_NEVER; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.navigationBars; import static com.google.common.truth.Truth.assertThat; @@ -31,9 +33,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; +import android.graphics.Insets; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; @@ -43,7 +47,9 @@ import android.testing.TestableLooper; import android.view.MotionEvent; import android.view.View; import android.view.ViewPropertyAnimator; +import android.view.WindowInsets; import android.view.WindowManager; +import android.view.WindowMetrics; import android.view.accessibility.AccessibilityNodeInfo; import androidx.annotation.NonNull; @@ -79,12 +85,17 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase { @Mock private ViewPropertyAnimator mAnimator; + @Mock + private WindowMetrics mWindowMetrics; + private MotionEvent mInterceptMotionEvent; private RecyclerView mListView; private Rect mAvailableBounds = new Rect(100, 200, 300, 400); + private int mScreenHeight; + private int mMenuWindowHeight; private int mMenuHalfWidth; private int mMenuHalfHeight; private int mScreenHalfWidth; @@ -111,18 +122,19 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase { final int margin = res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin); final int padding = - res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_padding); + res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_padding); final int iconWidthHeight = res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height); final int menuWidth = padding * 2 + iconWidthHeight; final int menuHeight = (padding + iconWidthHeight) * mTargets.size() + padding; final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels; - final int screenHeight = mContext.getResources().getDisplayMetrics().heightPixels; + mScreenHeight = mContext.getResources().getDisplayMetrics().heightPixels; mMenuHalfWidth = menuWidth / 2; mMenuHalfHeight = menuHeight / 2; mScreenHalfWidth = screenWidth / 2; - mScreenHalfHeight = screenHeight / 2; + mScreenHalfHeight = mScreenHeight / 2; mMaxWindowX = screenWidth - margin - menuWidth; + mMenuWindowHeight = menuHeight + margin * 2; } @Test @@ -464,12 +476,80 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase { assertThat(mListView.getOverScrollMode()).isEqualTo(OVER_SCROLL_NEVER); } + @Test + public void showMenuView_insetsListener_overlapWithIme_menuViewShifted() { + final int offset = 200; + + showMenuWithLatestStatus(); + final WindowInsets imeInset = fakeImeInsetWith(offset); + when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); + when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset); + final int expectedLayoutY = mMenuView.mCurrentLayoutParams.y - offset; + mMenuView.dispatchApplyWindowInsets(imeInset); + + assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY); + } + + @Test + public void hideIme_onMenuViewShifted_menuViewMovedBack() { + final int offset = 200; + showMenuWithLatestStatus(); + final WindowInsets imeInset = fakeImeInsetWith(offset); + when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); + when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset); + final int expectedLayoutY = mMenuView.mCurrentLayoutParams.y; + mMenuView.dispatchApplyWindowInsets(imeInset); + + mMenuView.dispatchApplyWindowInsets( + new WindowInsets.Builder().setVisible(ime(), false).build()); + + assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY); + } + + @Test + public void showMenuAndIme_withHigherIme_alignScreenTopEdge() { + final int offset = 99999; + + showMenuWithLatestStatus(); + final WindowInsets imeInset = fakeImeInsetWith(offset); + when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); + when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset); + mMenuView.dispatchApplyWindowInsets(imeInset); + + assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(0); + } + @After public void tearDown() { mInterceptMotionEvent = null; mMotionEventHelper.recycleEvents(); } + private void showMenuWithLatestStatus() { + mMenuView.show(); + mMenuView.onTargetsChanged(mTargets); + mMenuView.setSizeType(0); + mMenuView.setShapeType(0); + } + + /** + * Based on the current menu status, fake the ime inset component {@link WindowInsets} used + * for testing. + * + * @param offset is used for the y-axis position of ime higher than the y-axis position of menu. + * @return the ime inset + */ + private WindowInsets fakeImeInsetWith(int offset) { + // Ensure the keyboard has overlapped on the menu view. + final int fakeImeHeight = + mScreenHeight - (mMenuView.mCurrentLayoutParams.y + mMenuWindowHeight) + offset; + + return new WindowInsets.Builder() + .setVisible(ime() | navigationBars(), true) + .setInsets(ime() | navigationBars(), Insets.of(0, 0, 0, fakeImeHeight)) + .build(); + } + private class TestAccessibilityFloatingMenu extends AccessibilityFloatingMenuView { TestAccessibilityFloatingMenu(Context context, RecyclerView listView) { super(context, listView); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index 9a0ed9897e83..d4990114e1c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -2,6 +2,9 @@ package com.android.systemui.animation import android.app.ActivityManager import android.app.WindowConfiguration +import android.content.ComponentName +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo import android.graphics.Point import android.graphics.Rect import android.os.Looper @@ -167,10 +170,16 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { private fun fakeWindow(): RemoteAnimationTarget { val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */) + val taskInfo = ActivityManager.RunningTaskInfo() + taskInfo.topActivity = ComponentName("com.android.systemui", "FakeActivity") + taskInfo.topActivityInfo = ActivityInfo().apply { + applicationInfo = ApplicationInfo() + } + return RemoteAnimationTarget( 0, RemoteAnimationTarget.MODE_OPENING, SurfaceControl(), false, Rect(), Rect(), 0, Point(), Rect(), bounds, WindowConfiguration(), false, SurfaceControl(), Rect(), - ActivityManager.RunningTaskInfo() + taskInfo ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java index f41c100ed562..0b399cf118b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java @@ -224,7 +224,7 @@ public class AuthContainerViewTest extends SysuiTestCase { public void testLayoutParams_hasSecureWindowFlag() { final IBinder windowToken = mock(IBinder.class); final WindowManager.LayoutParams layoutParams = - AuthContainerView.getLayoutParams(windowToken); + AuthContainerView.getLayoutParams(windowToken, ""); assertTrue((layoutParams.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0); } @@ -232,7 +232,7 @@ public class AuthContainerViewTest extends SysuiTestCase { public void testLayoutParams_excludesImeInsets() { final IBinder windowToken = mock(IBinder.class); final WindowManager.LayoutParams layoutParams = - AuthContainerView.getLayoutParams(windowToken); + AuthContainerView.getLayoutParams(windowToken, ""); assertTrue((layoutParams.getFitInsetsTypes() & WindowInsets.Type.ime()) == 0); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 46c18480c77b..9322aa9b0ea8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -275,6 +275,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); + when(mUdfpsView.isIlluminationRequested()).thenReturn(true); // WHEN it is cancelled mUdfpsController.onCancelUdfps(); // THEN the illumination is hidden @@ -289,6 +290,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); + when(mUdfpsView.isIlluminationRequested()).thenReturn(true); // WHEN it times out mFgExecutor.advanceClockToNext(); mFgExecutor.runAllReady(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index a7c63c13637a..5923de6719a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -254,15 +254,15 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { @Test public void testOnDetachedStateReset() { - // GIVEN view is attached, alt auth is force being shown + // GIVEN view is attached mController.onViewAttached(); - captureStatusBarStateListeners(); + captureAltAuthInterceptor(); // WHEN view is detached mController.onViewDetached(); - // THEN set alternate auth interceptor to null - verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(null); + // THEN remove alternate auth interceptor + verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor); } private void sendStatusBarStateChanged(int statusBarState) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 6f03f5d975d0..e6f9aaf78080 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -49,6 +49,7 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; @@ -82,6 +83,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock SysuiStatusBarStateController mStatusBarStateController; private @Mock KeyguardStateController mKeyguardStateController; private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; + private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -101,7 +103,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mDismissCallbackRegistry, mUpdateMonitor, mDumpManager, mUiBgExecutor, mPowerManager, mTrustManager, mDeviceConfig, mNavigationModeController, mKeyguardDisplayManager, mDozeParameters, mStatusBarStateController, - mKeyguardStateController, () -> mKeyguardUnlockAnimationController); + mKeyguardStateController, () -> mKeyguardUnlockAnimationController, + mUnlockedScreenOffAnimationController); mViewMediator.start(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt index 126dca550cdb..a2b5013c8c2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt @@ -23,6 +23,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.text.TextUtils import android.view.View +import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -52,6 +53,7 @@ class QSTileViewImplTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + context.ensureTestableResources() tileView = FakeTileView(context, iconView, false) customDrawableView = tileView.requireViewById(R.id.customDrawable) @@ -117,7 +119,7 @@ class QSTileViewImplTest : SysuiTestCase() { } @Test - fun testSecondaryLabelDescription_unavailable() { + fun testSecondaryLabelDescription_unavailable_default() { val state = QSTile.State() state.state = Tile.STATE_UNAVAILABLE state.secondaryLabel = "" @@ -130,7 +132,7 @@ class QSTileViewImplTest : SysuiTestCase() { } @Test - fun testSecondaryLabelDescription_booleanInactive() { + fun testSecondaryLabelDescription_booleanInactive_default() { val state = QSTile.BooleanState() state.state = Tile.STATE_INACTIVE state.secondaryLabel = "" @@ -143,7 +145,7 @@ class QSTileViewImplTest : SysuiTestCase() { } @Test - fun testSecondaryLabelDescription_booleanActive() { + fun testSecondaryLabelDescription_booleanActive_default() { val state = QSTile.BooleanState() state.state = Tile.STATE_ACTIVE state.secondaryLabel = "" @@ -220,6 +222,41 @@ class QSTileViewImplTest : SysuiTestCase() { assertThat(chevronView.visibility).isEqualTo(View.GONE) } + @Test + fun testUseStateStringsForKnownSpec_Boolean() { + val state = QSTile.BooleanState() + val spec = "internet" + state.spec = spec + + val unavailableString = "${spec}_unavailable" + val offString = "${spec}_off" + val onString = "${spec}_on" + + context.orCreateTestableResources.addOverride(R.array.tile_states_internet, arrayOf( + unavailableString, + offString, + onString + )) + + // State UNAVAILABLE + state.secondaryLabel = "" + state.state = Tile.STATE_UNAVAILABLE + tileView.changeState(state) + assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(unavailableString) + + // State INACTIVE + state.secondaryLabel = "" + state.state = Tile.STATE_INACTIVE + tileView.changeState(state) + assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(offString) + + // State ACTIVE + state.secondaryLabel = "" + state.state = Tile.STATE_ACTIVE + tileView.changeState(state) + assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(onString) + } + class FakeTileView( context: Context, icon: QSIconView, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt new file mode 100644 index 000000000000..19ffa4991eaf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tileimpl + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class TilesStatesTextTest : SysuiTestCase() { + + @Test + fun testStockTilesHaveStatesArray() { + val tiles = mContext.getString(R.string.quick_settings_tiles_stock).split(",") + tiles.forEach { spec -> + val resName = "${QSTileViewImpl.TILE_STATE_RES_PREFIX}$spec" + val resId = mContext.resources.getIdentifier(resName, "array", mContext.packageName) + + assertNotEquals("Missing resource for $resName", 0, resId) + + val array = mContext.resources.getStringArray(resId) + + assertEquals("Array for $spec is of wrong size", 3, array.size) + } + } + + @Test + fun testDefaultArray() { + val array = mContext.resources.getStringArray(R.array.tile_states_default) + + assertThat(array.size).isEqualTo(3) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt index 03744b78e8d5..85ec3faa4013 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt @@ -20,6 +20,7 @@ import android.testing.AndroidTestingRunner import android.view.View import android.view.WindowManager import androidx.test.filters.SmallTest +import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.FeatureFlags import com.android.systemui.statusbar.commandline.CommandRegistry @@ -50,6 +51,7 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var rippleView: ChargingRippleView @Mock private lateinit var windowManager: WindowManager + @Mock private lateinit var uiEventLogger: UiEventLogger private val systemClock = FakeSystemClock() @Before @@ -58,7 +60,7 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { `when`(featureFlags.isChargingRippleEnabled).thenReturn(true) controller = WiredChargingRippleController( commandRegistry, batteryController, configurationController, - featureFlags, context, windowManager, systemClock) + featureFlags, context, windowManager, systemClock, uiEventLogger) controller.rippleView = rippleView // Replace the real ripple view with a mock instance } @@ -87,6 +89,10 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { // Verify ripple removed runnableCaptor.value.run() verify(windowManager).removeView(rippleView) + + // Verify event logged + verify(uiEventLogger).log( + WiredChargingRippleController.WiredChargingRippleEvent.CHARGING_RIPPLE_PLAYED) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index ee8d1209a5cb..ffb53a8b2e11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -160,6 +160,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private DozeParameters mDozeParameters; @Mock + private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; + @Mock private NotificationPanelView mView; @Mock private LayoutInflater mLayoutInflater; @@ -328,7 +330,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { mock(HeadsUpManagerPhone.class), new StatusBarStateControllerImpl(new UiEventLoggerFake()), mKeyguardBypassController, - mDozeParameters); + mDozeParameters, + mUnlockedScreenOffAnimationController); PulseExpansionHandler expansionHandler = new PulseExpansionHandler( mContext, coordinator, @@ -386,7 +389,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { mFragmentService, mQuickAccessWalletController, new FakeExecutor(new FakeSystemClock()), - mSecureSettings); + mSecureSettings, + mUnlockedScreenOffAnimationController); mNotificationPanelViewController.initDependencies( mStatusBar, mNotificationShelfController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 8b0b579bea54..075d1dd626e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -108,6 +108,8 @@ public class ScrimControllerTest extends SysuiTestCase { private DockManager mDockManager; @Mock private ConfigurationController mConfigurationController; + @Mock + private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private static class AnimatorListener implements Animator.AnimatorListener { @@ -221,7 +223,8 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController = new ScrimController(mLightBarController, mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder, new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor, - mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock())); + mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()), + mUnlockedScreenOffAnimationController); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront, mScrimForBubble); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 5a3683e8e3f4..deff2043f6dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -272,6 +272,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; @Mock private IWallpaperManager mWallpaperManager; @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; + @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private ShadeController mShadeController; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private InitController mInitController = new InitController(); @@ -441,7 +442,8 @@ public class StatusBarTest extends SysuiTestCase { mLocationPublisher, mLockscreenTransitionController, mFeatureFlags, - mKeyguardUnlockAnimationController); + mKeyguardUnlockAnimationController, + mUnlockedScreenOffAnimationController); when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class), any(NotificationPanelViewController.class), any(BiometricUnlockController.class), any(ViewGroup.class), any(KeyguardBypassController.class))) diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java index 9bb4c4b08481..50947ab0ee86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java @@ -17,6 +17,7 @@ package com.android.systemui.util.sensors; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.concurrency.FakeExecution; public class FakeProximitySensor extends ProximitySensor { private boolean mAvailable; @@ -25,7 +26,7 @@ public class FakeProximitySensor extends ProximitySensor { public FakeProximitySensor(ThresholdSensor primary, ThresholdSensor secondary, DelayableExecutor delayableExecutor) { super(primary, secondary == null ? new FakeThresholdSensor() : secondary, - delayableExecutor); + delayableExecutor, new FakeExecution()); mAvailable = true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java index bae1d98aa310..8f0754592b83 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java @@ -27,6 +27,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecution; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -54,7 +55,8 @@ public class ProximitySensorDualTest extends SysuiTestCase { mThresholdSensorSecondary.setLoaded(true); mProximitySensor = new ProximitySensor( - mThresholdSensorPrimary, mThresholdSensorSecondary, mFakeExecutor); + mThresholdSensorPrimary, mThresholdSensorSecondary, mFakeExecutor, + new FakeExecution()); } @Test @@ -324,9 +326,10 @@ public class ProximitySensorDualTest extends SysuiTestCase { TestableListener listener = new TestableListener(); + // WE immediately register the secondary sensor. mProximitySensor.register(listener); assertFalse(mThresholdSensorPrimary.isPaused()); - assertTrue(mThresholdSensorSecondary.isPaused()); + assertFalse(mThresholdSensorSecondary.isPaused()); assertNull(listener.mLastEvent); assertEquals(0, listener.mCallCount); diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java index f2d5284d4009..6c6d355d7866 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java @@ -27,6 +27,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecution; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -54,7 +55,7 @@ public class ProximitySensorSingleTest extends SysuiTestCase { mThresholdSensor.setLoaded(true); mProximitySensor = new ProximitySensor( - mThresholdSensor, new FakeThresholdSensor(), mFakeExecutor); + mThresholdSensor, new FakeThresholdSensor(), mFakeExecutor, new FakeExecution()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java index d3a35a735f6d..12765679a7f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java @@ -24,7 +24,7 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import com.android.systemui.SysuiTestCase; -import com.android.systemui.util.Assert; +import com.android.systemui.util.concurrency.FakeExecution; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeThreadFactory; import com.android.systemui.util.time.FakeSystemClock; @@ -52,7 +52,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase { mFakeProximitySensor = mSensorManager.getFakeProximitySensor(); ThresholdSensorImpl.Builder thresholdSensorBuilder = new ThresholdSensorImpl.Builder( - null, mAsyncSensorManager); + null, mAsyncSensorManager, new FakeExecution()); mThresholdSensor = (ThresholdSensorImpl) thresholdSensorBuilder .setSensor(mFakeProximitySensor.getSensor()) .setThresholdValue(mFakeProximitySensor.getSensor().getMaximumRange()) @@ -61,7 +61,6 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testSingleListener() { - Assert.setTestThread(Thread.currentThread()); TestableListener listener = new TestableListener(); assertFalse(mThresholdSensor.isRegistered()); @@ -83,7 +82,6 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testMultiListener() { - Assert.setTestThread(Thread.currentThread()); TestableListener listenerA = new TestableListener(); TestableListener listenerB = new TestableListener(); @@ -117,7 +115,6 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testDuplicateListener() { - Assert.setTestThread(Thread.currentThread()); TestableListener listenerA = new TestableListener(); assertFalse(mThresholdSensor.isRegistered()); @@ -142,7 +139,6 @@ public class ThresholdSensorImplTest extends SysuiTestCase { } @Test public void testUnregister() { - Assert.setTestThread(Thread.currentThread()); TestableListener listener = new TestableListener(); assertFalse(mThresholdSensor.isRegistered()); @@ -162,7 +158,6 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testPauseAndResume() { - Assert.setTestThread(Thread.currentThread()); TestableListener listener = new TestableListener(); assertFalse(mThresholdSensor.isRegistered()); @@ -205,7 +200,6 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testAlertListeners() { - Assert.setTestThread(Thread.currentThread()); TestableListener listenerA = new TestableListener(); TestableListener listenerB = new TestableListener(); @@ -237,12 +231,11 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testHysteresis() { - Assert.setTestThread(Thread.currentThread()); float lowValue = 10f; float highValue = 100f; FakeSensorManager.FakeGenericSensor sensor = mSensorManager.getFakeLightSensor(); ThresholdSensorImpl.Builder thresholdSensorBuilder = new ThresholdSensorImpl.Builder( - null, mAsyncSensorManager); + null, mAsyncSensorManager, new FakeExecution()); ThresholdSensorImpl thresholdSensor = (ThresholdSensorImpl) thresholdSensorBuilder .setSensor(sensor.getSensor()) .setThresholdValue(lowValue) @@ -286,7 +279,6 @@ public class ThresholdSensorImplTest extends SysuiTestCase { @Test public void testAlertAfterPause() { - Assert.setTestThread(Thread.currentThread()); TestableListener listener = new TestableListener(); mThresholdSensor.register(listener); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 71d6a48b89ee..8041ec4a9727 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -79,8 +79,10 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UptimeMillisLong; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityManagerInternal.ServiceNotificationPolicy; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -213,6 +215,9 @@ public final class ActiveServices { // at the same time. final int mMaxStartingBackground; + /** + * Master service bookkeeping, keyed by user number. + */ final SparseArray<ServiceMap> mServiceMap = new SparseArray<>(); /** @@ -1811,7 +1816,7 @@ public final class ActiveServices { showFgsBgRestrictedNotificationLocked(r); updateServiceForegroundLocked(psr, true); ignoreForeground = true; - logForegroundServiceStateChanged(r, + logFGSStateChangeLocked(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED, 0); if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, @@ -1859,6 +1864,7 @@ public final class ActiveServices { active.mNumActive++; } r.isForeground = true; + r.mLogEntering = true; enterForeground = true; r.mStartForegroundCount++; r.mFgsEnterTime = SystemClock.uptimeMillis(); @@ -1881,14 +1887,7 @@ public final class ActiveServices { } // Even if the service is already a FGS, we need to update the notification, // so we need to call it again. - postFgsNotificationLocked(r); - if (enterForeground) { - // Because we want to log what's updated in postFgsNotificationLocked(), - // this must be called after postFgsNotificationLocked(). - logForegroundServiceStateChanged(r, - FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, - 0); - } + r.postNotification(); if (r.app != null) { updateServiceForegroundLocked(psr, true); } @@ -1937,7 +1936,7 @@ public final class ActiveServices { AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); unregisterAppOpCallbackLocked(r); - logForegroundServiceStateChanged(r, + logFGSStateChangeLocked(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT, r.mFgsExitTime > r.mFgsEnterTime ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0); @@ -1964,7 +1963,18 @@ public final class ActiveServices { } } - private boolean withinFgsDeferRateLimit(final int uid, final long now) { + private boolean withinFgsDeferRateLimit(ServiceRecord sr, final long now) { + // If we're still within the service's deferral period, then by definition + // deferral is not rate limited. + if (now < sr.fgDisplayTime) { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, "FGS transition for " + sr + + " within deferral period, no rate limit applied"); + } + return false; + } + + final int uid = sr.appInfo.uid; final long eligible = mFgsDeferralEligible.get(uid, 0L); if (DEBUG_FOREGROUND_SERVICE) { if (now < eligible) { @@ -1975,62 +1985,137 @@ public final class ActiveServices { return now < eligible; } - // TODO: remove as part of fixing b/173627642 + ServiceNotificationPolicy applyForegroundServiceNotificationLocked(Notification notification, + final int id, final String pkg, final int userId) { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, "Evaluating FGS policy for id=" + id + + " pkg=" + pkg + " not=" + notification); + } + // Is there an FGS using this notification? + final ServiceMap smap = mServiceMap.get(userId); + if (smap == null) { + // No services in this user at all + return ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE; + } + + for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) { + final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i); + if (id != sr.foregroundId || !pkg.equals(sr.appInfo.packageName)) { + // Not this one; keep looking + continue; + } + + // Found; it is associated with an FGS. Make sure that it's flagged: + // it may have entered the bookkeeping outside of Service-related + // APIs. We also make sure to take this latest Notification as + // the content to be shown (immediately or eventually). + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, " FOUND: notification is for " + sr); + } + notification.flags |= Notification.FLAG_FOREGROUND_SERVICE; + sr.foregroundNoti = notification; + + // ...and determine immediate vs deferred display policy for it + final boolean showNow = shouldShowFgsNotificationLocked(sr); + if (showNow) { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, " Showing immediately due to policy"); + } + sr.mFgsNotificationDeferred = false; + return ServiceNotificationPolicy.SHOW_IMMEDIATELY; + } + + // Deferring - kick off the timer if necessary, and tell the caller + // that it's to be shown only if it's an update to already- + // visible content (e.g. if it's an FGS adopting a + // previously-posted Notification). + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, " Deferring / update-only"); + } + startFgsDeferralTimerLocked(sr); + return ServiceNotificationPolicy.UPDATE_ONLY; + } + + // None of the services in this user are FGSs + return ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE; + } + + // No legacy-app behavior skew intended but there's a runtime E-stop if a need + // arises, so note that @SuppressWarnings("AndroidFrameworkCompatChange") - private void postFgsNotificationLocked(ServiceRecord r) { - final int uid = r.appInfo.uid; + private boolean shouldShowFgsNotificationLocked(ServiceRecord r) { final long now = SystemClock.uptimeMillis(); - final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S); // Is the behavior enabled at all? - boolean showNow = !mAm.mConstants.mFlagFgsNotificationDeferralEnabled; - if (!showNow) { - // Did the app have another FGS notification deferred recently? - showNow = withinFgsDeferRateLimit(uid, now); + if (!mAm.mConstants.mFlagFgsNotificationDeferralEnabled) { + return true; + } + + // Has this service's deferral timer expired? + if (r.mFgsNotificationDeferred && now >= r.fgDisplayTime) { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG, "FGS reached end of deferral period: " + r); + } + return true; + } + + // Did the app have another FGS notification deferred recently? + if (withinFgsDeferRateLimit(r, now)) { + return true; } - if (!showNow) { - // Legacy apps' FGS notifications are not deferred unless the relevant + + if (mAm.mConstants.mFlagFgsNotificationDeferralApiGated) { + // Legacy apps' FGS notifications are also deferred unless the relevant // DeviceConfig element has been set - showNow = isLegacyApp && mAm.mConstants.mFlagFgsNotificationDeferralApiGated; - } - if (!showNow) { - // has the app forced deferral? - if (!r.foregroundNoti.isForegroundDisplayForceDeferred()) { - // is the notification such that it should show right away? - showNow = r.foregroundNoti.shouldShowForegroundImmediately(); - if (DEBUG_FOREGROUND_SERVICE && showNow) { + final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S); + if (isLegacyApp) { + return true; + } + } + + // did we already show it? + if (r.mFgsNotificationShown) { + return true; + } + + // has the app forced deferral? + if (!r.foregroundNoti.isForegroundDisplayForceDeferred()) { + // is the notification such that it should show right away? + if (r.foregroundNoti.shouldShowForegroundImmediately()) { + if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " notification policy says show immediately"); } - // or is this an type of FGS that always shows immediately? - if (!showNow) { - if ((r.foregroundServiceType & FGS_IMMEDIATE_DISPLAY_MASK) != 0) { - if (DEBUG_FOREGROUND_SERVICE) { - Slog.d(TAG_SERVICE, "FGS " + r - + " type gets immediate display"); - } - showNow = true; - } - } - } else { + return true; + } + + // or is this an type of FGS that always shows immediately? + if ((r.foregroundServiceType & FGS_IMMEDIATE_DISPLAY_MASK) != 0) { if (DEBUG_FOREGROUND_SERVICE) { - Slog.d(TAG_SERVICE, "FGS " + r + " notification is app deferred"); + Slog.d(TAG_SERVICE, "FGS " + r + + " type gets immediate display"); } + return true; } - } - if (showNow) { + // fall through to return false: no policy dictates immediate display + } else { if (DEBUG_FOREGROUND_SERVICE) { - Slog.d(TAG_SERVICE, "FGS " + r + " non-deferred notification"); + Slog.d(TAG_SERVICE, "FGS " + r + " notification is app deferred"); } - r.postNotification(); - r.mFgsNotificationDeferred = false; - r.mFgsNotificationShown = true; - return; + // fall through to return false } - r.mFgsNotificationDeferred = true; - r.mFgsNotificationShown = false; + return false; + } + + // Target SDK consultation here is strictly for logging purposes, not + // behavioral variation. + @SuppressWarnings("AndroidFrameworkCompatChange") + private void startFgsDeferralTimerLocked(ServiceRecord r) { + final long now = SystemClock.uptimeMillis(); + final int uid = r.appInfo.uid; + // schedule the actual notification post long when = now + mAm.mConstants.mFgsNotificationDeferralInterval; // If there are already deferred FGS notifications for this app, @@ -2053,11 +2138,14 @@ public final class ActiveServices { final long nextEligible = when + mAm.mConstants.mFgsNotificationDeferralExclusionTime; mFgsDeferralEligible.put(uid, nextEligible); r.fgDisplayTime = when; + r.mFgsNotificationDeferred = true; + r.mFgsNotificationShown = false; mPendingFgsNotifications.add(r); if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " notification in " + (when - now) + " ms"); } + final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S); if (isLegacyApp) { Slog.i(TAG_SERVICE, "Deferring FGS notification in legacy app " + r.appInfo.packageName + "/" + UserHandle.formatUid(r.appInfo.uid) @@ -2089,9 +2177,16 @@ public final class ActiveServices { if (r.isForeground && r.app != null) { r.postNotification(); r.mFgsNotificationShown = true; - } else if (DEBUG_FOREGROUND_SERVICE) { - Slog.d(TAG_SERVICE, " - service no longer running/fg, ignoring"); + } else { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, " - service no longer running/fg, ignoring"); + } } + // Regardless of whether we needed to post the notification or the + // service is no longer running, we may not have logged its FGS + // transition yet depending on the timing and API sequence that led + // to this point - so make sure to do so. + maybeLogFGSStateEnteredLocked(r); } } if (DEBUG_FOREGROUND_SERVICE) { @@ -2102,6 +2197,60 @@ public final class ActiveServices { } }; + private void maybeLogFGSStateEnteredLocked(ServiceRecord r) { + if (r.mLogEntering) { + logFGSStateChangeLocked(r, + FrameworkStatsLog + .FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, + 0); + r.mLogEntering = false; + } + } + + /** + * Callback from NotificationManagerService whenever it posts a notification + * associated with a foreground service. This is the unified handling point + * for the disjoint code flows that affect an FGS's notifiation content and + * visibility, starting with both Service.startForeground() and + * NotificationManager.notify(). + */ + public void onForegroundServiceNotificationUpdateLocked(Notification notification, + final int id, final String pkg, @UserIdInt final int userId) { + // If this happens to be a Notification for an FGS still in its deferral period, + // drop the deferral and make sure our content bookkeeping is up to date. + for (int i = mPendingFgsNotifications.size() - 1; i >= 0; i--) { + final ServiceRecord sr = mPendingFgsNotifications.get(i); + if (userId == sr.userId + && id == sr.foregroundId + && sr.appInfo.packageName.equals(pkg)) { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, "Notification shown; canceling deferral of " + + sr); + } + maybeLogFGSStateEnteredLocked(sr); + sr.mFgsNotificationShown = true; + sr.mFgsNotificationDeferred = false; + mPendingFgsNotifications.remove(i); + } + } + // And make sure to retain the latest notification content for the FGS + ServiceMap smap = mServiceMap.get(userId); + if (smap != null) { + for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) { + final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i); + if (sr.isForeground + && id == sr.foregroundId + && sr.appInfo.packageName.equals(pkg)) { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, "Recording shown notification for " + + sr); + } + sr.foregroundNoti = notification; + } + } + } + } + /** Registers an AppOpCallback for monitoring special AppOps for this foreground service. */ private void registerAppOpCallbackLocked(@NonNull ServiceRecord r) { if (r.app == null) { @@ -4016,7 +4165,7 @@ public final class ActiveServices { AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); unregisterAppOpCallbackLocked(r); r.mFgsExitTime = SystemClock.uptimeMillis(); - logForegroundServiceStateChanged(r, + logFGSStateChangeLocked(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT, r.mFgsExitTime > r.mFgsEnterTime ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0); @@ -6022,7 +6171,7 @@ public final class ActiveServices { * @param state one of ENTER/EXIT/DENIED event. * @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state. */ - private void logForegroundServiceStateChanged(ServiceRecord r, int state, int durationMs) { + private void logFGSStateChangeLocked(ServiceRecord r, int state, int durationMs) { if (!ActivityManagerUtils.shouldSamplePackageForAtom( r.packageName, mAm.mConstants.mFgsAtomSampleRate)) { return; diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 0fff8be70574..0d19efc20785 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -453,7 +453,7 @@ final class ActivityManagerConstants extends ContentObserver { volatile long mFgsNotificationDeferralInterval = 10_000; // Rate limit: minimum time after an app's FGS notification is deferred - // before another FGS notifiction from that app can be deferred. + // before another FGS notification from that app can be deferred. volatile long mFgsNotificationDeferralExclusionTime = 2 * 60 * 1000L; /** diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3e6a0a8ec80d..6e500e4ea5ea 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16059,6 +16059,24 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public ServiceNotificationPolicy applyForegroundServiceNotification( + Notification notification, int id, String pkg, int userId) { + synchronized (ActivityManagerService.this) { + return mServices.applyForegroundServiceNotificationLocked(notification, + id, pkg, userId); + } + } + + @Override + public void onForegroundServiceNotificationUpdate(Notification notification, + int id, String pkg, @UserIdInt int userId) { + synchronized (ActivityManagerService.this) { + mServices.onForegroundServiceNotificationUpdateLocked(notification, + id, pkg, userId); + } + } + + @Override public void stopForegroundServicesForChannel(String pkg, int userId, String channelId) { synchronized (ActivityManagerService.this) { diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java index 70a54cfdafa5..98ad81d7d9de 100644 --- a/services/core/java/com/android/server/am/AssistDataRequester.java +++ b/services/core/java/com/android/server/am/AssistDataRequester.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManager.ASSIST_CONTEXT_CONTENT; import static android.app.ActivityManager.ASSIST_CONTEXT_FULL; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_NONE; @@ -143,45 +144,47 @@ public class AssistDataRequester extends IAssistDataReceiver.Stub { * Request that autofill data be loaded asynchronously. The resulting data will be provided * through the {@link AssistDataRequesterCallbacks}. * - * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String, - * boolean)}. + * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, int, + * String, boolean)}. */ public void requestAutofillData(List<IBinder> activityTokens, int callingUid, String callingPackage) { requestData(activityTokens, true /* requestAutofillData */, true /* fetchData */, false /* fetchScreenshot */, - true /* allowFetchData */, false /* allowFetchScreenshot */, - false /* ignoreTopActivityCheck */, callingUid, callingPackage); + true /* fetchStructure */, true /* allowFetchData */, + false /* allowFetchScreenshot */, false /* ignoreTopActivityCheck */, callingUid, + callingPackage); } /** * Request that assist data be loaded asynchronously. The resulting data will be provided * through the {@link AssistDataRequesterCallbacks}. * - * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String, - * boolean)}. + * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, int, + * String, boolean)}. */ public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData, final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot, int callingUid, String callingPackage) { - requestAssistData(activityTokens, fetchData, fetchScreenshot, allowFetchData, - allowFetchScreenshot, false /* ignoreTopActivityCheck */, callingUid, - callingPackage); + requestAssistData(activityTokens, fetchData, fetchScreenshot, true /* fetchStructure */, + allowFetchData, allowFetchScreenshot, false /* ignoreTopActivityCheck */, + callingUid, callingPackage); } /** * Request that assist data be loaded asynchronously. The resulting data will be provided * through the {@link AssistDataRequesterCallbacks}. * - * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String, - * boolean)}. + * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, int, + * String, boolean)}. */ public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData, - final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot, - boolean ignoreTopActivityCheck, int callingUid, String callingPackage) { + final boolean fetchScreenshot, final boolean fetchStructure, boolean allowFetchData, + boolean allowFetchScreenshot, boolean ignoreTopActivityCheck, int callingUid, + String callingPackage) { requestData(activityTokens, false /* requestAutofillData */, fetchData, fetchScreenshot, - allowFetchData, allowFetchScreenshot, ignoreTopActivityCheck, callingUid, - callingPackage); + fetchStructure, allowFetchData, allowFetchScreenshot, ignoreTopActivityCheck, + callingUid, callingPackage); } /** @@ -197,6 +200,8 @@ public class AssistDataRequester extends IAssistDataReceiver.Stub { * @param fetchScreenshot whether or not to fetch the screenshot, only applies if fetchData is * true, the caller is allowed to fetch the assist data, and the current activity allows * assist data to be fetched from it + * @param fetchStructure whether or not to fetch the AssistStructure along with the + * AssistContent * @param allowFetchData to be joined with other checks, determines whether or not the requester * is allowed to fetch the assist data * @param allowFetchScreenshot to be joined with other checks, determines whether or not the @@ -205,9 +210,9 @@ public class AssistDataRequester extends IAssistDataReceiver.Stub { * making the request. Used when passing an activity from Recents. */ private void requestData(List<IBinder> activityTokens, final boolean requestAutofillData, - final boolean fetchData, final boolean fetchScreenshot, boolean allowFetchData, - boolean allowFetchScreenshot, boolean ignoreTopActivityCheck, int callingUid, - String callingPackage) { + final boolean fetchData, final boolean fetchScreenshot, final boolean fetchStructure, + boolean allowFetchData, boolean allowFetchScreenshot, boolean ignoreTopActivityCheck, + int callingUid, String callingPackage) { // TODO(b/34090158): Known issue, if the assist data is not allowed on the current activity, // then no assist data is requested for any of the other activities @@ -246,13 +251,18 @@ public class AssistDataRequester extends IAssistDataReceiver.Stub { Bundle receiverExtras = new Bundle(); receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i); receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, numActivities); - boolean result = requestAutofillData - ? mActivityTaskManager.requestAutofillData(this, - receiverExtras, topActivity, 0 /* flags */) - : mActivityTaskManager.requestAssistContextExtras( - ASSIST_CONTEXT_FULL, this, receiverExtras, topActivity, + boolean result; + if (requestAutofillData) { + result = mActivityTaskManager.requestAutofillData(this, receiverExtras, + topActivity, 0 /* flags */); + } else { + int requestType = fetchStructure ? ASSIST_CONTEXT_FULL : + ASSIST_CONTEXT_CONTENT; + result = mActivityTaskManager.requestAssistContextExtras( + requestType, this, receiverExtras, topActivity, /* checkActivityIsTop= */ (i == 0) && !ignoreTopActivityCheck, /* newSessionId= */ i == 0); + } if (result) { mPendingDataCount++; } else if (i == 0) { diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index dbb2f65b0680..dd1ddd73a84f 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -109,6 +109,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean fgWaiting; // is a timeout for going foreground already scheduled? boolean isNotAppComponentUsage; // is service binding not considered component/package usage? boolean isForeground; // is service currently in foreground mode? + boolean mLogEntering; // need to report fgs transition once deferral policy is known int foregroundId; // Notification ID of last foreground req. Notification foregroundNoti; // Notification record of foreground state. long fgDisplayTime; // time at which the FGS notification should become visible diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java index f0e45978c365..879c8a0317d7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java @@ -113,6 +113,19 @@ public class UdfpsHelper { } } + public static void onAcquiredGood(int sensorId, + @Nullable IUdfpsOverlayController udfpsOverlayController) { + if (udfpsOverlayController == null) { + return; + } + + try { + udfpsOverlayController.onAcquiredGood(sensorId); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when sending onAcquiredGood", e); + } + } + public static void onEnrollmentProgress(int sensorId, int remaining, @Nullable IUdfpsOverlayController udfpsOverlayController) { if (udfpsOverlayController == null) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 45842677609c..a5326b352264 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -22,6 +22,7 @@ import android.app.TaskStackListener; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; +import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.fingerprint.ISession; @@ -81,6 +82,17 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp } @Override + public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) { + // For UDFPS, notify SysUI that the illumination can be turned off. + // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE + if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) { + UdfpsHelper.onAcquiredGood(getSensorId(), mUdfpsOverlayController); + } + + super.onAcquired(acquiredInfo, vendorCode); + } + + @Override public void onError(int errorCode, int vendorCode) { super.onError(errorCode, vendorCode); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index ed886fe166e0..125cfd2e134d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; +import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.fingerprint.ISession; @@ -85,14 +86,19 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { } } - @Override - public void onAcquired(int acquiredInfo, int vendorCode) { - super.onAcquired(acquiredInfo, vendorCode); + public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) { + // For UDFPS, notify SysUI that the illumination can be turned off. + // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE + if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) { + UdfpsHelper.onAcquiredGood(getSensorId(), mUdfpsOverlayController); + } if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) { UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController); } + + super.onAcquired(acquiredInfo, vendorCode); } @Override diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java index a34d7226136a..70a222fb09c5 100644 --- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java @@ -35,17 +35,17 @@ public class SystemEmergencyHelper extends EmergencyHelper { private final Context mContext; - private TelephonyManager mTelephonyManager; + TelephonyManager mTelephonyManager; - private boolean mIsInEmergencyCall; - private long mEmergencyCallEndRealtimeMs = Long.MIN_VALUE; + boolean mIsInEmergencyCall; + long mEmergencyCallEndRealtimeMs = Long.MIN_VALUE; public SystemEmergencyHelper(Context context) { mContext = context; } /** Called when system is ready. */ - public void onSystemReady() { + public synchronized void onSystemReady() { if (mTelephonyManager != null) { return; } @@ -64,14 +64,20 @@ public class SystemEmergencyHelper extends EmergencyHelper { return; } - mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber( - intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)); + synchronized (SystemEmergencyHelper.this) { + mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber( + intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)); + } } }, new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL)); } @Override - public boolean isInEmergency(long extensionTimeMs) { + public synchronized boolean isInEmergency(long extensionTimeMs) { + if (mTelephonyManager == null) { + return false; + } + boolean isInExtensionTime = mEmergencyCallEndRealtimeMs != Long.MIN_VALUE && (SystemClock.elapsedRealtime() - mEmergencyCallEndRealtimeMs) < extensionTimeMs; @@ -84,12 +90,16 @@ public class SystemEmergencyHelper extends EmergencyHelper { private class EmergencyCallTelephonyCallback extends TelephonyCallback implements TelephonyCallback.CallStateListener{ + EmergencyCallTelephonyCallback() {} + @Override public void onCallStateChanged(int state) { if (state == TelephonyManager.CALL_STATE_IDLE) { - if (mIsInEmergencyCall) { - mEmergencyCallEndRealtimeMs = SystemClock.elapsedRealtime(); - mIsInEmergencyCall = false; + synchronized (SystemEmergencyHelper.this) { + if (mIsInEmergencyCall) { + mEmergencyCallEndRealtimeMs = SystemClock.elapsedRealtime(); + mIsInEmergencyCall = false; + } } } } diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 6d7f79250715..4f8b87b51294 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -1527,16 +1527,16 @@ public class LocationProviderManager extends throw new IllegalArgumentException(mName + " provider is not a test provider"); } + String locationProvider = location.getProvider(); + if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) { + // The location has an explicit provider that is different from the mock + // provider name. The caller may be trying to fool us via b/33091107. + EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), + mName + "!=" + locationProvider); + } + final long identity = Binder.clearCallingIdentity(); try { - String locationProvider = location.getProvider(); - if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) { - // The location has an explicit provider that is different from the mock - // provider name. The caller may be trying to fool us via b/33091107. - EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), - mName + "!=" + locationProvider); - } - mProvider.setMockProviderLocation(location); } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 5b03989f5248..0bec09cd003f 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -105,6 +105,7 @@ import android.security.keystore2.AndroidKeyStoreLoadStoreParameter; import android.security.keystore2.AndroidKeyStoreProvider; import android.service.gatekeeper.GateKeeperResponse; import android.service.gatekeeper.IGateKeeperService; +import android.system.keystore2.Domain; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -254,8 +255,7 @@ public class LockSettingsService extends ILockSettings.Stub { * The UIDs that are used for system credential storage in keystore. */ private static final int[] SYSTEM_CREDENTIAL_UIDS = { - Process.WIFI_UID, Process.VPN_UID, - Process.ROOT_UID, Process.SYSTEM_UID }; + Process.VPN_UID, Process.ROOT_UID, Process.SYSTEM_UID}; // This class manages life cycle events for encrypted users on File Based Encryption (FBE) // devices. The most basic of these is to show/hide notifications about missing features until @@ -2123,9 +2123,14 @@ public class LockSettingsService extends ILockSettings.Stub { // Clear all the users credentials could have been installed in for this user. for (int profileId : mUserManager.getProfileIdsWithDisabled(userId)) { for (int uid : SYSTEM_CREDENTIAL_UIDS) { - mKeyStore.clearUid(UserHandle.getUid(profileId, uid)); + AndroidKeyStoreMaintenance.clearNamespace(Domain.APP, + UserHandle.getUid(profileId, uid)); } } + if (mUserManager.getUserInfo(userId).isPrimary()) { + AndroidKeyStoreMaintenance.clearNamespace(Domain.SELINUX, + KeyProperties.NAMESPACE_WIFI); + } } finally { if (managedUserId != -1 && managedUserDecryptedPassword != null) { if (DEBUG) Slog.v(TAG, "Restore tied profile lock"); diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index 162c388dadd1..607218e20ea8 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -56,13 +56,17 @@ public class MediaSession2Record implements MediaSessionRecordImpl { public MediaSession2Record(Session2Token sessionToken, MediaSessionService service, Looper handlerLooper, int policies) { - mSessionToken = sessionToken; - mService = service; - mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper)); - mController = new MediaController2.Builder(service.getContext(), sessionToken) - .setControllerCallback(mHandlerExecutor, new Controller2Callback()) - .build(); - mPolicies = policies; + // The lock is required to prevent `Controller2Callback` from using partially initialized + // `MediaSession2Record.this`. + synchronized (mLock) { + mSessionToken = sessionToken; + mService = service; + mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper)); + mController = new MediaController2.Builder(service.getContext(), sessionToken) + .setControllerCallback(mHandlerExecutor, new Controller2Callback()) + .build(); + mPolicies = policies; + } } @Override @@ -176,10 +180,12 @@ public class MediaSession2Record implements MediaSessionRecordImpl { if (DEBUG) { Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands); } + MediaSessionService service; synchronized (mLock) { mIsConnected = true; + service = mService; } - mService.onSessionActiveStateChanged(MediaSession2Record.this); + service.onSessionActiveStateChanged(MediaSession2Record.this); } @Override @@ -187,10 +193,12 @@ public class MediaSession2Record implements MediaSessionRecordImpl { if (DEBUG) { Log.d(TAG, "disconnected from " + mSessionToken); } + MediaSessionService service; synchronized (mLock) { mIsConnected = false; + service = mService; } - mService.onSessionDied(MediaSession2Record.this); + service.onSessionDied(MediaSession2Record.this); } @Override @@ -199,7 +207,11 @@ public class MediaSession2Record implements MediaSessionRecordImpl { Log.d(TAG, "playback active changed, " + mSessionToken + ", active=" + playbackActive); } - mService.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive); + MediaSessionService service; + synchronized (mLock) { + service = mService; + } + service.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive); } } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 8c1fd3693d06..cfd575080d97 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -99,8 +99,12 @@ import static android.net.NetworkPolicyManager.resolveNetworkId; import static android.net.NetworkPolicyManager.uidPoliciesToString; import static android.net.NetworkPolicyManager.uidRulesToString; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; +import static android.net.NetworkStats.METERED_ALL; +import static android.net.NetworkStats.METERED_YES; +import static android.net.NetworkTemplate.MATCH_CARRIER; import static android.net.NetworkTemplate.MATCH_MOBILE; import static android.net.NetworkTemplate.MATCH_WIFI; +import static android.net.NetworkTemplate.buildTemplateCarrierMetered; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; @@ -349,7 +353,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int VERSION_SWITCH_UID = 10; private static final int VERSION_ADDED_CYCLE = 11; private static final int VERSION_ADDED_NETWORK_TYPES = 12; - private static final int VERSION_LATEST = VERSION_ADDED_NETWORK_TYPES; + private static final int VERSION_SUPPORTED_CARRIER_USAGE = 13; + private static final int VERSION_LATEST = VERSION_SUPPORTED_CARRIER_USAGE; @VisibleForTesting public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING; @@ -374,7 +379,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final String ATTR_RESTRICT_BACKGROUND = "restrictBackground"; private static final String ATTR_NETWORK_TEMPLATE = "networkTemplate"; private static final String ATTR_SUBSCRIBER_ID = "subscriberId"; + private static final String ATTR_SUBSCRIBER_ID_MATCH_RULE = "subscriberIdMatchRule"; private static final String ATTR_NETWORK_ID = "networkId"; + private static final String ATTR_TEMPLATE_METERED = "templateMetered"; @Deprecated private static final String ATTR_CYCLE_DAY = "cycleDay"; @Deprecated private static final String ATTR_CYCLE_TIMEZONE = "cycleTimezone"; private static final String ATTR_CYCLE_START = "cycleStart"; @@ -1451,7 +1458,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { */ @GuardedBy("mNetworkPoliciesSecondLock") private int findRelevantSubIdNL(NetworkTemplate template) { - // Mobile template is relevant when any active subscriber matches + // Carrier template is relevant when any active subscriber matches for (int i = 0; i < mSubIdToSubscriberId.size(); i++) { final int subId = mSubIdToSubscriberId.keyAt(i); final String subscriberId = mSubIdToSubscriberId.valueAt(i); @@ -1530,6 +1537,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } case TYPE_LIMIT: { switch (policy.template.getMatchRule()) { + case MATCH_CARRIER: case MATCH_MOBILE: title = res.getText(R.string.data_usage_mobile_limit_title); break; @@ -1558,6 +1566,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } case TYPE_LIMIT_SNOOZED: { switch (policy.template.getMatchRule()) { + case MATCH_CARRIER: case MATCH_MOBILE: title = res.getText(R.string.data_usage_mobile_limit_snoozed_title); break; @@ -1655,7 +1664,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { synchronized (mUidRulesFirstLock) { synchronized (mNetworkPoliciesSecondLock) { - ensureActiveMobilePolicyAL(); + ensureActiveCarrierPolicyAL(); normalizePoliciesNL(); updateNetworkEnabledNL(); updateNetworkRulesNL(); @@ -1680,17 +1689,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } /** - * Update mobile policies with data cycle information from {@link CarrierConfigManager} + * Update carrier policies with data cycle information from {@link CarrierConfigManager} * if necessary. * * @param subId that has its associated NetworkPolicy updated if necessary * @return if any policies were updated */ @GuardedBy("mNetworkPoliciesSecondLock") - private boolean maybeUpdateMobilePolicyCycleAL(int subId, String subscriberId) { - if (LOGV) Slog.v(TAG, "maybeUpdateMobilePolicyCycleAL()"); + private boolean maybeUpdateCarrierPolicyCycleAL(int subId, String subscriberId) { + if (LOGV) Slog.v(TAG, "maybeUpdateCarrierPolicyCycleAL()"); - // find and update the mobile NetworkPolicy for this subscriber id + // find and update the carrier NetworkPolicy for this subscriber id boolean policyUpdated = false; final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, true, @@ -1699,21 +1708,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final NetworkTemplate template = mNetworkPolicy.keyAt(i); if (template.matches(probeIdent)) { final NetworkPolicy policy = mNetworkPolicy.valueAt(i); - policyUpdated |= updateDefaultMobilePolicyAL(subId, policy); + policyUpdated |= updateDefaultCarrierPolicyAL(subId, policy); } } return policyUpdated; } /** - * Returns the cycle day that should be used for a mobile NetworkPolicy. + * Returns the cycle day that should be used for a carrier NetworkPolicy. * * It attempts to get an appropriate cycle day from the passed in CarrierConfig. If it's unable * to do so, it returns the fallback value. * * @param config The CarrierConfig to read the value from. * @param fallbackCycleDay to return if the CarrierConfig can't be read. - * @return cycleDay to use in the mobile NetworkPolicy. + * @return cycleDay to use in the carrier NetworkPolicy. */ @VisibleForTesting int getCycleDayFromCarrierConfig(@Nullable PersistableBundle config, @@ -1738,14 +1747,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } /** - * Returns the warning bytes that should be used for a mobile NetworkPolicy. + * Returns the warning bytes that should be used for a carrier NetworkPolicy. * * It attempts to get an appropriate value from the passed in CarrierConfig. If it's unable * to do so, it returns the fallback value. * * @param config The CarrierConfig to read the value from. * @param fallbackWarningBytes to return if the CarrierConfig can't be read. - * @return warningBytes to use in the mobile NetworkPolicy. + * @return warningBytes to use in the carrier NetworkPolicy. */ @VisibleForTesting long getWarningBytesFromCarrierConfig(@Nullable PersistableBundle config, @@ -1771,14 +1780,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } /** - * Returns the limit bytes that should be used for a mobile NetworkPolicy. + * Returns the limit bytes that should be used for a carrier NetworkPolicy. * * It attempts to get an appropriate value from the passed in CarrierConfig. If it's unable * to do so, it returns the fallback value. * * @param config The CarrierConfig to read the value from. * @param fallbackLimitBytes to return if the CarrierConfig can't be read. - * @return limitBytes to use in the mobile NetworkPolicy. + * @return limitBytes to use in the carrier NetworkPolicy. */ @VisibleForTesting long getLimitBytesFromCarrierConfig(@Nullable PersistableBundle config, @@ -1824,8 +1833,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { synchronized (mNetworkPoliciesSecondLock) { final String subscriberId = mSubIdToSubscriberId.get(subId, null); if (subscriberId != null) { - ensureActiveMobilePolicyAL(subId, subscriberId); - maybeUpdateMobilePolicyCycleAL(subId, subscriberId); + ensureActiveCarrierPolicyAL(subId, subscriberId); + maybeUpdateCarrierPolicyCycleAL(subId, subscriberId); } else { Slog.wtf(TAG, "Missing subscriberId for subId " + subId); } @@ -1911,10 +1920,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // TODO: reach into ConnectivityManager to proactively disable bringing // up this network, since we know that traffic will be blocked. - if (template.getMatchRule() == MATCH_MOBILE) { - // If mobile data usage hits the limit or if the user resumes the data, we need to + if (template.getMatchRule() == MATCH_MOBILE + || template.getMatchRule() == MATCH_CARRIER) { + // If carrier data usage hits the limit or if the user resumes the data, we need to // notify telephony. + // TODO: It needs to check if it matches the merged WIFI and notify to wifi module. final IntArray matchingSubIds = new IntArray(); synchronized (mNetworkPoliciesSecondLock) { for (int i = 0; i < mSubIdToSubscriberId.size(); i++) { @@ -2174,7 +2185,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { .truncatedTo(ChronoUnit.DAYS) .toInstant().toEpochMilli(); final long totalBytes = getTotalBytes( - NetworkTemplate.buildTemplateMobileAll(snapshot.getSubscriberId()), + buildTemplateCarrierMetered(snapshot.getSubscriberId()), start, startOfDay); final long remainingBytes = limitBytes - totalBytes; // Number of remaining days including current day @@ -2200,31 +2211,31 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** * Once any {@link #mNetworkPolicy} are loaded from disk, ensure that we - * have at least a default mobile policy defined. + * have at least a default carrier policy defined. */ @GuardedBy("mNetworkPoliciesSecondLock") - private void ensureActiveMobilePolicyAL() { - if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyAL()"); + private void ensureActiveCarrierPolicyAL() { + if (LOGV) Slog.v(TAG, "ensureActiveCarrierPolicyAL()"); if (mSuppressDefaultPolicy) return; for (int i = 0; i < mSubIdToSubscriberId.size(); i++) { final int subId = mSubIdToSubscriberId.keyAt(i); final String subscriberId = mSubIdToSubscriberId.valueAt(i); - ensureActiveMobilePolicyAL(subId, subscriberId); + ensureActiveCarrierPolicyAL(subId, subscriberId); } } /** * Once any {@link #mNetworkPolicy} are loaded from disk, ensure that we - * have at least a default mobile policy defined. + * have at least a default carrier policy defined. * * @param subId to build a default policy for * @param subscriberId that we check for an existing policy - * @return true if a mobile network policy was added, or false one already existed. + * @return true if a carrier network policy was added, or false one already existed. */ @GuardedBy("mNetworkPoliciesSecondLock") - private boolean ensureActiveMobilePolicyAL(int subId, String subscriberId) { + private boolean ensureActiveCarrierPolicyAL(int subId, String subscriberId) { // Poke around to see if we already have a policy final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, true, @@ -2243,7 +2254,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { Slog.i(TAG, "No policy for subscriber " + NetworkIdentityUtils.scrubSubscriberId(subscriberId) + "; generating default policy"); - final NetworkPolicy policy = buildDefaultMobilePolicy(subId, subscriberId); + final NetworkPolicy policy = buildDefaultCarrierPolicy(subId, subscriberId); addNetworkPolicyAL(policy); return true; } @@ -2263,8 +2274,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @VisibleForTesting - NetworkPolicy buildDefaultMobilePolicy(int subId, String subscriberId) { - final NetworkTemplate template = buildTemplateMobileAll(subscriberId); + NetworkPolicy buildDefaultCarrierPolicy(int subId, String subscriberId) { + final NetworkTemplate template = buildTemplateCarrierMetered(subscriberId); final RecurrenceRule cycleRule = NetworkPolicy .buildRule(ZonedDateTime.now().getDayOfMonth(), ZoneId.systemDefault()); final NetworkPolicy policy = new NetworkPolicy(template, cycleRule, @@ -2272,7 +2283,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { SNOOZE_NEVER, SNOOZE_NEVER, true, true); synchronized (mUidRulesFirstLock) { synchronized (mNetworkPoliciesSecondLock) { - updateDefaultMobilePolicyAL(subId, policy); + updateDefaultCarrierPolicyAL(subId, policy); } } return policy; @@ -2286,7 +2297,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * @return if the policy was modified */ @GuardedBy("mNetworkPoliciesSecondLock") - private boolean updateDefaultMobilePolicyAL(int subId, NetworkPolicy policy) { + private boolean updateDefaultCarrierPolicyAL(int subId, NetworkPolicy policy) { if (!policy.inferred) { if (LOGD) Slog.d(TAG, "Ignoring user-defined policy " + policy); return false; @@ -2372,14 +2383,33 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mLoadedRestrictBackground = (version >= VERSION_ADDED_RESTRICT_BACKGROUND) && readBooleanAttribute(in, ATTR_RESTRICT_BACKGROUND); } else if (TAG_NETWORK_POLICY.equals(tag)) { - final int networkTemplate = readIntAttribute(in, ATTR_NETWORK_TEMPLATE); + int templateType = readIntAttribute(in, ATTR_NETWORK_TEMPLATE); final String subscriberId = in.getAttributeValue(null, ATTR_SUBSCRIBER_ID); final String networkId; + final int subscriberIdMatchRule; + final int templateMeteredness; if (version >= VERSION_ADDED_NETWORK_ID) { networkId = in.getAttributeValue(null, ATTR_NETWORK_ID); } else { networkId = null; } + + if (version >= VERSION_SUPPORTED_CARRIER_USAGE) { + subscriberIdMatchRule = readIntAttribute(in, + ATTR_SUBSCRIBER_ID_MATCH_RULE); + templateMeteredness = readIntAttribute(in, ATTR_TEMPLATE_METERED); + + } else { + subscriberIdMatchRule = NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT; + if (templateType == MATCH_MOBILE) { + Log.d(TAG, "Update template match rule from mobile to carrier and" + + " force to metered"); + templateType = MATCH_CARRIER; + templateMeteredness = METERED_YES; + } else { + templateMeteredness = METERED_ALL; + } + } final RecurrenceRule cycleRule; if (version >= VERSION_ADDED_CYCLE) { final String start = readStringAttribute(in, ATTR_CYCLE_START); @@ -2413,7 +2443,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (version >= VERSION_ADDED_METERED) { metered = readBooleanAttribute(in, ATTR_METERED); } else { - switch (networkTemplate) { + switch (templateType) { case MATCH_MOBILE: metered = true; break; @@ -2433,9 +2463,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } else { inferred = false; } - - final NetworkTemplate template = new NetworkTemplate(networkTemplate, - subscriberId, networkId); + final NetworkTemplate template = new NetworkTemplate(templateType, + subscriberId, new String[] { subscriberId }, + networkId, templateMeteredness, NetworkStats.ROAMING_ALL, + NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL, + NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule); if (template.isPersistable()) { mNetworkPolicy.put(template, new NetworkPolicy(template, cycleRule, warningBytes, limitBytes, lastWarningSnooze, @@ -2642,10 +2674,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (subscriberId != null) { out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId); } + writeIntAttribute(out, ATTR_SUBSCRIBER_ID_MATCH_RULE, + template.getSubscriberIdMatchRule()); final String networkId = template.getNetworkId(); if (networkId != null) { out.attribute(null, ATTR_NETWORK_ID, networkId); } + writeIntAttribute(out, ATTR_TEMPLATE_METERED, + template.getMeteredness()); writeStringAttribute(out, ATTR_CYCLE_START, RecurrenceRule.convertZonedDateTime(policy.cycleRule.start)); writeStringAttribute(out, ATTR_CYCLE_END, @@ -3513,8 +3549,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final String subscriberId = mSubIdToSubscriberId.get(subId, null); if (subscriberId != null) { - ensureActiveMobilePolicyAL(subId, subscriberId); - maybeUpdateMobilePolicyCycleAL(subId, subscriberId); + ensureActiveCarrierPolicyAL(subId, subscriberId); + maybeUpdateCarrierPolicyCycleAL(subId, subscriberId); } else { Slog.wtf(TAG, "Missing subscriberId for subId " + subId); } @@ -5575,11 +5611,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return; } - // Turn mobile data limit off + // Turn carrier/mobile data limit off NetworkPolicy[] policies = getNetworkPolicies(mContext.getOpPackageName()); - NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriber); + NetworkTemplate templateCarrier = buildTemplateCarrierMetered(subscriber); + NetworkTemplate templateMobile = buildTemplateMobileAll(subscriber); for (NetworkPolicy policy : policies) { - if (policy.template.equals(template)) { + // All policies loaded from disk will be carrier templates, and setting will also only + // set carrier templates, but we clear mobile templates just in case one is set by + // some other caller + if (policy.template.equals(templateCarrier) || policy.template.equals(templateMobile)) { policy.limitBytes = NetworkPolicy.LIMIT_DISABLED; policy.inferred = false; policy.clearSnooze(); diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index dc9839c6da0e..0528b95d1a6e 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -30,6 +30,9 @@ public interface NotificationManagerInternal { void cancelNotification(String pkg, String basePkg, int callingUid, int callingPid, String tag, int id, int userId); + /** is the given notification currently showing? */ + boolean isNotificationShown(String pkg, String tag, int notificationId, int userId); + void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId); void onConversationRemoved(String pkg, int uid, Set<String> shortcuts); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0dd9b292b386..e24c4af7026e 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -130,6 +130,7 @@ import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityManagerInternal.ServiceNotificationPolicy; import android.app.AlarmManager; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -211,9 +212,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.os.VibrationAttributes; import android.os.VibrationEffect; -import android.os.Vibrator; import android.provider.DeviceConfig; import android.provider.Settings; import android.service.notification.Adjustment; @@ -369,12 +368,8 @@ public class NotificationManagerService extends SystemService { // 1 second past the ANR timeout. static final int FINISH_TOKEN_TIMEOUT = 11 * 1000; - static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; - static final long SNOOZE_UNTIL_UNSPECIFIED = -1; - static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps - static final int INVALID_UID = -1; static final String ROOT_PKG = "root"; @@ -482,7 +477,6 @@ public class NotificationManagerService extends SystemService { AudioManagerInternal mAudioManagerInternal; // Can be null for wear @Nullable StatusBarManagerInternal mStatusBar; - Vibrator mVibrator; private WindowManagerInternal mWindowManagerInternal; private AlarmManager mAlarmManager; private ICompanionDeviceManager mCompanionManager; @@ -504,7 +498,6 @@ public class NotificationManagerService extends SystemService { private LogicalLight mNotificationLight; LogicalLight mAttentionLight; - private long[] mFallbackVibrationPattern; private boolean mUseAttentionLight; boolean mHasLight = true; boolean mLightEnabled; @@ -583,6 +576,7 @@ public class NotificationManagerService extends SystemService { RankingHelper mRankingHelper; @VisibleForTesting PreferencesHelper mPreferencesHelper; + private VibratorHelper mVibratorHelper; private final UserProfiles mUserProfiles = new UserProfiles(); private NotificationListeners mListeners; @@ -1597,10 +1591,7 @@ public class NotificationManagerService extends SystemService { mVibrateNotificationKey = null; final long identity = Binder.clearCallingIdentity(); try { - // Stop all vibrations with usage of class alarm (ringtone, alarm, notification usages). - int usageFilter = - VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK; - mVibrator.cancel(usageFilter); + mVibratorHelper.cancelVibration(); } finally { Binder.restoreCallingIdentity(identity); } @@ -1994,19 +1985,6 @@ public class NotificationManagerService extends SystemService { private SettingsObserver mSettingsObserver; protected ZenModeHelper mZenModeHelper; - static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) { - int[] ar = r.getIntArray(resid); - if (ar == null) { - return def; - } - final int len = ar.length > maxlen ? maxlen : ar.length; - long[] out = new long[len]; - for (int i=0; i<len; i++) { - out[i] = ar[i]; - } - return out; - } - public NotificationManagerService(Context context) { this(context, new NotificationRecordLoggerImpl(), @@ -2040,13 +2018,18 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting - void setHints(int hints) { - mListenerHints = hints; + VibratorHelper getVibratorHelper() { + return mVibratorHelper; } @VisibleForTesting - void setVibrator(Vibrator vibrator) { - mVibrator = vibrator; + void setVibratorHelper(VibratorHelper helper) { + mVibratorHelper = helper; + } + + @VisibleForTesting + void setHints(int hints) { + mListenerHints = hints; } @VisibleForTesting @@ -2121,11 +2104,6 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting - void setFallbackVibrationPattern(long[] vibrationPattern) { - mFallbackVibrationPattern = vibrationPattern; - } - - @VisibleForTesting void setPackageManager(IPackageManager packageManager) { mPackageManager = packageManager; } @@ -2199,7 +2177,6 @@ public class NotificationManagerService extends SystemService { mPackageManager = packageManager; mPackageManagerClient = packageManagerClient; mAppOps = appOps; - mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); mAppUsageStats = appUsageStats; mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mCompanionManager = companionManager; @@ -2287,6 +2264,7 @@ public class NotificationManagerService extends SystemService { extractorNames); mSnoozeHelper = snoozeHelper; mGroupHelper = groupHelper; + mVibratorHelper = new VibratorHelper(getContext()); mHistoryManager = historyManager; // This is a ManagedServices object that keeps track of the listeners. @@ -2308,10 +2286,6 @@ public class NotificationManagerService extends SystemService { mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS); mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); - mFallbackVibrationPattern = getLongArray(resources, - R.array.config_notificationFallbackVibePattern, - VIBRATE_PATTERN_MAXLEN, - DEFAULT_VIBRATE_PATTERN); mInCallNotificationUri = Uri.parse("file://" + resources.getString(R.string.config_inCallNotificationSound)); mInCallNotificationAudioAttributes = new AudioAttributes.Builder() @@ -3078,6 +3052,20 @@ public class NotificationManagerService extends SystemService { } } + protected void maybeReportForegroundServiceUpdate(final NotificationRecord r) { + if (r.isForegroundService()) { + // snapshot live state for the asynchronous operation + final StatusBarNotification sbn = r.getSbn(); + final Notification notification = sbn.getNotification(); + final int id = sbn.getId(); + final String pkg = sbn.getPackageName(); + final int userId = sbn.getUser().getIdentifier(); + mHandler.post(() -> { + mAmi.onForegroundServiceNotificationUpdate(notification, id, pkg, userId); + }); + } + } + private String getHistoryTitle(Notification n) { CharSequence title = null; if (n.extras != null) { @@ -5070,7 +5058,7 @@ public class NotificationManagerService extends SystemService { } @Override - public boolean isNotificationPolicyAccessGrantedForPackage(String pkg) {; + public boolean isNotificationPolicyAccessGrantedForPackage(String pkg) { enforceSystemOrSystemUIOrSamePackage(pkg, "request policy access status for another package"); return checkPolicyAccess(pkg); @@ -5725,9 +5713,7 @@ public class NotificationManagerService extends SystemService { summaryNotification.extras.putAll(extras); Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg); if (appIntent != null) { - final ActivityManagerInternal ami = LocalServices - .getService(ActivityManagerInternal.class); - summaryNotification.contentIntent = ami.getPendingIntentActivityAsApp( + summaryNotification.contentIntent = mAmi.getPendingIntentActivityAsApp( 0, appIntent, PendingIntent.FLAG_IMMUTABLE, null, pkg, appInfo.uid); } @@ -5778,7 +5764,7 @@ public class NotificationManagerService extends SystemService { return "callState"; } return null; - }; + } private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter) { JSONObject dump = new JSONObject(); @@ -6059,6 +6045,11 @@ public class NotificationManagerService extends SystemService { } @Override + public boolean isNotificationShown(String pkg, String tag, int notificationId, int userId) { + return isNotificationShownInternal(pkg, tag, notificationId, userId); + } + + @Override public void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId) { checkCallerIsSystem(); @@ -6141,16 +6132,22 @@ public class NotificationManagerService extends SystemService { mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null); } + boolean isNotificationShownInternal(String pkg, String tag, int notificationId, int userId) { + synchronized (mNotificationLock) { + return findNotificationLocked(pkg, tag, notificationId, userId) != null; + } + } + void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId) { enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, - incomingUserId, false); + incomingUserId, false); } void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, - final int callingPid, final String tag, final int id, final Notification notification, - int incomingUserId, boolean postSilently) { + final int callingPid, final String tag, final int id, final Notification notification, + int incomingUserId, boolean postSilently) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); @@ -6185,6 +6182,22 @@ public class NotificationManagerService extends SystemService { return; } + // Notifications passed to setForegroundService() have FLAG_FOREGROUND_SERVICE, + // but it's also possible that the app has called notify() with an update to an + // FGS notification that hasn't yet been displayed. Make sure we check for any + // FGS-related situation up front, outside of any locks so it's safe to call into + // the Activity Manager. + final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification( + notification, id, pkg, userId); + if (policy == ServiceNotificationPolicy.UPDATE_ONLY) { + // Proceed if the notification is already showing/known, otherwise ignore + // because the service lifecycle logic has retained responsibility for its + // handling. + if (!isNotificationShownInternal(pkg, tag, id, userId)) { + return; + } + } + mUsageStats.registerEnqueuedByApp(pkg); final StatusBarNotification n = new StatusBarNotification( @@ -6287,19 +6300,17 @@ public class NotificationManagerService extends SystemService { if (notification.allPendingIntents != null) { final int intentCount = notification.allPendingIntents.size(); if (intentCount > 0) { - final ActivityManagerInternal am = LocalServices - .getService(ActivityManagerInternal.class); final long duration = LocalServices.getService( DeviceIdleInternal.class).getNotificationAllowlistDuration(); for (int i = 0; i < intentCount; i++) { PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i); if (pendingIntent != null) { - am.setPendingIntentAllowlistDuration(pendingIntent.getTarget(), + mAmi.setPendingIntentAllowlistDuration(pendingIntent.getTarget(), ALLOWLIST_TOKEN, duration, TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_NOTIFICATION_SERVICE, "NotificationManagerService"); - am.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(), + mAmi.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(), ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER)); } @@ -6568,15 +6579,14 @@ public class NotificationManagerService extends SystemService { } // bubble or inline reply that's immutable? - // TODO (b/171418004): renable after app outreach - /*if (n.getBubbleMetadata() != null + if (n.getBubbleMetadata() != null && n.getBubbleMetadata().getIntent() != null && hasFlag(mAmi.getPendingIntentFlags( n.getBubbleMetadata().getIntent().getTarget()), PendingIntent.FLAG_IMMUTABLE)) { throw new IllegalArgumentException(r.getKey() + " Not posted." + " PendingIntents attached to bubbles must be mutable"); - }*/ + } if (n.actions != null) { for (Notification.Action action : n.actions) { @@ -6821,12 +6831,14 @@ public class NotificationManagerService extends SystemService { mUsageStats.registerClickedByUser(r); } - if (mReason == REASON_LISTENER_CANCEL - && r.getNotification().isBubbleNotification()) { + if ((mReason == REASON_LISTENER_CANCEL + && r.getNotification().isBubbleNotification()) + || (mReason == REASON_CLICK && r.canBubble() + && r.isFlagBubbleRemoved())) { boolean isBubbleSuppressed = r.getNotification().getBubbleMetadata() != null && r.getNotification().getBubbleMetadata().isBubbleSuppressed(); mNotificationDelegate.onBubbleNotificationSuppressionChanged( - r.getKey(), true /* suppressed */, isBubbleSuppressed); + r.getKey(), true /* notifSuppressed */, isBubbleSuppressed); return; } @@ -7108,6 +7120,7 @@ public class NotificationManagerService extends SystemService { maybeRecordInterruptionLocked(r); maybeRegisterMessageSent(r); + maybeReportForegroundServiceUpdate(r); // Log event to statsd mNotificationRecordLogger.maybeLogNotificationPosted(r, old, position, @@ -7398,7 +7411,7 @@ public class NotificationManagerService extends SystemService { if (mSystemReady && mAudioManager != null) { Uri soundUri = record.getSound(); hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri); - long[] vibration = record.getVibration(); + VibrationEffect vibration = record.getVibration(); // Demote sound to vibration if vibration missing & phone in vibration mode. if (vibration == null && hasValidSound @@ -7406,7 +7419,8 @@ public class NotificationManagerService extends SystemService { == AudioManager.RINGER_MODE_VIBRATE) && mAudioManager.getStreamVolume( AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) { - vibration = mFallbackVibrationPattern; + boolean insistent = (record.getFlags() & Notification.FLAG_INSISTENT) != 0; + vibration = mVibratorHelper.createFallbackVibration(insistent); } hasValidVibrate = vibration != null; boolean hasAudibleAlert = hasValidSound || hasValidVibrate; @@ -7633,23 +7647,12 @@ public class NotificationManagerService extends SystemService { return false; } - private boolean playVibration(final NotificationRecord record, long[] vibration, + private boolean playVibration(final NotificationRecord record, final VibrationEffect effect, boolean delayVibForSound) { // Escalate privileges so we can use the vibrator even if the // notifying app does not have the VIBRATE permission. final long identity = Binder.clearCallingIdentity(); try { - final VibrationEffect effect; - try { - final boolean insistent = - (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0; - effect = VibrationEffect.createWaveform( - vibration, insistent ? 0 : -1 /*repeatIndex*/); - } catch (IllegalArgumentException e) { - Slog.e(TAG, "Error creating vibration waveform with pattern: " + - Arrays.toString(vibration)); - return false; - } if (delayVibForSound) { new Thread(() -> { // delay the vibration by the same amount as the notification sound @@ -7686,8 +7689,7 @@ public class NotificationManagerService extends SystemService { // to the reason so we can still debug from bugreports String reason = "Notification (" + record.getSbn().getOpPkg() + " " + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : ""); - mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME, - effect, reason, record.getAudioAttributes()); + mVibratorHelper.vibrate(effect, record.getAudioAttributes(), reason); } private boolean isNotificationForCurrentUser(NotificationRecord record) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index e87506544188..f66cfa99ce98 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -47,6 +47,7 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.UserHandle; +import android.os.VibrationEffect; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.NotificationListenerService; @@ -158,7 +159,7 @@ public final class NotificationRecord { private String mUserExplanation; private boolean mPreChannelsNotification = true; private Uri mSound; - private long[] mVibration; + private VibrationEffect mVibration; private AudioAttributes mAttributes; private NotificationChannel mChannel; private ArrayList<String> mPeopleOverride; @@ -287,29 +288,28 @@ public final class NotificationRecord { return light; } - private long[] calculateVibration() { - long[] vibration; - final long[] defaultVibration = NotificationManagerService.getLongArray( - mContext.getResources(), - com.android.internal.R.array.config_defaultNotificationVibePattern, - NotificationManagerService.VIBRATE_PATTERN_MAXLEN, - NotificationManagerService.DEFAULT_VIBRATE_PATTERN); + private VibrationEffect calculateVibration() { + VibratorHelper helper = new VibratorHelper(mContext); + final Notification notification = getSbn().getNotification(); + final boolean insistent = (notification.flags & Notification.FLAG_INSISTENT) != 0; + VibrationEffect defaultVibration = helper.createDefaultVibration(insistent); + VibrationEffect vibration; if (getChannel().shouldVibrate()) { vibration = getChannel().getVibrationPattern() == null - ? defaultVibration : getChannel().getVibrationPattern(); + ? defaultVibration + : helper.createWaveformVibration(getChannel().getVibrationPattern(), insistent); } else { vibration = null; } if (mPreChannelsNotification && (getChannel().getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0) { - final Notification notification = getSbn().getNotification(); final boolean useDefaultVibrate = (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; if (useDefaultVibrate) { vibration = defaultVibration; } else { - vibration = notification.vibrate; + vibration = helper.createWaveformVibration(notification.vibrate, insistent); } } return vibration; @@ -877,6 +877,10 @@ public final class NotificationRecord { return mHidden; } + public boolean isForegroundService() { + return 0 != (getFlags() & Notification.FLAG_FOREGROUND_SERVICE); + } + /** * Override of all alerting information on the channel and notification. Used when notifications * are reposted in response to direct user action and thus don't need to alert. @@ -1067,7 +1071,7 @@ public final class NotificationRecord { return mSound; } - public long[] getVibration() { + public VibrationEffect getVibration() { return mVibration; } diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java new file mode 100644 index 000000000000..f25b0470cc8a --- /dev/null +++ b/services/core/java/com/android/server/notification/VibratorHelper.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.media.AudioAttributes; +import android.os.Process; +import android.os.VibrationAttributes; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.util.Slog; + +import com.android.internal.R; +import com.android.server.pm.PackageManagerService; + +import java.util.Arrays; + +/** + * NotificationManagerService helper for functionality related to the vibrator. + */ +public final class VibratorHelper { + private static final String TAG = "NotificationVibratorHelper"; + + private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; + private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps + + private final Vibrator mVibrator; + private final long[] mDefaultPattern; + private final long[] mFallbackPattern; + + public VibratorHelper(Context context) { + mVibrator = context.getSystemService(Vibrator.class); + mDefaultPattern = getLongArray( + context.getResources(), + com.android.internal.R.array.config_defaultNotificationVibePattern, + VIBRATE_PATTERN_MAXLEN, + DEFAULT_VIBRATE_PATTERN); + mFallbackPattern = getLongArray(context.getResources(), + R.array.config_notificationFallbackVibePattern, + VIBRATE_PATTERN_MAXLEN, + DEFAULT_VIBRATE_PATTERN); + } + + /** + * Safely create a {@link VibrationEffect} from given vibration {@code pattern}. + * + * <p>This method returns {@code null} if the pattern is also {@code null} or invalid. + * + * @param pattern The off/on vibration pattern, where each item is a duration in milliseconds. + * @param insistent {@code true} if the vibration should loop until it is cancelled. + */ + @Nullable + public static VibrationEffect createWaveformVibration(@Nullable long[] pattern, + boolean insistent) { + try { + if (pattern != null) { + return VibrationEffect.createWaveform(pattern, /* repeat= */ insistent ? 0 : -1); + } + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Error creating vibration waveform with pattern: " + + Arrays.toString(pattern)); + } + return null; + } + + /** + * Vibrate the device with given {@code effect}. + * + * <p>We need to vibrate as "android" so we can breakthrough DND. + */ + public void vibrate(VibrationEffect effect, AudioAttributes attrs, String reason) { + mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME, + effect, reason, attrs); + } + + /** Stop all notification vibrations (ringtone, alarm, notification usages). */ + public void cancelVibration() { + int usageFilter = + VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK; + mVibrator.cancel(usageFilter); + } + + /** + * Creates a vibration to be used as fallback when the device is in vibrate mode. + * + * @param insistent {@code true} if the vibration should loop until it is cancelled. + */ + public VibrationEffect createFallbackVibration(boolean insistent) { + if (mVibrator.hasFrequencyControl()) { + return createChirpVibration(insistent); + } + return createWaveformVibration(mFallbackPattern, insistent); + } + + /** + * Creates a vibration to be used by notifications without a custom pattern. + * + * @param insistent {@code true} if the vibration should loop until it is cancelled. + */ + public VibrationEffect createDefaultVibration(boolean insistent) { + if (mVibrator.hasFrequencyControl()) { + return createChirpVibration(insistent); + } + return createWaveformVibration(mDefaultPattern, insistent); + } + + private static VibrationEffect createChirpVibration(boolean insistent) { + VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform() + .addStep(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 0) + .addRamp(/* amplitude= */ 1, /* frequency= */ -0.25f, /* duration= */ 100) + .addStep(/* amplitude= */ 1, /* duration= */ 150) + .addRamp(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 250); + + if (insistent) { + return waveformBuilder.build(/* repeat= */ 0); + } + + VibrationEffect singleBeat = waveformBuilder.build(); + return VibrationEffect.startComposition() + .addEffect(singleBeat) + .addEffect(singleBeat) + .compose(); + } + + private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) { + int[] ar = resources.getIntArray(resId); + if (ar == null) { + return def; + } + final int len = ar.length > maxLength ? maxLength : ar.length; + long[] out = new long[len]; + for (int i = 0; i < len; i++) { + out[i] = ar[i]; + } + return out; + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2f8ba6da89a3..1e35bf4e511e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1413,8 +1413,8 @@ public class PackageManagerService extends IPackageManager.Stub mStaticLibsByDeclaringPackage = new WatchedArrayMap<>(); private final SnapshotCache<WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>> mStaticLibsByDeclaringPackageSnapshot = - new SnapshotCache.Auto<>(mSharedLibraries, mSharedLibraries, - "PackageManagerService.mSharedLibraries"); + new SnapshotCache.Auto<>(mStaticLibsByDeclaringPackage, mStaticLibsByDeclaringPackage, + "PackageManagerService.mStaticLibsByDeclaringPackage"); // Mapping from instrumentation class names to info about them. @Watched @@ -13224,9 +13224,8 @@ public class PackageManagerService extends IPackageManager.Stub if (!sharedLibraryInfo.isStatic()) { continue; } - final PackageSetting staticLibPkgSetting = getPackageSetting( - toStaticSharedLibraryPackageName(sharedLibraryInfo.getName(), - sharedLibraryInfo.getLongVersion())); + final PackageSetting staticLibPkgSetting = + getPackageSetting(sharedLibraryInfo.getPackageName()); if (staticLibPkgSetting == null) { Slog.wtf(TAG, "Shared lib without setting: " + sharedLibraryInfo); continue; @@ -22726,7 +22725,8 @@ public class PackageManagerService extends IPackageManager.Stub */ public int getPreferredActivitiesInternal(List<WatchedIntentFilter> outFilters, List<ComponentName> outActivities, String packageName) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { + final int callingUid = Binder.getCallingUid(); + if (getInstantAppPackageName(callingUid) != null) { return 0; } int num = 0; @@ -22738,9 +22738,13 @@ public class PackageManagerService extends IPackageManager.Stub final Iterator<PreferredActivity> it = pir.filterIterator(); while (it.hasNext()) { final PreferredActivity pa = it.next(); + final String prefPackageName = pa.mPref.mComponent.getPackageName(); if (packageName == null - || (pa.mPref.mComponent.getPackageName().equals(packageName) - && pa.mPref.mAlways)) { + || (prefPackageName.equals(packageName) && pa.mPref.mAlways)) { + if (shouldFilterApplicationLocked( + mSettings.getPackageLPr(prefPackageName), callingUid, userId)) { + continue; + } if (outFilters != null) { outFilters.add(new WatchedIntentFilter(pa.getIntentFilter())); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 9c25159faef1..ae8f967ade64 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -436,6 +436,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { volatile boolean mPowerKeyHandled; volatile boolean mBackKeyHandled; volatile boolean mEndCallKeyHandled; + volatile boolean mCameraGestureTriggered; volatile boolean mCameraGestureTriggeredDuringGoingToSleep; /** @@ -3833,6 +3834,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { final MutableBoolean outLaunched = new MutableBoolean(false); final boolean gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive, outLaunched); + if (outLaunched.value) { + mCameraGestureTriggered = true; + } if (outLaunched.value && mRequestedOrSleepingDefaultDisplay) { mCameraGestureTriggeredDuringGoingToSleep = true; } @@ -4209,13 +4213,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDefaultDisplayRotation.updateOrientationListener(); if (mKeyguardDelegate != null) { - mKeyguardDelegate.onFinishedGoingToSleep(pmSleepReason, - mCameraGestureTriggeredDuringGoingToSleep); + mKeyguardDelegate.onFinishedGoingToSleep(pmSleepReason, mCameraGestureTriggered); } if (mDisplayFoldController != null) { mDisplayFoldController.finishedGoingToSleep(); } mCameraGestureTriggeredDuringGoingToSleep = false; + mCameraGestureTriggered = false; } // Called on the PowerManager's Notifier thread. @@ -4242,8 +4246,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDefaultDisplayRotation.updateOrientationListener(); if (mKeyguardDelegate != null) { - mKeyguardDelegate.onStartedWakingUp(pmWakeReason); + mKeyguardDelegate.onStartedWakingUp(pmWakeReason, mCameraGestureTriggered); } + + mCameraGestureTriggered = false; } // Called on the PowerManager's Notifier thread. diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 44f14b4d5b0d..cdce660835b6 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -178,7 +178,8 @@ public class KeyguardServiceDelegate { // This is used to hide the scrim once keyguard displays. if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE || mKeyguardState.interactiveState == INTERACTIVE_STATE_WAKING) { - mKeyguardService.onStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); + mKeyguardService.onStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN, + false /* cameraGestureTriggered */); } if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE) { mKeyguardService.onFinishedWakingUp(); @@ -297,10 +298,11 @@ public class KeyguardServiceDelegate { mKeyguardState.dreaming = false; } - public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) { + public void onStartedWakingUp( + @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) { if (mKeyguardService != null) { if (DEBUG) Log.v(TAG, "onStartedWakingUp()"); - mKeyguardService.onStartedWakingUp(pmWakeReason); + mKeyguardService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered); } mKeyguardState.interactiveState = INTERACTIVE_STATE_WAKING; } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index 0872b3a5fd42..855a1ccc172d 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -121,9 +121,10 @@ public class KeyguardServiceWrapper implements IKeyguardService { } @Override - public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) { + public void onStartedWakingUp( + @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) { try { - mService.onStartedWakingUp(pmWakeReason); + mService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } diff --git a/services/core/java/com/android/server/power/FaceDownDetector.java b/services/core/java/com/android/server/power/FaceDownDetector.java index 816c81ddf305..b237ca27e62d 100644 --- a/services/core/java/com/android/server/power/FaceDownDetector.java +++ b/services/core/java/com/android/server/power/FaceDownDetector.java @@ -77,6 +77,8 @@ public class FaceDownDetector implements SensorEventListener { private boolean mIsEnabled; + private int mSensorMaxLatencyMicros; + /** * DeviceConfig flag name, determines how long to disable sensor when user interacts while * device is flipped. @@ -202,7 +204,11 @@ public class FaceDownDetector implements SensorEventListener { if (mActive != shouldBeActive) { if (shouldBeActive) { mSensorManager.registerListener( - this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); + this, + mAccelerometer, + SensorManager.SENSOR_DELAY_NORMAL, + mSensorMaxLatencyMicros + ); if (mPreviousResultType == SCREEN_OFF_RESULT) { logScreenOff(); } @@ -226,6 +232,7 @@ public class FaceDownDetector implements SensorEventListener { pw.println(" mFaceDown=" + mFaceDown); pw.println(" mActive=" + mActive); pw.println(" mLastFlipTime=" + mLastFlipTime); + pw.println(" mSensorMaxLatencyMicros=" + mSensorMaxLatencyMicros); pw.println(" mUserInteractionBackoffMillis=" + mUserInteractionBackoffMillis); pw.println(" mPreviousResultTime=" + mPreviousResultTime); pw.println(" mPreviousResultType=" + mPreviousResultType); @@ -356,6 +363,11 @@ public class FaceDownDetector implements SensorEventListener { 3600_000); } + private int getSensorMaxLatencyMicros() { + return mContext.getResources().getInteger( + com.android.internal.R.integer.config_flipToScreenOffMaxLatencyMicros); + } + private float getFloatFlagValue(String key, float defaultValue, float min, float max) { final float value = DeviceConfig.getFloat(NAMESPACE_ATTENTION_MANAGER_SERVICE, key, @@ -416,6 +428,7 @@ public class FaceDownDetector implements SensorEventListener { mZAccelerationThreshold = getZAccelerationThreshold(); mZAccelerationThresholdLenient = mZAccelerationThreshold + 1.0f; mTimeThreshold = getTimeThreshold(); + mSensorMaxLatencyMicros = getSensorMaxLatencyMicros(); mUserInteractionBackoffMillis = getUserInteractionBackoffMillis(); final boolean oldEnabled = mIsEnabled; mIsEnabled = isEnabled(); diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java new file mode 100644 index 000000000000..fbb6644934f1 --- /dev/null +++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.sensors; + +import android.annotation.NonNull; + +import java.util.concurrent.Executor; + +/** + * Local system service interface for sensors. + * + * @hide Only for use within system server. + */ +public abstract class SensorManagerInternal { + /** + * Adds a listener for changes in proximity sensor state. + * @param executor The {@link Executor} to {@link Executor#execute invoke} the listener on. + * @param listener The listener to add. + * + * @throws IllegalArgumentException when adding a listener that is already listening + */ + public abstract void addProximityActiveListener(@NonNull Executor executor, + @NonNull ProximityActiveListener listener); + + /** + * Removes a previously registered listener of proximity sensor state changes. + * @param listener The listener to remove. + */ + public abstract void removeProximityActiveListener(@NonNull ProximityActiveListener listener); + + /** + * Listener for proximity sensor state changes. + */ + public interface ProximityActiveListener { + /** + * Callback invoked when the proximity sensor state changes + * @param isActive whether the sensor is being enabled or disabled. + */ + void onProximityActive(boolean isActive); + } +} diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java index f7ed8e7138fa..8fe2d52f7160 100644 --- a/services/core/java/com/android/server/sensors/SensorService.java +++ b/services/core/java/com/android/server/sensors/SensorService.java @@ -16,25 +16,41 @@ package com.android.server.sensors; +import static com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; + +import android.annotation.NonNull; import android.content.Context; +import android.util.ArrayMap; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ConcurrentUtils; +import com.android.server.LocalServices; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.utils.TimingsTraceAndSlog; +import java.util.Objects; +import java.util.concurrent.Executor; import java.util.concurrent.Future; public class SensorService extends SystemService { private static final String START_NATIVE_SENSOR_SERVICE = "StartNativeSensorService"; private final Object mLock = new Object(); @GuardedBy("mLock") + private final ArrayMap<ProximityActiveListener, ProximityListenerProxy> mProximityListeners = + new ArrayMap<>(); + @GuardedBy("mLock") private Future<?> mSensorServiceStart; + @GuardedBy("mLock") + private long mPtr; /** Start the sensor service. This is a blocking call and can take time. */ - private static native void startNativeSensorService(); + private static native long startSensorServiceNative(ProximityActiveListener listener); + + private static native void registerProximityActiveListenerNative(long ptr); + private static native void unregisterProximityActiveListenerNative(long ptr); + public SensorService(Context ctx) { super(ctx); @@ -42,14 +58,19 @@ public class SensorService extends SystemService { mSensorServiceStart = SystemServerInitThreadPool.submit(() -> { TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog(); traceLog.traceBegin(START_NATIVE_SENSOR_SERVICE); - startNativeSensorService(); + long ptr = startSensorServiceNative(new ProximityListenerDelegate()); + synchronized (mLock) { + mPtr = ptr; + } traceLog.traceEnd(); }, START_NATIVE_SENSOR_SERVICE); } } @Override - public void onStart() { } + public void onStart() { + LocalServices.addService(SensorManagerInternal.class, new LocalService()); + } @Override public void onBootPhase(int phase) { @@ -61,4 +82,70 @@ public class SensorService extends SystemService { } } } + + class LocalService extends SensorManagerInternal { + @Override + public void addProximityActiveListener(@NonNull Executor executor, + @NonNull ProximityActiveListener listener) { + Objects.requireNonNull(executor, "executor must not be null"); + Objects.requireNonNull(listener, "listener must not be null"); + ProximityListenerProxy proxy = new ProximityListenerProxy(executor, listener); + synchronized (mLock) { + if (mProximityListeners.containsKey(listener)) { + throw new IllegalArgumentException("listener already registered"); + } + mProximityListeners.put(listener, proxy); + if (mProximityListeners.size() == 1) { + registerProximityActiveListenerNative(mPtr); + } + } + } + + @Override + public void removeProximityActiveListener(@NonNull ProximityActiveListener listener) { + Objects.requireNonNull(listener, "listener must not be null"); + synchronized (mLock) { + ProximityListenerProxy proxy = mProximityListeners.remove(listener); + if (proxy == null) { + throw new IllegalArgumentException( + "listener was not registered with sensor service"); + } + if (mProximityListeners.isEmpty()) { + unregisterProximityActiveListenerNative(mPtr); + } + } + } + } + + private static class ProximityListenerProxy implements ProximityActiveListener { + private final Executor mExecutor; + private final ProximityActiveListener mListener; + + ProximityListenerProxy(Executor executor, ProximityActiveListener listener) { + mExecutor = executor; + mListener = listener; + } + + @Override + public void onProximityActive(boolean isActive) { + mExecutor.execute(() -> mListener.onProximityActive(isActive)); + } + } + + private class ProximityListenerDelegate implements ProximityActiveListener { + @Override + public void onProximityActive(boolean isActive) { + final ProximityListenerProxy[] listeners; + // We can't call out while holding the lock because clients might be calling into us + // while holding their own locks (e.g. when registering / unregistering their + // listeners).This would break lock ordering and create deadlocks. Instead, we need to + // copy the listeners out and then only invoke them once we've dropped the lock. + synchronized (mLock) { + listeners = mProximityListeners.values().toArray(new ProximityListenerProxy[0]); + } + for (ProximityListenerProxy listener : listeners) { + listener.onProximityActive(isActive); + } + } + } } diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 753b42b24556..0f37450c24c9 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -25,6 +25,8 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Binder; @@ -59,6 +61,7 @@ import android.view.textclassifier.TextLinks; import android.view.textclassifier.TextSelection; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageMonitor; import com.android.internal.util.DumpUtils; import com.android.internal.util.FunctionalUtils; import com.android.internal.util.FunctionalUtils.ThrowingConsumer; @@ -110,6 +113,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi try { publishBinderService(Context.TEXT_CLASSIFICATION_SERVICE, mManagerService); mManagerService.startListenSettings(); + mManagerService.startTrackingPackageChanges(); } catch (Throwable t) { // Starting this service is not critical to the running of this device and should // therefore not crash the device. If it fails, log the error and continue. @@ -119,11 +123,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi @Override public void onUserStarting(@NonNull TargetUser user) { + updatePackageStateForUser(user.getUserIdentifier()); processAnyPendingWork(user.getUserIdentifier()); } @Override public void onUserUnlocking(@NonNull TargetUser user) { + // refresh if we failed earlier due to locked encrypted user + updatePackageStateForUser(user.getUserIdentifier()); // Rebind if we failed earlier due to locked encrypted user processAnyPendingWork(user.getUserIdentifier()); } @@ -134,6 +141,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } + private void updatePackageStateForUser(int userId) { + synchronized (mManagerService.mLock) { + // Update the cached disable status, the TextClassfier may not be direct boot aware, + // we should update the disable status after user unlock + mManagerService.getUserStateLocked(userId).updatePackageStateLocked(); + } + } + @Override public void onUserStopping(@NonNull TargetUser user) { int userId = user.getUserIdentifier(); @@ -160,6 +175,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi private final String mDefaultTextClassifierPackage; @Nullable private final String mSystemTextClassifierPackage; + // TODO: consider using device config to control it. + private boolean DEBUG = false; private TextClassificationManagerService(Context context) { mContext = Objects.requireNonNull(context); @@ -176,6 +193,46 @@ public final class TextClassificationManagerService extends ITextClassifierServi mSettingsListener.registerObserver(); } + void startTrackingPackageChanges() { + final PackageMonitor monitor = new PackageMonitor() { + + @Override + public void onPackageAdded(String packageName, int uid) { + notifyPackageInstallStatusChange(packageName, /* installed*/ true); + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + notifyPackageInstallStatusChange(packageName, /* installed= */ false); + } + + @Override + public void onPackageModified(String packageName) { + final int userId = getChangingUserId(); + synchronized (mLock) { + final UserState userState = getUserStateLocked(userId); + final ServiceState serviceState = userState.getServiceStateLocked(packageName); + if (serviceState != null) { + serviceState.onPackageModifiedLocked(); + } + } + } + + private void notifyPackageInstallStatusChange(String packageName, boolean installed) { + final int userId = getChangingUserId(); + synchronized (mLock) { + final UserState userState = getUserStateLocked(userId); + final ServiceState serviceState = userState.getServiceStateLocked(packageName); + if (serviceState != null) { + serviceState.onPackageInstallStatusChangeLocked(installed); + } + } + } + }; + + monitor.register(mContext, null, UserHandle.ALL, true); + } + @Override public void onConnectedStateChanged(@ConnectionState int connected) { } @@ -452,6 +509,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi if (serviceState == null) { Slog.d(LOG_TAG, "No configured system TextClassifierService"); callback.onFailure(); + } else if (!serviceState.isInstalledLocked() || !serviceState.isEnabledLocked()) { + if (DEBUG) { + Slog.d(LOG_TAG, + serviceState.mPackageName + " is not available in user " + userId + + ". Installed: " + serviceState.isInstalledLocked() + + ", enabled:" + serviceState.isEnabledLocked()); + } + callback.onFailure(); } else if (attemptToBind && !serviceState.bindLocked()) { Slog.d(LOG_TAG, "Unable to bind TextClassifierService at " + methodName); callback.onFailure(); @@ -761,6 +826,24 @@ public final class TextClassificationManagerService extends ITextClassifierServi return serviceStates; } + @GuardedBy("mLock") + @Nullable + private ServiceState getServiceStateLocked(String packageName) { + for (ServiceState serviceState : getAllServiceStatesLocked()) { + if (serviceState.mPackageName.equals(packageName)) { + return serviceState; + } + } + return null; + } + + @GuardedBy("mLock") + private void updatePackageStateLocked() { + for (ServiceState serviceState : getAllServiceStatesLocked()) { + serviceState.updatePackageStateLocked(); + } + } + void dump(IndentingPrintWriter pw) { synchronized (mLock) { pw.increaseIndent(); @@ -814,6 +897,10 @@ public final class TextClassificationManagerService extends ITextClassifierServi ComponentName mBoundComponentName = null; @GuardedBy("mLock") int mBoundServiceUid = Process.INVALID_UID; + @GuardedBy("mLock") + boolean mInstalled; + @GuardedBy("mLock") + boolean mEnabled; private ServiceState( @UserIdInt int userId, @NonNull String packageName, boolean isTrusted) { @@ -822,6 +909,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi mConnection = new TextClassifierServiceConnection(mUserId); mIsTrusted = isTrusted; mBindServiceFlags = createBindServiceFlags(packageName); + mInstalled = isPackageInstalledForUser(); + mEnabled = isServiceEnabledForUser(); } @Context.BindServiceFlags @@ -833,6 +922,54 @@ public final class TextClassificationManagerService extends ITextClassifierServi return flags; } + private boolean isPackageInstalledForUser() { + try { + PackageManager packageManager = mContext.getPackageManager(); + return packageManager.getPackageInfoAsUser(mPackageName, 0, mUserId) != null; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + private boolean isServiceEnabledForUser() { + PackageManager packageManager = mContext.getPackageManager(); + Intent intent = new Intent(TextClassifierService.SERVICE_INTERFACE); + intent.setPackage(mPackageName); + ResolveInfo resolveInfo = packageManager.resolveServiceAsUser(intent, + PackageManager.GET_SERVICES, mUserId); + ServiceInfo serviceInfo = resolveInfo == null ? null : resolveInfo.serviceInfo; + return serviceInfo != null; + } + + @GuardedBy("mLock") + @NonNull + private void onPackageInstallStatusChangeLocked(boolean installed) { + mInstalled = installed; + } + + @GuardedBy("mLock") + @NonNull + private void onPackageModifiedLocked() { + mEnabled = isServiceEnabledForUser(); + } + + @GuardedBy("mLock") + @NonNull + private void updatePackageStateLocked() { + mInstalled = isPackageInstalledForUser(); + mEnabled = isServiceEnabledForUser(); + } + + @GuardedBy("mLock") + boolean isInstalledLocked() { + return mInstalled; + } + + @GuardedBy("mLock") + boolean isEnabledLocked() { + return mEnabled; + } + @GuardedBy("mLock") boolean isBoundLocked() { return mService != null; @@ -923,6 +1060,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi pw.printPair("userId", mUserId); synchronized (mLock) { pw.printPair("packageName", mPackageName); + pw.printPair("installed", mInstalled); + pw.printPair("enabled", mEnabled); pw.printPair("boundComponentName", mBoundComponentName); pw.printPair("isTrusted", mIsTrusted); pw.printPair("bindServiceFlags", mBindServiceFlags); diff --git a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java new file mode 100644 index 000000000000..0690d3be3db1 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.os.VibratorInfo; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.MathUtils; + +import java.util.List; + +/** + * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRange()} and + * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}. + * + * <p>Devices with no frequency control will collapse all frequencies to zero and leave + * amplitudes unchanged. + * + * <p>The frequency value returned in segments will be absolute, converted with + * {@link VibratorInfo#getAbsoluteFrequency(float)}. + */ +final class ClippingAmplitudeAndFrequencyAdapter + implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> { + + @Override + public int apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info) { + int segmentCount = segments.size(); + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = segments.get(i); + if (segment instanceof StepSegment) { + segments.set(i, apply((StepSegment) segment, info)); + } else if (segment instanceof RampSegment) { + segments.set(i, apply((RampSegment) segment, info)); + } + } + return repeatIndex; + } + + private StepSegment apply(StepSegment segment, VibratorInfo info) { + float clampedFrequency = clampFrequency(info, segment.getFrequency()); + return new StepSegment( + clampAmplitude(info, clampedFrequency, segment.getAmplitude()), + info.getAbsoluteFrequency(clampedFrequency), + (int) segment.getDuration()); + } + + private RampSegment apply(RampSegment segment, VibratorInfo info) { + float clampedStartFrequency = clampFrequency(info, segment.getStartFrequency()); + float clampedEndFrequency = clampFrequency(info, segment.getEndFrequency()); + return new RampSegment( + clampAmplitude(info, clampedStartFrequency, segment.getStartAmplitude()), + clampAmplitude(info, clampedEndFrequency, segment.getEndAmplitude()), + info.getAbsoluteFrequency(clampedStartFrequency), + info.getAbsoluteFrequency(clampedEndFrequency), + (int) segment.getDuration()); + } + + private float clampFrequency(VibratorInfo info, float frequency) { + return info.getFrequencyRange().clamp(frequency); + } + + private float clampAmplitude(VibratorInfo info, float frequency, float amplitude) { + return MathUtils.min(amplitude, info.getMaxAmplitude(frequency)); + } +} diff --git a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java index 7f2b07b6f367..b695150d9ba3 100644 --- a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java +++ b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java @@ -16,159 +16,33 @@ package com.android.server.vibrator; -import android.hardware.vibrator.IVibrator; import android.os.VibrationEffect; import android.os.VibratorInfo; -import android.os.vibrator.RampSegment; -import android.os.vibrator.StepSegment; -import android.os.vibrator.VibrationEffectSegment; -import android.util.MathUtils; -import android.util.Range; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** Adapts a {@link VibrationEffect} to a specific device, taking into account its capabilities. */ -final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<VibratorInfo> { +final class DeviceVibrationEffectAdapter + implements VibrationEffectAdapters.EffectAdapter<VibratorInfo> { - /** Adapts a sequence of {@link VibrationEffectSegment} to device's capabilities. */ - interface SegmentsAdapter { + /** Duration of each step created to simulate a ramp segment. */ + private static final int RAMP_STEP_DURATION_MILLIS = 5; - /** - * Modifies the given segments list by adding/removing segments to it based on the - * device capabilities specified by given {@link VibratorInfo}. - * - * @param segments List of {@link VibrationEffectSegment} to be adapter to the device. - * @param repeatIndex Repeat index on the current segment list. - * @param info The device vibrator info that the segments must be adapted to. - * @return The new repeat index on the modifies list. - */ - int apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info); - } - - private final SegmentsAdapter mAmplitudeFrequencyAdapter; - private final SegmentsAdapter mStepToRampAdapter; + private final List<VibrationEffectAdapters.SegmentsAdapter<VibratorInfo>> mSegmentAdapters; DeviceVibrationEffectAdapter() { - this(new ClippingAmplitudeFrequencyAdapter()); - } - - DeviceVibrationEffectAdapter(SegmentsAdapter amplitudeFrequencyAdapter) { - mAmplitudeFrequencyAdapter = amplitudeFrequencyAdapter; - mStepToRampAdapter = new StepToRampAdapter(); + mSegmentAdapters = Arrays.asList( + // TODO(b/167947076): add filter that removes unsupported primitives + // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback + new RampToStepAdapter(RAMP_STEP_DURATION_MILLIS), + new StepToRampAdapter(), + new ClippingAmplitudeAndFrequencyAdapter() + ); } @Override public VibrationEffect apply(VibrationEffect effect, VibratorInfo info) { - if (!(effect instanceof VibrationEffect.Composed)) { - return effect; - } - - VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; - List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments()); - int newRepeatIndex = composed.getRepeatIndex(); - - // Maps steps that should be handled by PWLE to ramps. - // This should be done before frequency is converted from relative to absolute values. - newRepeatIndex = mStepToRampAdapter.apply(newSegments, newRepeatIndex, info); - - // Adapt amplitude and frequency values to device supported ones, converting frequency - // to absolute values in Hertz. - newRepeatIndex = mAmplitudeFrequencyAdapter.apply(newSegments, newRepeatIndex, info); - - // TODO(b/167947076): add ramp to step adapter - // TODO(b/167947076): add filter that removes unsupported primitives - // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback - - return new VibrationEffect.Composed(newSegments, newRepeatIndex); - } - - /** - * Adapter that converts step segments that should be handled as PWLEs to ramp segments. - * - * <p>This leves the list unchanged if the device do not have compose PWLE capability. - */ - private static final class StepToRampAdapter implements SegmentsAdapter { - @Override - public int apply(List<VibrationEffectSegment> segments, int repeatIndex, - VibratorInfo info) { - if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) { - // The vibrator do not have PWLE capability, so keep the segments unchanged. - return repeatIndex; - } - int segmentCount = segments.size(); - // Convert steps that require frequency control to ramps. - for (int i = 0; i < segmentCount; i++) { - VibrationEffectSegment segment = segments.get(i); - if ((segment instanceof StepSegment) - && ((StepSegment) segment).getFrequency() != 0) { - segments.set(i, apply((StepSegment) segment)); - } - } - // Convert steps that are next to ramps to also become ramps, so they can be composed - // together in the same PWLE waveform. - for (int i = 1; i < segmentCount; i++) { - if (segments.get(i) instanceof RampSegment) { - for (int j = i - 1; j >= 0 && (segments.get(j) instanceof StepSegment); j--) { - segments.set(j, apply((StepSegment) segments.get(j))); - } - } - } - return repeatIndex; - } - - private RampSegment apply(StepSegment segment) { - return new RampSegment(segment.getAmplitude(), segment.getAmplitude(), - segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration()); - } - } - - /** - * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRange()} and - * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}. - * - * <p>Devices with no frequency control will collapse all frequencies to zero and leave - * amplitudes unchanged. - * - * <p>The frequency value returned in segments will be absolute, conveted with - * {@link VibratorInfo#getAbsoluteFrequency(float)}. - */ - private static final class ClippingAmplitudeFrequencyAdapter implements SegmentsAdapter { - @Override - public int apply(List<VibrationEffectSegment> segments, int repeatIndex, - VibratorInfo info) { - int segmentCount = segments.size(); - for (int i = 0; i < segmentCount; i++) { - VibrationEffectSegment segment = segments.get(i); - if (segment instanceof StepSegment) { - segments.set(i, apply((StepSegment) segment, info)); - } else if (segment instanceof RampSegment) { - segments.set(i, apply((RampSegment) segment, info)); - } - } - return repeatIndex; - } - - private StepSegment apply(StepSegment segment, VibratorInfo info) { - float clampedFrequency = info.getFrequencyRange().clamp(segment.getFrequency()); - return new StepSegment( - MathUtils.min(segment.getAmplitude(), info.getMaxAmplitude(clampedFrequency)), - info.getAbsoluteFrequency(clampedFrequency), - (int) segment.getDuration()); - } - - private RampSegment apply(RampSegment segment, VibratorInfo info) { - Range<Float> frequencyRange = info.getFrequencyRange(); - float clampedStartFrequency = frequencyRange.clamp(segment.getStartFrequency()); - float clampedEndFrequency = frequencyRange.clamp(segment.getEndFrequency()); - return new RampSegment( - MathUtils.min(segment.getStartAmplitude(), - info.getMaxAmplitude(clampedStartFrequency)), - MathUtils.min(segment.getEndAmplitude(), - info.getMaxAmplitude(clampedEndFrequency)), - info.getAbsoluteFrequency(clampedStartFrequency), - info.getAbsoluteFrequency(clampedEndFrequency), - (int) segment.getDuration()); - } + return VibrationEffectAdapters.apply(effect, mSegmentAdapters, info); } } diff --git a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java new file mode 100644 index 000000000000..1e05bdbdf082 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.hardware.vibrator.IVibrator; +import android.os.VibratorInfo; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Adapter that converts ramp segments that to a sequence of fixed step segments. + * + * <p>This leaves the list unchanged if the device have compose PWLE capability. + */ +final class RampToStepAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> { + + private final int mStepDuration; + + RampToStepAdapter(int stepDuration) { + mStepDuration = stepDuration; + } + + @Override + public int apply(List<VibrationEffectSegment> segments, int repeatIndex, + VibratorInfo info) { + if (info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) { + // The vibrator have PWLE capability, so keep the segments unchanged. + return repeatIndex; + } + int segmentCount = segments.size(); + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = segments.get(i); + if (!(segment instanceof RampSegment)) { + continue; + } + List<StepSegment> steps = apply((RampSegment) segment); + segments.remove(i); + segments.addAll(i, steps); + int addedSegments = steps.size() - 1; + if (repeatIndex > i) { + repeatIndex += addedSegments; + } + i += addedSegments; + segmentCount += addedSegments; + } + return repeatIndex; + } + + private List<StepSegment> apply(RampSegment ramp) { + if (Float.compare(ramp.getStartAmplitude(), ramp.getEndAmplitude()) == 0) { + // Amplitude is the same, so return a single step to simulate this ramp. + return Arrays.asList( + new StepSegment(ramp.getStartAmplitude(), ramp.getStartFrequency(), + (int) ramp.getDuration())); + } + + List<StepSegment> steps = new ArrayList<>(); + int stepCount = (int) (ramp.getDuration() + mStepDuration - 1) / mStepDuration; + for (int i = 0; i < stepCount - 1; i++) { + float pos = (float) i / stepCount; + steps.add(new StepSegment( + interpolate(ramp.getStartAmplitude(), ramp.getEndAmplitude(), pos), + interpolate(ramp.getStartFrequency(), ramp.getEndFrequency(), pos), + mStepDuration)); + } + int duration = (int) ramp.getDuration() - mStepDuration * (stepCount - 1); + steps.add(new StepSegment(ramp.getEndAmplitude(), ramp.getEndFrequency(), duration)); + return steps; + } + + private static float interpolate(float start, float end, float position) { + return start + position * (end - start); + } +} diff --git a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java new file mode 100644 index 000000000000..f78df9208fbd --- /dev/null +++ b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.hardware.vibrator.IVibrator; +import android.os.VibratorInfo; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; + +import java.util.List; + +/** + * Adapter that converts step segments that should be handled as PWLEs to ramp segments. + * + * <p>This leaves the list unchanged if the device do not have compose PWLE capability. + */ +final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> { + @Override + public int apply(List<VibrationEffectSegment> segments, int repeatIndex, + VibratorInfo info) { + if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) { + // The vibrator do not have PWLE capability, so keep the segments unchanged. + return repeatIndex; + } + int segmentCount = segments.size(); + // Convert steps that require frequency control to ramps. + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = segments.get(i); + if ((segment instanceof StepSegment) + && ((StepSegment) segment).getFrequency() != 0) { + segments.set(i, apply((StepSegment) segment)); + } + } + // Convert steps that are next to ramps to also become ramps, so they can be composed + // together in the same PWLE waveform. + for (int i = 1; i < segmentCount; i++) { + if (segments.get(i) instanceof RampSegment) { + for (int j = i - 1; j >= 0 && (segments.get(j) instanceof StepSegment); j--) { + segments.set(j, apply((StepSegment) segments.get(j))); + } + } + } + return repeatIndex; + } + + private RampSegment apply(StepSegment segment) { + return new RampSegment(segment.getAmplitude(), segment.getAmplitude(), + segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration()); + } +} diff --git a/services/core/java/com/android/server/vibrator/VibrationEffectAdapters.java b/services/core/java/com/android/server/vibrator/VibrationEffectAdapters.java new file mode 100644 index 000000000000..446d9810ff19 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VibrationEffectAdapters.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.os.VibrationEffect; +import android.os.vibrator.VibrationEffectSegment; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helpers to adapt a {@link VibrationEffect} to generic modifiers (e.g. device capabilities, + * user settings, etc). + */ +public final class VibrationEffectAdapters { + + /** + * Function that applies a generic modifier to a sequence of {@link VibrationEffectSegment}. + * + * @param <T> The type of modifiers this adapter accepts. + */ + public interface SegmentsAdapter<T> { + + /** + * Add and/or remove segments to the given {@link VibrationEffectSegment} list based on the + * given modifier. + * + * <p>This returns the new {@code repeatIndex} to be used together with the updated list to + * specify an equivalent {@link VibrationEffect}. + * + * @param segments List of {@link VibrationEffectSegment} to be modified. + * @param repeatIndex Repeat index on the current segment list. + * @param modifier The modifier to be applied to the sequence of segments. + * @return The new repeat index on the modifies list. + */ + int apply(List<VibrationEffectSegment> segments, int repeatIndex, T modifier); + } + + /** + * Function that applies a generic modifier to a {@link VibrationEffect}. + * + * @param <T> The type of modifiers this adapter accepts. + */ + public interface EffectAdapter<T> { + + /** Applies the modifier to given {@link VibrationEffect}, returning the new effect. */ + VibrationEffect apply(VibrationEffect effect, T modifier); + } + + /** + * Applies a sequence of {@link SegmentsAdapter} to the segments of a given + * {@link VibrationEffect}, in order. + * + * @param effect The effect to be adapted to given modifier. + * @param adapters The sequence of adapters to be applied to given {@link VibrationEffect}. + * @param modifier The modifier to be passed to each adapter that describes the conditions the + * {@link VibrationEffect} needs to be adapted to (e.g. device capabilities, + * user settings, etc). + */ + public static <T> VibrationEffect apply(VibrationEffect effect, + List<SegmentsAdapter<T>> adapters, T modifier) { + if (!(effect instanceof VibrationEffect.Composed)) { + // Segments adapters can only be applied to Composed effects. + return effect; + } + + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments()); + int newRepeatIndex = composed.getRepeatIndex(); + + int adapterCount = adapters.size(); + for (int i = 0; i < adapterCount; i++) { + newRepeatIndex = adapters.get(i).apply(newSegments, newRepeatIndex, modifier); + } + + return new VibrationEffect.Composed(newSegments, newRepeatIndex); + } +} diff --git a/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java b/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java deleted file mode 100644 index d287c8faa34d..000000000000 --- a/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.vibrator; - -import android.os.VibrationEffect; - -/** Function that applies a generic modifier to a {@link VibrationEffect}. */ -interface VibrationEffectModifier<T> { - - /** Applies the modifier to given {@link VibrationEffect}. */ - VibrationEffect apply(VibrationEffect effect, T modifier); -} diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index bc61478ec1b5..e3672f4d497c 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -45,8 +45,10 @@ import com.android.internal.util.FrameworkStatsLog; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; +import java.util.Queue; /** Plays a {@link Vibration} in dedicated thread. */ final class VibrationThread extends Thread implements IBinder.DeathRecipient { @@ -93,7 +95,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private final WorkSource mWorkSource = new WorkSource(); private final PowerManager.WakeLock mWakeLock; private final IBatteryStats mBatteryStatsService; - private final VibrationEffectModifier<VibratorInfo> mDeviceEffectAdapter = + private final DeviceVibrationEffectAdapter mDeviceEffectAdapter = new DeviceVibrationEffectAdapter(); private final Vibration mVibration; private final VibrationCallbacks mCallbacks; @@ -171,7 +173,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { Slog.d(TAG, "Synced vibration complete reported by vibrator manager"); } for (int i = 0; i < mVibrators.size(); i++) { - mStepQueue.consumeOnVibratorComplete(mVibrators.keyAt(i)); + mStepQueue.notifyVibratorComplete(mVibrators.keyAt(i)); } mLock.notify(); } @@ -183,7 +185,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { if (DEBUG) { Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId); } - mStepQueue.consumeOnVibratorComplete(vibratorId); + mStepQueue.notifyVibratorComplete(vibratorId); mLock.notify(); } } @@ -192,26 +194,25 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration"); try { CombinedVibration.Sequential effect = toSequential(mVibration.getEffect()); - int stepsPlayed = 0; - - synchronized (mLock) { - mStepQueue.offer(new StartVibrateStep(effect)); - Step topOfQueue; + mStepQueue.offer(new StartVibrateStep(effect)); - while ((topOfQueue = mStepQueue.peek()) != null) { - long waitTime = topOfQueue.calculateWaitTime(); - if (waitTime <= 0) { - stepsPlayed += mStepQueue.consume(); - } else { + int stepsPlayed = 0; + while (!mStepQueue.isEmpty()) { + long waitTime = mStepQueue.calculateWaitTime(); + if (waitTime <= 0) { + stepsPlayed += mStepQueue.consumeNext(); + } else { + synchronized (mLock) { try { mLock.wait(waitTime); - } catch (InterruptedException e) { } - } - if (mForceStop) { - mStepQueue.cancel(); - return Vibration.Status.CANCELLED; + } catch (InterruptedException e) { + } } } + if (mForceStop) { + mStepQueue.cancel(); + return Vibration.Status.CANCELLED; + } } // Some effects might be ignored because the specified vibrator don't exist or doesn't @@ -295,54 +296,75 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private final class StepQueue { @GuardedBy("mLock") private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>(); - @GuardedBy("mLock") + private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>(); + public void offer(@NonNull Step step) { - mNextSteps.offer(step); + synchronized (mLock) { + mNextSteps.offer(step); + } } - @GuardedBy("mLock") - @Nullable - public Step peek() { - return mNextSteps.peek(); + public boolean isEmpty() { + synchronized (mLock) { + return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty(); + } + } + + /** Returns the time in millis to wait before calling {@link #consumeNext()}. */ + public long calculateWaitTime() { + Step nextStep; + synchronized (mLock) { + if (!mPendingOnVibratorCompleteSteps.isEmpty()) { + // Steps anticipated by vibrator complete callback should be played right away. + return 0; + } + nextStep = mNextSteps.peek(); + } + return nextStep == null ? 0 : nextStep.calculateWaitTime(); } /** - * Play and remove the step at the top of this queue, and also adds the next steps - * generated to be played next. + * Play and remove the step at the top of this queue, and also adds the next steps generated + * to be played next. * * @return the number of steps played */ - @GuardedBy("mLock") - public int consume() { - Step nextStep = mNextSteps.poll(); + public int consumeNext() { + Step nextStep = pollNext(); if (nextStep != null) { - mNextSteps.addAll(nextStep.play()); + // This might turn on the vibrator and have a HAL latency. Execute this outside any + // lock to avoid blocking other interactions with the thread. + List<Step> nextSteps = nextStep.play(); + synchronized (mLock) { + mNextSteps.addAll(nextSteps); + } return 1; } return 0; } /** - * Play and remove the step in this queue that should be anticipated by the vibrator - * completion callback. + * Notify the step in this queue that should be anticipated by the vibrator completion + * callback and keep it separate to be consumed by {@link #consumeNext()}. + * + * <p>This is a lightweight method that do not trigger any operation from {@link + * VibratorController}, so it can be called directly from a native callback. * * <p>This assumes only one of the next steps is waiting on this given vibrator, so the - * first step found is played by this method, in no particular order. + * first step found will be anticipated by this method, in no particular order. */ @GuardedBy("mLock") - public void consumeOnVibratorComplete(int vibratorId) { + public void notifyVibratorComplete(int vibratorId) { Iterator<Step> it = mNextSteps.iterator(); - List<Step> nextSteps = EMPTY_STEP_LIST; while (it.hasNext()) { Step step = it.next(); if (step.shouldPlayWhenVibratorComplete(vibratorId)) { it.remove(); - nextSteps = step.play(); + mPendingOnVibratorCompleteSteps.offer(step); break; } } - mNextSteps.addAll(nextSteps); } /** @@ -350,13 +372,25 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { * * <p>This will remove and trigger {@link Step#cancel()} in all steps, in order. */ - @GuardedBy("mLock") public void cancel() { Step step; - while ((step = mNextSteps.poll()) != null) { + while ((step = pollNext()) != null) { + // This might turn off the vibrator and have a HAL latency. Execute this outside + // any lock to avoid blocking other interactions with the thread. step.cancel(); } } + + @Nullable + private Step pollNext() { + synchronized (mLock) { + // Prioritize the steps anticipated by a vibrator complete callback. + if (!mPendingOnVibratorCompleteSteps.isEmpty()) { + return mPendingOnVibratorCompleteSteps.poll(); + } + return mNextSteps.poll(); + } + } } /** diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index a09bfc57ba23..5dceac2d066c 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -67,7 +67,7 @@ final class VibratorController { mNativeWrapper = nativeWrapper; mNativeWrapper.init(vibratorId, listener); // TODO(b/167947076): load suggested range from config - mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 100); + mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 200); Preconditions.checkNotNull(mVibratorInfo, "Failed to retrieve data for vibrator %d", vibratorId); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3969a5f8d50c..d4df2f258a04 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7013,7 +7013,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/181207944): Consider removing the if condition and always run // resolveFixedOrientationConfiguration() since this should be applied for all cases. if (isFixedOrientationLetterboxAllowed) { - resolveFixedOrientationConfiguration(newParentConfiguration); + resolveFixedOrientationConfiguration(newParentConfiguration, parentWindowingMode); } if (mCompatDisplayInsets != null) { @@ -7160,16 +7160,21 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * change and the requested orientation is different from the parent. * * <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied - * in this methiod. + * in this method. */ - private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) { + private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig, + int windowingMode) { mLetterboxBoundsForFixedOrientationAndAspectRatio = null; if (handlesOrientationChangeFromDescendant()) { // No need to letterbox because of fixed orientation. Display will handle // fixed-orientation requests. return; } - if (newParentConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED) { + if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable()) { + // Ignore orientation request for resizable apps in multi window. + return; + } + if (windowingMode == WINDOWING_MODE_PINNED) { // PiP bounds have higher priority than the requested orientation. Otherwise the // activity may be squeezed into a small piece. return; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 29e55df32509..5e75cebc322d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2835,7 +2835,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { List<IBinder> topActivityToken = new ArrayList<>(); topActivityToken.add(tokens.getActivityToken()); requester.requestAssistData(topActivityToken, true /* fetchData */, - false /* fetchScreenshot */, true /* allowFetchData */, + false /* fetchScreenshot */, false /* fetchStructure */, true /* allowFetchData */, false /* allowFetchScreenshot*/, true /* ignoreFocusCheck */, Binder.getCallingUid(), callingPackageName); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 70f2d64e89aa..977df93412f8 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1453,8 +1453,7 @@ public class DisplayPolicy { } void onDisplayInfoChanged(DisplayInfo info) { - mSystemGestures.screenWidth = info.logicalWidth; - mSystemGestures.screenHeight = info.logicalHeight; + mSystemGestures.onDisplayInfoChanged(info); } private void layoutStatusBar(DisplayFrames displayFrames, Rect contentFrame) { diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java index f3859b41b6fd..513b1b715a27 100644 --- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java +++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java @@ -17,6 +17,7 @@ package com.android.server.wm; import android.content.Context; +import android.content.res.Resources; import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManagerGlobal; @@ -65,6 +66,7 @@ class SystemGesturesPointerEventListener implements PointerEventListener { int screenHeight; int screenWidth; + private DisplayInfo mTmpDisplayInfo = new DisplayInfo(); private int mDownPointers; private boolean mSwipeFireable; private boolean mDebugFireable; @@ -75,23 +77,31 @@ class SystemGesturesPointerEventListener implements PointerEventListener { mContext = checkNull("context", context); mHandler = handler; mCallbacks = checkNull("callbacks", callbacks); + onConfigurationChanged(); + } + void onDisplayInfoChanged(DisplayInfo info) { + screenWidth = info.logicalWidth; + screenHeight = info.logicalHeight; onConfigurationChanged(); } void onConfigurationChanged() { - mSwipeStartThreshold = mContext.getResources() - .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); - + final Resources r = mContext.getResources(); final Display display = DisplayManagerGlobal.getInstance() .getRealDisplay(Display.DEFAULT_DISPLAY); + display.getDisplayInfo(mTmpDisplayInfo); + mSwipeStartThreshold = mTmpDisplayInfo.logicalWidth > mTmpDisplayInfo.logicalHeight + ? r.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height_landscape) + : r.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height_portrait); + final DisplayCutout displayCutout = display.getCutout(); if (displayCutout != null) { final Rect bounds = displayCutout.getBoundingRectTop(); if (!bounds.isEmpty()) { // Expand swipe start threshold such that we can catch touches that just start below // the notch area - mDisplayCutoutTouchableRegionSize = mContext.getResources().getDimensionPixelSize( + mDisplayCutoutTouchableRegionSize = r.getDimensionPixelSize( com.android.internal.R.dimen.display_cutout_touchable_region_size); mSwipeStartThreshold += mDisplayCutoutTouchableRegionSize; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7df5744cdf15..4a9c1bbb0f28 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2329,6 +2329,17 @@ public class WindowManagerService extends IWindowManager.Stub } result |= RELAYOUT_RES_SURFACE_CHANGED; if (!win.mWillReplaceWindow) { + // When FLAG_SHOW_WALLPAPER flag is removed from a window, we usually set a flag + // in DC#pendingLayoutChanges and update the wallpaper target later. + // However it's possible that FLAG_SHOW_WALLPAPER flag is removed from a window + // when the window is about to exit, so we update the wallpaper target + // immediately here. Otherwise this window will be stuck in exiting and its + // surface remains on the screen. + // TODO(b/189856716): Allow destroying surface even if it belongs to the + // keyguard target. + if (wallpaperMayMove) { + displayContent.mWallpaperController.adjustWallpaperWindows(); + } focusMayChange = tryStartExitingAnimation(win, winAnimator, focusMayChange); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 5f4798650184..20a992d244c5 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1500,7 +1500,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } boolean didFrameInsetsChange = setReportResizeHints(); - boolean configChanged = !isLastConfigReportedToClient(); + // The latest configuration will be returned by the out parameter of relayout, so it is + // unnecessary to report resize if this window is running relayout. + final boolean configChanged = !mInRelayout && !isLastConfigReportedToClient(); if (DEBUG_CONFIGURATION && configChanged) { Slog.v(TAG_WM, "Win " + this + " config changed: " + getConfiguration()); } diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp index acad1bc95ce0..d0f56e62f487 100644 --- a/services/core/jni/com_android_server_sensor_SensorService.cpp +++ b/services/core/jni/com_android_server_sensor_SensorService.cpp @@ -14,34 +14,123 @@ * limitations under the License. */ -#define LOG_TAG "SensorService" - -#include <nativehelper/JNIHelp.h> -#include "android_runtime/AndroidRuntime.h" -#include "core_jni_helpers.h" -#include "jni.h" +#define LOG_TAG "NativeSensorService" +#include <android-base/properties.h> +#include <android_runtime/AndroidRuntime.h> +#include <core_jni_helpers.h> #include <cutils/properties.h> +#include <jni.h> #include <sensorservice/SensorService.h> #include <utils/Log.h> #include <utils/misc.h> +#include <mutex> + +#define PROXIMITY_ACTIVE_CLASS \ + "com/android/server/sensors/SensorManagerInternal$ProximityActiveListener" + namespace android { -static void startNativeSensorService(JNIEnv* env, jclass clazz) { - char propBuf[PROPERTY_VALUE_MAX]; - property_get("system_init.startsensorservice", propBuf, "1"); - if (strcmp(propBuf, "1") == 0) { - SensorService::publish(false /* allowIsolated */, - IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL); +static jmethodID sMethodIdOnProximityActive; + +class NativeSensorService { +public: + NativeSensorService(JNIEnv* env, jobject listener); + + void registerProximityActiveListener(); + void unregisterProximityActiveListener(); + +private: + sp<SensorService> mService; + + class ProximityActiveListenerDelegate : public SensorService::ProximityActiveListener { + public: + ProximityActiveListenerDelegate(JNIEnv* env, jobject listener); + ~ProximityActiveListenerDelegate(); + + void onProximityActive(bool isActive) override; + + private: + jobject mListener; + }; + sp<ProximityActiveListenerDelegate> mProximityActiveListenerDelegate; +}; + +NativeSensorService::NativeSensorService(JNIEnv* env, jobject listener) + : mProximityActiveListenerDelegate(new ProximityActiveListenerDelegate(env, listener)) { + if (base::GetBoolProperty("system_init.startsensorservice", true)) { + sp<IServiceManager> sm(defaultServiceManager()); + mService = new SensorService(); + sm->addService(String16(SensorService::getServiceName()), mService, + false /* allowIsolated */, IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL); + } +} + +void NativeSensorService::registerProximityActiveListener() { + if (mService == nullptr) { + ALOGD("Dropping registerProximityActiveListener, sensor service not available."); + return; + } + mService->addProximityActiveListener(mProximityActiveListenerDelegate); +} + +void NativeSensorService::unregisterProximityActiveListener() { + if (mService == nullptr) { + ALOGD("Dropping unregisterProximityActiveListener, sensor service not available."); + return; } + + mService->removeProximityActiveListener(mProximityActiveListenerDelegate); +} + +NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate( + JNIEnv* env, jobject listener) + : mListener(env->NewGlobalRef(listener)) {} + +NativeSensorService::ProximityActiveListenerDelegate::~ProximityActiveListenerDelegate() { + AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mListener); +} + +void NativeSensorService::ProximityActiveListenerDelegate::onProximityActive(bool isActive) { + AndroidRuntime::getJNIEnv()->CallVoidMethod(mListener, sMethodIdOnProximityActive, + static_cast<jboolean>(isActive)); +} + +static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) { + NativeSensorService* service = new NativeSensorService(env, listener); + return reinterpret_cast<jlong>(service); +} + +static void registerProximityActiveListenerNative(JNIEnv* env, jclass, jlong ptr) { + auto* service = reinterpret_cast<NativeSensorService*>(ptr); + service->registerProximityActiveListener(); +} + +static void unregisterProximityActiveListenerNative(JNIEnv* env, jclass, jlong ptr) { + auto* service = reinterpret_cast<NativeSensorService*>(ptr); + service->unregisterProximityActiveListener(); } static const JNINativeMethod methods[] = { - {"startNativeSensorService", "()V", (void*)startNativeSensorService}, + { + "startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J", + reinterpret_cast<void*>(startSensorServiceNative) + }, + { + "registerProximityActiveListenerNative", "(J)V", + reinterpret_cast<void*>(registerProximityActiveListenerNative) + }, + { + "unregisterProximityActiveListenerNative", "(J)V", + reinterpret_cast<void*>(unregisterProximityActiveListenerNative) + }, + }; int register_android_server_sensor_SensorService(JNIEnv* env) { + jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS); + sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V"); return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods, NELEM(methods)); } diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-mobile.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-mobile.xml new file mode 100644 index 000000000000..d1357e70ab32 --- /dev/null +++ b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-mobile.xml @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policy-list version="12" restrictBackground="false"> + <network-policy networkTemplate="1" subscriberId="466977604504520" cycleStart="2020-01-09T00:00+08:00[Asia/Taipei]" cyclePeriod="P1M" warningBytes="2147483648" limitBytes="-1" lastWarningSnooze="-1" lastLimitSnooze="-1" metered="true" inferred="true" /> +</policy-list> +<whitelist /> + diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-wifi-with-subscriberId-match-rule-all-and-templateMetered-no.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-wifi-with-subscriberId-match-rule-all-and-templateMetered-no.xml new file mode 100644 index 000000000000..60d7d2555f04 --- /dev/null +++ b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-wifi-with-subscriberId-match-rule-all-and-templateMetered-no.xml @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policy-list version="13" restrictBackground="false"> +<network-policy networkTemplate="4" subscriberIdMatchRule="1" networkId="TEST_SSID" templateMetered="0" cycleStart="2020-01-09T00:00+08:00[Asia/Taipei]" cyclePeriod="P1M" warningBytes="2147483648" limitBytes="-1" lastWarningSnooze="-1" lastLimitSnooze="-1" metered="true" inferred="true" /> +</policy-list> +<whitelist /> diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index da6c30e1cb0a..23517a9b0458 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -50,10 +50,12 @@ import static android.net.NetworkPolicyManager.uidPoliciesToString; import static android.net.NetworkPolicyManager.uidRulesToString; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.METERED_YES; import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.TAG_ALL; import static android.net.NetworkStats.TAG_NONE; -import static android.net.NetworkTemplate.buildTemplateMobileAll; +import static android.net.NetworkTemplate.buildTemplateCarrierMetered; import static android.net.NetworkTemplate.buildTemplateWifi; import static android.net.TrafficStats.MB_IN_BYTES; import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; @@ -231,7 +233,8 @@ public class NetworkPolicyManagerServiceTest { private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_SSID); - private static NetworkTemplate sTemplateMobileAll = buildTemplateMobileAll(TEST_IMSI); + private static NetworkTemplate sTemplateCarrierMetered = + buildTemplateCarrierMetered(TEST_IMSI); /** * Path on assets where files used by {@link NetPolicyXml} are located. @@ -452,7 +455,7 @@ public class NetworkPolicyManagerServiceTest { verify(mNetworkManager).registerObserver(networkObserver.capture()); mNetworkObserver = networkObserver.getValue(); - NetworkPolicy defaultPolicy = mService.buildDefaultMobilePolicy(0, ""); + NetworkPolicy defaultPolicy = mService.buildDefaultCarrierPolicy(0, ""); mDefaultWarningBytes = defaultPolicy.warningBytes; mDefaultLimitBytes = defaultPolicy.limitBytes; } @@ -1231,7 +1234,7 @@ public class NetworkPolicyManagerServiceTest { reset(mTelephonyManager, mNetworkManager, mNotifManager); TelephonyManager tmSub = expectMobileDefaults(); - mService.snoozeLimit(NetworkTemplate.buildTemplateMobileAll(TEST_IMSI)); + mService.snoozeLimit(NetworkTemplate.buildTemplateCarrierMetered(TEST_IMSI)); mService.updateNetworks(); verify(tmSub, atLeastOnce()).setPolicyDataEnabled(true); @@ -1484,7 +1487,7 @@ public class NetworkPolicyManagerServiceTest { assertEquals(mDefaultLimitBytes, actualLimitBytes); } - private PersistableBundle setupUpdateMobilePolicyCycleTests() throws RemoteException { + private PersistableBundle setupUpdateCarrierPolicyCycleTests() throws RemoteException { when(mConnManager.getAllNetworkStateSnapshots()) .thenReturn(new ArrayList<NetworkStateSnapshot>()); @@ -1492,19 +1495,19 @@ public class NetworkPolicyManagerServiceTest { PersistableBundle bundle = CarrierConfigManager.getDefaultConfig(); when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(bundle); - setNetworkPolicies(buildDefaultFakeMobilePolicy()); + setNetworkPolicies(buildDefaultFakeCarrierPolicy()); return bundle; } @Test - public void testUpdateMobilePolicyCycleWithNullConfig() throws RemoteException { + public void testUpdateCarrierPolicyCycleWithNullConfig() throws RemoteException { when(mConnManager.getAllNetworkStateSnapshots()) .thenReturn(new ArrayList<NetworkStateSnapshot>()); setupTelephonySubscriptionManagers(FAKE_SUB_ID, FAKE_SUBSCRIBER_ID); when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(null); - setNetworkPolicies(buildDefaultFakeMobilePolicy()); + setNetworkPolicies(buildDefaultFakeCarrierPolicy()); // smoke test to make sure no errors are raised mServiceContext.sendBroadcast( new Intent(ACTION_CARRIER_CONFIG_CHANGED) @@ -1515,8 +1518,8 @@ public class NetworkPolicyManagerServiceTest { } @Test - public void testUpdateMobilePolicyCycleWithInvalidConfig() throws RemoteException { - PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + public void testUpdateCarrierPolicyCycleWithInvalidConfig() throws RemoteException { + PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests(); // Test with an invalid CarrierConfig, there should be no changes or crashes. bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, -100); bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, -100); @@ -1531,8 +1534,8 @@ public class NetworkPolicyManagerServiceTest { } @Test - public void testUpdateMobilePolicyCycleWithDefaultConfig() throws RemoteException { - PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + public void testUpdateCarrierPolicyCycleWithDefaultConfig() throws RemoteException { + PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests(); // Test that we respect the platform values when told to bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, DATA_CYCLE_USE_PLATFORM_DEFAULT); @@ -1550,11 +1553,11 @@ public class NetworkPolicyManagerServiceTest { } @Test - public void testUpdateMobilePolicyCycleWithUserOverrides() throws RemoteException { - PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + public void testUpdateCarrierPolicyCycleWithUserOverrides() throws RemoteException { + PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests(); // inferred = false implies that a user manually modified this policy. - NetworkPolicy policy = buildDefaultFakeMobilePolicy(); + NetworkPolicy policy = buildDefaultFakeCarrierPolicy(); policy.inferred = false; setNetworkPolicies(policy); @@ -1573,8 +1576,8 @@ public class NetworkPolicyManagerServiceTest { } @Test - public void testUpdateMobilePolicyCycleUpdatesDataCycle() throws RemoteException { - PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + public void testUpdateCarrierPolicyCycleUpdatesDataCycle() throws RemoteException { + PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests(); bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31); bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, 9999); @@ -1588,8 +1591,8 @@ public class NetworkPolicyManagerServiceTest { } @Test - public void testUpdateMobilePolicyCycleDisableThresholds() throws RemoteException { - PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + public void testUpdateCarrierPolicyCycleDisableThresholds() throws RemoteException { + PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests(); bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31); bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, @@ -1605,8 +1608,8 @@ public class NetworkPolicyManagerServiceTest { } @Test - public void testUpdateMobilePolicyCycleRevertsToDefault() throws RemoteException { - PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + public void testUpdateCarrierPolicyCycleRevertsToDefault() throws RemoteException { + PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests(); bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31); bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, @@ -1775,7 +1778,7 @@ public class NetworkPolicyManagerServiceTest { @Test public void testSetNetworkPolicies_withNullPolicies_doesNotThrow() { NetworkPolicy[] policies = new NetworkPolicy[3]; - policies[1] = buildDefaultFakeMobilePolicy(); + policies[1] = buildDefaultFakeCarrierPolicy(); setNetworkPolicies(policies); assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes, @@ -1821,7 +1824,8 @@ public class NetworkPolicyManagerServiceTest { // Set warning to 7KB and limit to 10KB. setNetworkPolicies(new NetworkPolicy( - sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, 7000L, 10000L, true)); + sTemplateCarrierMetered, CYCLE_DAY, TIMEZONE_UTC, 7000L, 10000L, + true)); postMsgAndWaitForCompletion(); // Verifies that remaining quotas are set to providers. @@ -1972,6 +1976,40 @@ public class NetworkPolicyManagerServiceTest { } } + @Test + @NetPolicyXml("network-policy-mobile.xml") + public void testStartToSupportCarrierUsagePolicy() throws Exception { + NetworkPolicy[] policies = mService.getNetworkPolicies( + mServiceContext.getOpPackageName()); + assertEquals("Unexpected number of network policies", 1, policies.length); + NetworkPolicy actualPolicy = policies[0]; + assertEquals("Unexpected template match rule in network policies", + NetworkTemplate.MATCH_CARRIER, + actualPolicy.template.getMatchRule()); + assertEquals("Unexpected subscriberId match rule in network policies", + NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT, + actualPolicy.template.getSubscriberIdMatchRule()); + assertEquals("Unexpected template meteredness in network policies", + METERED_YES, actualPolicy.template.getMeteredness()); + } + + @Test + @NetPolicyXml("network-policy-wifi-with-subscriberId-match-rule-all-and-templateMetered-no.xml") + public void testSupportedCarrierUsagePolicy() throws Exception { + NetworkPolicy[] policies = mService.getNetworkPolicies( + mServiceContext.getOpPackageName()); + assertEquals("Unexpected number of network policies", 1, policies.length); + NetworkPolicy actualPolicy = policies[0]; + assertEquals("Unexpected template match rule in network policies", + NetworkTemplate.MATCH_WIFI, + actualPolicy.template.getMatchRule()); + assertEquals("Unexpected subscriberId match rule in network policies", + NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_ALL, + actualPolicy.template.getSubscriberIdMatchRule()); + assertEquals("Unexpected template meteredness in network policies", + METERED_NO, actualPolicy.template.getMeteredness()); + } + private String formatBlockedStateError(int uid, int rule, boolean metered, boolean backgroundRestricted) { return String.format( @@ -2024,8 +2062,8 @@ public class NetworkPolicyManagerServiceTest { return builder.build(); } - private NetworkPolicy buildDefaultFakeMobilePolicy() { - NetworkPolicy p = mService.buildDefaultMobilePolicy(FAKE_SUB_ID, FAKE_SUBSCRIBER_ID); + private NetworkPolicy buildDefaultFakeCarrierPolicy() { + NetworkPolicy p = mService.buildDefaultCarrierPolicy(FAKE_SUB_ID, FAKE_SUBSCRIBER_ID); // set a deterministic cycle date p.cycleRule = new RecurrenceRule( p.cycleRule.start.withDayOfMonth(DEFAULT_CYCLE_DAY), @@ -2033,9 +2071,9 @@ public class NetworkPolicyManagerServiceTest { return p; } - private static NetworkPolicy buildFakeMobilePolicy(int cycleDay, long warningBytes, + private static NetworkPolicy buildFakeCarrierPolicy(int cycleDay, long warningBytes, long limitBytes, boolean inferred) { - final NetworkTemplate template = buildTemplateMobileAll(FAKE_SUBSCRIBER_ID); + final NetworkTemplate template = buildTemplateCarrierMetered(FAKE_SUBSCRIBER_ID); return new NetworkPolicy(template, cycleDay, TimeZone.getDefault().getID(), warningBytes, limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, inferred); } @@ -2046,8 +2084,8 @@ public class NetworkPolicyManagerServiceTest { mServiceContext.getOpPackageName()); assertEquals("Unexpected number of network policies", 1, policies.length); NetworkPolicy actualPolicy = policies[0]; - NetworkPolicy expectedPolicy = buildFakeMobilePolicy(expectedCycleDay, expectedWarningBytes, - expectedLimitBytes, expectedInferred); + NetworkPolicy expectedPolicy = buildFakeCarrierPolicy(expectedCycleDay, + expectedWarningBytes, expectedLimitBytes, expectedInferred); assertEquals(expectedPolicy, actualPolicy); } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java index f65969833521..00b05d4ad10c 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java @@ -77,6 +77,38 @@ public class DeviceVibrationEffectAdapterTest { } @Test + public void testStepAndRampSegments_withoutPwleCapability_convertsRampsToSteps() { + VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f, + /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f, + /* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 11), + new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f, + /* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 200)), + /* repeatIndex= */ 3); + + VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList( + new StepSegment(/* amplitude= */ 0, Float.NaN, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0.5f, Float.NaN, /* duration= */ 100), + // 10ms ramp becomes 2 steps + new StepSegment(/* amplitude= */ 1, Float.NaN, /* duration= */ 5), + new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 5), + // 11ms ramp becomes 3 steps + new StepSegment(/* amplitude= */ 0.8f, Float.NaN, /* duration= */ 5), + new StepSegment(/* amplitude= */ 0.6f, Float.NaN, /* duration= */ 5), + new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 1), + // 200ms ramp with same amplitude becomes a single step + new StepSegment(/* amplitude= */ 0.65f, Float.NaN, /* duration= */ 200)), + // Repeat index fixed after intermediate steps added + /* repeatIndex= */ 4); + + VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING); + assertEquals(expected, mAdapter.apply(effect, info)); + } + + @Test public void testStepAndRampSegments_withPwleCapability_convertsStepsToRamps() { VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10), @@ -131,7 +163,7 @@ public class DeviceVibrationEffectAdapterTest { } @Test - public void testStepAndRampSegments_emptyMapping_returnsSameAmplitudesAndFrequencyZero() { + public void testStepAndRampSegments_withEmptyFreqMapping_returnsSameAmplitudesAndZeroFreq() { VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10), new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100), @@ -142,8 +174,11 @@ public class DeviceVibrationEffectAdapterTest { /* repeatIndex= */ 2); VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList( - new StepSegment(/* amplitude= */ 0, /* frequency= */ Float.NaN, /* duration= */ 10), - new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ Float.NaN, + new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0, + /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN, + /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f, + /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN, /* duration= */ 100), new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 1, /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN, @@ -153,11 +188,13 @@ public class DeviceVibrationEffectAdapterTest { /* duration= */ 20)), /* repeatIndex= */ 2); - assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING))); + VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING, + IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + assertEquals(expected, mAdapter.apply(effect, info)); } @Test - public void testStepAndRampSegments_nonEmptyMapping_returnsClippedValues() { + public void testStepAndRampSegments_withValidFreqMapping_returnsClippedValues() { VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 10), new StepSegment(/* amplitude= */ 1, /* frequency= */ -1, /* duration= */ 100), @@ -168,15 +205,21 @@ public class DeviceVibrationEffectAdapterTest { /* repeatIndex= */ 2); VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList( - new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 150, /* duration= */ 10), - new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 125, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f, + /* startFrequency= */ 150, /* endFrequency= */ 150, + /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f, + /* startFrequency= */ 125, /* endFrequency= */ 125, + /* duration= */ 100), new RampSegment(/* startAmplitude= */ 0.1f, /* endAmplitude= */ 0.8f, /* startFrequency= */ 50, /* endFrequency= */ 200, /* duration= */ 50), new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.1f, /* startFrequency= */ 200, /* endFrequency= */ 50, /* duration= */ 20)), /* repeatIndex= */ 2); - assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING))); + VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING, + IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + assertEquals(expected, mAdapter.apply(effect, info)); } private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyMapping frequencyMapping, diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index 5743982171cb..ac3e05d27962 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -85,12 +85,17 @@ public class VibrationThreadTest { private static final String PACKAGE_NAME = "package"; private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build(); - @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); - - @Mock private VibrationThread.VibrationCallbacks mThreadCallbacks; - @Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks; - @Mock private IBinder mVibrationToken; - @Mock private IBatteryStats mIBatteryStatsMock; + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock + private VibrationThread.VibrationCallbacks mThreadCallbacks; + @Mock + private VibratorController.OnVibrationCompleteListener mControllerCallbacks; + @Mock + private IBinder mVibrationToken; + @Mock + private IBatteryStats mIBatteryStatsMock; private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>(); private PowerManager.WakeLock mWakeLock; @@ -253,7 +258,7 @@ public class VibrationThreadTest { Thread cancellingThread = new Thread(() -> vibrationThread.cancel()); cancellingThread.start(); - waitForCompletion(vibrationThread, 20); + waitForCompletion(vibrationThread, /* timeout= */ 50); waitForCompletion(cancellingThread); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); @@ -278,7 +283,7 @@ public class VibrationThreadTest { Thread cancellingThread = new Thread(() -> vibrationThread.cancel()); cancellingThread.start(); - waitForCompletion(vibrationThread, 20); + waitForCompletion(vibrationThread, /* timeout= */ 50); waitForCompletion(cancellingThread); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); @@ -844,6 +849,39 @@ public class VibrationThreadTest { delay < maxDelay); } + @LargeTest + @Test + public void vibrate_cancelSlowVibrator_cancelIsNotBlockedByVibrationThread() throws Exception { + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); + fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); + + long latency = 5_000; // 5s + fakeVibrator.setLatency(latency); + + long vibrationId = 1; + VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + VibrationThread vibrationThread = startThreadAndDispatcher(vibrationId, effect); + + assertTrue(waitUntil( + t -> !fakeVibrator.getEffectSegments().isEmpty(), + vibrationThread, TEST_TIMEOUT_MILLIS)); + assertTrue(vibrationThread.isAlive()); + + // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should + // fail at waitForCompletion(cancellingThread). + Thread cancellingThread = new Thread(() -> vibrationThread.cancel()); + cancellingThread.start(); + + // Cancelling the vibration should be fast and return right away, even if the thread is + // stuck at the slow call to the vibrator. + waitForCompletion(cancellingThread, /* timeout= */ 50); + + // After the vibrator call ends the vibration is cancelled and the vibrator is turned off. + waitForCompletion(vibrationThread, /* timeout= */ latency + TEST_TIMEOUT_MILLIS); + verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); + assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating()); + } + @Test public void vibrate_multiplePredefinedCancel_cancelsVibrationImmediately() throws Exception { mockVibrators(1, 2); @@ -870,7 +908,7 @@ public class VibrationThreadTest { Thread cancellingThread = new Thread(() -> vibrationThread.cancel()); cancellingThread.start(); - waitForCompletion(vibrationThread, 20); + waitForCompletion(vibrationThread, /* timeout= */ 50); waitForCompletion(cancellingThread); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); @@ -902,7 +940,7 @@ public class VibrationThreadTest { Thread cancellingThread = new Thread(() -> vibrationThread.cancel()); cancellingThread.start(); - waitForCompletion(vibrationThread, 20); + waitForCompletion(vibrationThread, /* timeout= */ 50); waitForCompletion(cancellingThread); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java index 59370e210652..9e98e7d0410c 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -297,7 +298,7 @@ public class VibratorControllerTest { private void mockVibratorCapabilities(int capabilities) { VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping( Float.NaN, Float.NaN, Float.NaN, Float.NaN, null); - when(mNativeWrapperMock.getInfo(/* suggestedFrequencyRange= */ 100)).thenReturn( + when(mNativeWrapperMock.getInfo(anyFloat())).thenReturn( new VibratorInfo.Builder(VIBRATOR_ID) .setCapabilities(capabilities) .setFrequencyMapping(frequencyMapping) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 00eb0f284719..3862d754b8f5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -93,6 +93,9 @@ import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.verification.VerificationMode; + +import java.util.Objects; @SmallTest @RunWith(AndroidJUnit4.class) @@ -137,20 +140,19 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { private static final int CUSTOM_LIGHT_COLOR = Color.BLACK; private static final int CUSTOM_LIGHT_ON = 10000; private static final int CUSTOM_LIGHT_OFF = 10000; - private static final long[] FALLBACK_VIBRATION_PATTERN = new long[] {100, 100, 100}; - private static final VibrationEffect FALLBACK_VIBRATION = - VibrationEffect.createWaveform(FALLBACK_VIBRATION_PATTERN, -1); private static final int MAX_VIBRATION_DELAY = 1000; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + getContext().addMockSystemService(Vibrator.class, mVibrator); when(mAudioManager.isAudioFocusExclusive()).thenReturn(false); when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); + when(mVibrator.hasFrequencyControl()).thenReturn(false); long serviceReturnValue = IntPair.of( AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED, @@ -164,13 +166,12 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger, mNotificationInstanceIdSequence)); + mService.setVibratorHelper(new VibratorHelper(getContext())); mService.setAudioManager(mAudioManager); - mService.setVibrator(mVibrator); mService.setSystemReady(true); mService.setHandler(mHandler); mService.setLights(mLight); mService.setScreenOn(false); - mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN); mService.setUsageStats(mUsageStats); mService.setAccessibilityManager(accessibilityManager); mService.mScreenOn = false; @@ -416,32 +417,34 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { } private void verifyVibrate() { - ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class); - verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher), - anyString(), captor.capture()); - assertEquals(0, (captor.getValue().getAllFlags() - & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)); + verifyVibrate(/* times= */ 1); } private void verifyVibrate(int times) { - verify(mVibrator, times(times)).vibrate(eq(Process.SYSTEM_UID), - eq(PackageManagerService.PLATFORM_PACKAGE_NAME), any(), anyString(), - any(AudioAttributes.class)); + verifyVibrate(mVibrateOnceMatcher, times(times)); } private void verifyVibrateLooped() { - verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher), - anyString(), any(AudioAttributes.class)); + verifyVibrate(mVibrateLoopMatcher, times(1)); } private void verifyDelayedVibrateLooped() { - verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(), - argThat(mVibrateLoopMatcher), anyString(), any(AudioAttributes.class)); + verifyVibrate(mVibrateLoopMatcher, timeout(MAX_VIBRATION_DELAY).times(1)); } - private void verifyDelayedVibrate() { - verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(), - argThat(mVibrateOnceMatcher), anyString(), any(AudioAttributes.class)); + private void verifyDelayedVibrate(VibrationEffect effect) { + verifyVibrate(argument -> Objects.equals(effect, argument), + timeout(MAX_VIBRATION_DELAY).times(1)); + } + + private void verifyVibrate(ArgumentMatcher<VibrationEffect> effectMatcher, + VerificationMode verification) { + ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class); + verify(mVibrator, verification).vibrate(eq(Process.SYSTEM_UID), + eq(PackageManagerService.PLATFORM_PACKAGE_NAME), argThat(effectMatcher), + anyString(), captor.capture()); + assertEquals(0, (captor.getValue().getAllFlags() + & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)); } private void verifyStopVibrate() { @@ -761,11 +764,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); - VibrationEffect effect = VibrationEffect.createWaveform(r.getVibration(), -1); - - verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(), - eq(effect), anyString(), - (AudioAttributes) anyObject()); + verifyDelayedVibrate(r.getVibration()); assertTrue(r.isInterruptive()); assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @@ -800,8 +799,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); - verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(), - eq(FALLBACK_VIBRATION), anyString(), (AudioAttributes) anyObject()); + verifyDelayedVibrate( + mService.getVibratorHelper().createFallbackVibration(/* insistent= */ false)); verify(mRingtonePlayer, never()).playAsync (anyObject(), anyObject(), anyBoolean(), anyObject()); assertTrue(r.isInterruptive()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java index 6a8f602a8350..5eed30be9279 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java @@ -37,6 +37,7 @@ import android.content.pm.PackageManager; import android.graphics.Color; import android.os.Build; import android.os.UserHandle; +import android.os.Vibrator; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.telecom.TelecomManager; @@ -63,6 +64,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { @Mock TelecomManager mTm; @Mock RankingHandler handler; @Mock PackageManager mPm; + @Mock Vibrator mVibrator; private final String callPkg = "com.android.server.notification"; private final int callUid = 10; @@ -98,6 +100,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { when(mContext.getContentResolver()).thenReturn(getContext().getContentResolver()); when(mContext.getPackageManager()).thenReturn(mPm); when(mContext.getSystemService(eq(Context.TELECOM_SERVICE))).thenReturn(mTm); + when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator); when(mContext.getString(anyInt())).thenCallRealMethod(); when(mContext.getColor(anyInt())).thenCallRealMethod(); when(mTm.getDefaultDialerPackage()).thenReturn(callPkg); 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 c33287c57377..c480258c7fd9 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -7223,6 +7223,47 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mNotificationRecordLogger.event(0)); } + /** + * When something is bubble'd and the bubble is dismissed, but the notification is still + * visible, clicking on the notification shouldn't auto-cancel it because clicking on + * it will produce a bubble. + */ + @Test + public void testNotificationBubbles_bubbleStays_whenClicked_afterBubbleDismissed() + throws Exception { + setUpPrefsForBubbles(PKG, mUid, + true /* global */, + BUBBLE_PREFERENCE_ALL /* app */, + true /* channel */); + + // GIVEN a notification that has the auto cancels flag (cancel on click) and is a bubble + final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel); + nr.getSbn().getNotification().flags |= FLAG_BUBBLE | FLAG_AUTO_CANCEL; + nr.setAllowBubble(true); + mService.addNotification(nr); + + // And the bubble is dismissed + mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), + false /* isBubble */, 0 /* bubbleFlags */); + waitForIdle(); + assertTrue(nr.isFlagBubbleRemoved()); + + // WHEN we click the notification + final NotificationVisibility nv = NotificationVisibility.obtain(nr.getKey(), 1, 2, true); + mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(), + nr.getKey(), nv); + waitForIdle(); + + // THEN the bubble should still exist + StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG); + assertEquals(1, notifsAfter.length); + + // Check we got the click log + assertEquals(1, mNotificationRecordLogger.numCalls()); + assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLICKED, + mNotificationRecordLogger.event(0)); + } + @Test public void testLoadDefaultApprovedServices_emptyResources() { TestableResources tr = mContext.getOrCreateTestableResources(); @@ -7770,8 +7811,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { inOrder.verify(child).recordDismissalSentiment(anyInt()); } - // TODO (b/171418004): renable after app outreach - /*@Test + @Test public void testImmutableBubbleIntent() throws Exception { when(mAmi.getPendingIntentFlags(pi1)) .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT); @@ -7786,7 +7826,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } catch (IllegalArgumentException e) { // good } - }*/ + } @Test public void testMutableBubbleIntent() throws Exception { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 38c470da5029..49b63863fec7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -62,6 +62,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.UserHandle; +import android.os.Vibrator; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.StatusBarNotification; @@ -82,7 +83,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; -import java.util.Arrays; @SmallTest @RunWith(AndroidJUnit4.class) @@ -91,6 +91,7 @@ public class NotificationRecordTest extends UiServiceTestCase { private final Context mMockContext = mock(Context.class); @Mock private PackageManager mPm; @Mock private ContentResolver mContentResolver; + @Mock private Vibrator mVibrator; private final String mPkg = PKG_O; private final int uid = 9583; @@ -122,6 +123,7 @@ public class NotificationRecordTest extends UiServiceTestCase { public void setUp() { MockitoAnnotations.initMocks(this); + when(mMockContext.getSystemService(eq(Vibrator.class))).thenReturn(mVibrator); when(mMockContext.getResources()).thenReturn(getContext().getResources()); when(mMockContext.getPackageManager()).thenReturn(mPm); when(mMockContext.getContentResolver()).thenReturn(mContentResolver); @@ -203,6 +205,26 @@ public class NotificationRecordTest extends UiServiceTestCase { return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid); } + + private StatusBarNotification getInsistentNotification(boolean defaultVibration) { + final Builder builder = new Builder(mMockContext) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setPriority(Notification.PRIORITY_HIGH); + int defaults = 0; + if (defaultVibration) { + defaults |= Notification.DEFAULT_VIBRATE; + } else { + builder.setVibrate(CUSTOM_VIBRATION); + channel.setVibrationPattern(CUSTOM_CHANNEL_VIBRATION); + } + builder.setDefaults(defaults); + builder.setFlag(Notification.FLAG_INSISTENT, true); + + Notification n = builder.build(); + return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid); + } + private StatusBarNotification getMessagingStyleNotification() { return getMessagingStyleNotification(mPkg); } @@ -309,7 +331,8 @@ public class NotificationRecordTest extends UiServiceTestCase { false /* lights */, false /* defaultLights */, null /* group */); NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); - assertEquals(CUSTOM_VIBRATION, record.getVibration()); + assertEquals(VibratorHelper.createWaveformVibration( + CUSTOM_VIBRATION, /* insistent= */ false), record.getVibration()); } @Test @@ -322,7 +345,8 @@ public class NotificationRecordTest extends UiServiceTestCase { false /* lights */, false /* defaultLights */, null /* group */); NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); - assertTrue(!Arrays.equals(CUSTOM_VIBRATION, record.getVibration())); + assertNotEquals(VibratorHelper.createWaveformVibration( + CUSTOM_VIBRATION, /* insistent= */ false), record.getVibration()); } @Test @@ -334,7 +358,18 @@ public class NotificationRecordTest extends UiServiceTestCase { false /* lights */, false /* defaultLights */, null /* group */); NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); - assertEquals(CUSTOM_CHANNEL_VIBRATION, record.getVibration()); + assertEquals(VibratorHelper.createWaveformVibration( + CUSTOM_CHANNEL_VIBRATION, /* insistent= */ false), record.getVibration()); + } + + @Test + public void testVibration_insistent_createsInsistentVibrationEffect() { + channel.enableVibration(true); + StatusBarNotification sbn = getInsistentNotification(false /* defaultBuzz */); + + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + assertEquals(VibratorHelper.createWaveformVibration( + CUSTOM_CHANNEL_VIBRATION, /* insistent= */ true), record.getVibration()); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java index b83f9f266ad7..4217881495b1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java @@ -41,6 +41,7 @@ import android.media.AudioAttributes; import android.net.Uri; import android.os.Build; import android.os.UserHandle; +import android.os.Vibrator; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.testing.TestableContentResolver; @@ -84,6 +85,7 @@ public class RankingHelperTest extends UiServiceTestCase { @Mock Context mContext; @Mock ZenModeHelper mMockZenModeHelper; @Mock RankingConfig mConfig; + @Mock Vibrator mVibrator; private NotificationManager.Policy mTestNotificationPolicy; private Notification mNotiGroupGSortA; @@ -125,6 +127,7 @@ public class RankingHelperTest extends UiServiceTestCase { InstrumentationRegistry.getContext().getContentResolver()); when(mContext.getPackageManager()).thenReturn(mPm); when(mContext.getApplicationInfo()).thenReturn(legacy); + when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator); TestableContentResolver contentResolver = getContext().getContentResolver(); contentResolver.setFallbackToExisting(false); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java new file mode 100644 index 000000000000..c77a474e032c --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.when; + +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.UiServiceTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class VibratorHelperTest extends UiServiceTestCase { + + private static final long[] CUSTOM_PATTERN = new long[] { 100, 200, 300, 400 }; + + @Mock private Vibrator mVibrator; + + VibratorHelper mVibratorHelper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + getContext().addMockSystemService(Vibrator.class, mVibrator); + + mVibratorHelper = new VibratorHelper(getContext()); + } + + @Test + public void createWaveformVibration_insistent_createsRepeatingVibration() { + assertRepeatingVibration( + VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ true)); + } + + @Test + public void createWaveformVibration_nonInsistent_createsSingleShotVibration() { + assertSingleVibration( + VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ false)); + } + + @Test + public void createWaveformVibration_invalidPattern_returnsNullAndDoesNotCrash() { + assertNull(VibratorHelper.createWaveformVibration(null, false)); + assertNull(VibratorHelper.createWaveformVibration(new long[0], false)); + assertNull(VibratorHelper.createWaveformVibration(new long[] { 0, 0 }, false)); + } + + @Test + public void createVibration_insistent_createsRepeatingVibration() { + when(mVibrator.hasFrequencyControl()).thenReturn(false); + assertRepeatingVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ true)); + assertRepeatingVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ true)); + + when(mVibrator.hasFrequencyControl()).thenReturn(true); + assertRepeatingVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ true)); + assertRepeatingVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ true)); + } + + @Test + public void createVibration_nonInsistent_createsSingleShotVibration() { + when(mVibrator.hasFrequencyControl()).thenReturn(false); + assertSingleVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ false)); + assertSingleVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ false)); + + when(mVibrator.hasFrequencyControl()).thenReturn(true); + assertSingleVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ false)); + assertSingleVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ false)); + } + + private void assertRepeatingVibration(VibrationEffect effect) { + assertTrue(getRepeatIndex(effect) >= 0); + } + + private void assertSingleVibration(VibrationEffect effect) { + assertEquals(-1, getRepeatIndex(effect)); + } + + private static int getRepeatIndex(VibrationEffect effect) { + assertTrue("Unknown vibration effect " + effect, + effect instanceof VibrationEffect.Composed); + return ((VibrationEffect.Composed) effect).getRepeatIndex(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index aed3f52bd563..821683043804 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -26,6 +26,7 @@ import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; @@ -546,7 +547,7 @@ public class ActivityRecordTests extends WindowTestsBase { } @Test - public void ignoreRequestedOrientationInSplitWindows() { + public void ignoreRequestedOrientationForResizableInSplitWindows() { final ActivityRecord activity = createActivityWith2LevelTask(); final Task task = activity.getTask(); final Task rootTask = activity.getRootTask(); @@ -578,13 +579,45 @@ public class ActivityRecordTests extends WindowTestsBase { } task.setBounds(bounds); + final int activityCurOrientation = activity.getConfiguration().orientation; + // Requests orientation that's different from its bounds. - activity.setRequestedOrientation( - isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE); + activity.setRequestedOrientation(activityCurOrientation == ORIENTATION_LANDSCAPE + ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE); - // Asserts it has orientation derived requested orientation (fixed orientation letterbox). - assertEquals(isScreenPortrait ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE, - activity.getConfiguration().orientation); + // Asserts fixed orientation request is ignored, and the orientation is not changed + // (fill Task). + assertEquals(activityCurOrientation, activity.getConfiguration().orientation); + assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio()); + } + + @Test + public void respectRequestedOrientationForNonResizableInSplitWindows() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateParentTask(true).setCreateActivity(true).build(); + final Task rootTask = task.getRootTask(); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setParentTask(task) + .setOnTop(true) + .setResizeMode(RESIZE_MODE_UNRESIZEABLE) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + + // Task in landscape. + rootTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + task.setBounds(0, 0, 1000, 500); + assertEquals(ORIENTATION_LANDSCAPE, task.getConfiguration().orientation); + + // Asserts fixed orientation request is respected, and the orientation is not changed. + assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation); + + // Clear size compat. + activity.clearSizeCompatMode(); + activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); + activity.mDisplayContent.sendNewConfiguration(); + + // Relaunching the app should still respect the orientation request. + assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation); assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio()); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 6b7fc2fb4b7d..8fb805b543e2 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -4346,7 +4346,7 @@ public class CarrierConfigManager { "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.deferred\"", "+g.gsma.rcs.cpm.pager-large", "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"", - "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"", + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.filetransfer\"", "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\"", "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.ftsms\"", "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callcomposer\"", |