diff options
55 files changed, 957 insertions, 562 deletions
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java index a9f720ac2ba0..515ddc8d1d49 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java @@ -80,7 +80,7 @@ public class BenchmarkRunner { private void prepareForNextRun() { SystemClock.sleep(COOL_OFF_PERIOD_MS); - ShellHelper.runShellCommand("am wait-for-broadcast-idle"); + ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers"); mStartTimeNs = System.nanoTime(); mPausedDurationNs = 0; } @@ -102,7 +102,7 @@ public class BenchmarkRunner { * to avoid unnecessary waiting. */ public void resumeTiming() { - ShellHelper.runShellCommand("am wait-for-broadcast-idle"); + ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers"); resumeTimer(); } diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java index 19a47669b0dd..6dba5b3517f7 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java @@ -1541,7 +1541,8 @@ public class UserLifecycleTests { private void waitForBroadcastIdle() { try { - ShellHelper.runShellCommandWithTimeout("am wait-for-broadcast-idle", TIMEOUT_IN_SECOND); + ShellHelper.runShellCommandWithTimeout( + "am wait-for-broadcast-idle --flush-broadcast-loopers", TIMEOUT_IN_SECOND); } catch (TimeoutException e) { Log.e(TAG, "Ending waitForBroadcastIdle because it is taking too long", e); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index df9257c1b18a..5c1b3ee0134b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5006,12 +5006,6 @@ public class Notification implements Parcelable return mUserExtras; } - private Bundle getAllExtras() { - final Bundle saveExtras = (Bundle) mUserExtras.clone(); - saveExtras.putAll(mN.extras); - return saveExtras; - } - /** * Add an action to this notification. Actions are typically displayed by * the system as a button adjacent to the notification content. @@ -6617,9 +6611,16 @@ public class Notification implements Parcelable + " vs bubble: " + mN.mBubbleMetadata.getShortcutId()); } - // first, add any extras from the calling code + // Adds any new extras provided by the user. if (mUserExtras != null) { - mN.extras = getAllExtras(); + final Bundle saveExtras = (Bundle) mUserExtras.clone(); + if (SystemProperties.getBoolean( + "persist.sysui.notification.builder_extras_override", false)) { + mN.extras.putAll(saveExtras); + } else { + saveExtras.putAll(mN.extras); + mN.extras = saveExtras; + } } mN.creationTime = System.currentTimeMillis(); diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 776e34bb4792..385fd509757b 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -24,9 +24,11 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserIdInt; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; +import android.compat.annotation.LoggingOnly; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -49,6 +51,7 @@ import android.util.Slog; import android.view.KeyEvent; import android.view.View; +import com.android.internal.compat.IPlatformCompat; import com.android.internal.statusbar.AppClipsServiceConnector; import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.IStatusBarService; @@ -170,6 +173,8 @@ public class StatusBarManager { public @interface Disable2Flags {} // LINT.ThenChange(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt) + private static final String TAG = "StatusBarManager"; + /** * Default disable flags for setup * @@ -572,13 +577,13 @@ public class StatusBarManager { private static final long MEDIA_CONTROL_SESSION_ACTIONS = 203800354L; /** - * Media controls based on {@link android.app.Notification.MediaStyle} notifications will be - * required to include a non-empty title, either in the {@link android.media.MediaMetadata} or + * Media controls based on {@link android.app.Notification.MediaStyle} notifications should + * include a non-empty title, either in the {@link android.media.MediaMetadata} or * notification title. */ @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - private static final long MEDIA_CONTROL_REQUIRES_TITLE = 274775190L; + @LoggingOnly + private static final long MEDIA_CONTROL_BLANK_TITLE = 274775190L; @UnsupportedAppUsage private Context mContext; @@ -586,6 +591,9 @@ public class StatusBarManager { @UnsupportedAppUsage private IBinder mToken = new Binder(); + private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + @UnsupportedAppUsage StatusBarManager(Context context) { mContext = context; @@ -597,7 +605,7 @@ public class StatusBarManager { mService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); if (mService == null) { - Slog.w("StatusBarManager", "warning: no STATUS_BAR_SERVICE"); + Slog.w(TAG, "warning: no STATUS_BAR_SERVICE"); } } return mService; @@ -1226,18 +1234,22 @@ public class StatusBarManager { } /** - * Checks whether the given package must include a non-empty title for its media controls. + * Log that the given package has posted media controls with a blank title * * @param packageName App posting media controls - * @param user Current user handle - * @return true if the app is required to provide a non-empty title + * @param userId Current user ID + * @throws RuntimeException if there is an error reporting the change * * @hide */ - @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG, - android.Manifest.permission.LOG_COMPAT_CHANGE}) - public static boolean isMediaTitleRequiredForApp(String packageName, UserHandle user) { - return CompatChanges.isChangeEnabled(MEDIA_CONTROL_REQUIRES_TITLE, packageName, user); + public void logBlankMediaTitle(String packageName, @UserIdInt int userId) + throws RuntimeException { + try { + mPlatformCompat.reportChangeByPackageName(MEDIA_CONTROL_BLANK_TITLE, packageName, + userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 6f2a915cee46..3f4013908612 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -37,7 +37,6 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.preference.VolumePreference.VolumeStore; -import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.System; @@ -47,7 +46,6 @@ import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import com.android.internal.annotations.GuardedBy; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.os.SomeArgs; import java.util.concurrent.TimeUnit; @@ -295,14 +293,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba if (zenMuted) { mSeekBar.setProgress(mLastAudibleStreamVolume, true); } else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { - /** - * the first variable above is preserved and the conditions below are made explicit - * so that when user attempts to slide the notification seekbar out of vibrate the - * seekbar doesn't wrongly snap back to 0 when the streams aren't aliased - */ - if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false) - || mStreamType == AudioManager.STREAM_RING + // For ringer-mode affected streams, show volume as zero when ringermode is vibrate + if (mStreamType == AudioManager.STREAM_RING || (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) { mSeekBar.setProgress(0, true); } @@ -397,9 +389,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba // set the time of stop volume if ((mStreamType == AudioManager.STREAM_VOICE_CALL || mStreamType == AudioManager.STREAM_RING - || (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false) - && mStreamType == AudioManager.STREAM_NOTIFICATION) + || mStreamType == AudioManager.STREAM_NOTIFICATION || mStreamType == AudioManager.STREAM_ALARM)) { sStopVolumeTime = java.lang.System.currentTimeMillis(); } @@ -686,10 +676,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } private void updateVolumeSlider(int streamType, int streamValue) { - final boolean streamMatch = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false) - && mNotificationOrRing ? isNotificationOrRing(streamType) : - streamType == mStreamType; + final boolean streamMatch = (streamType == mStreamType); if (mSeekBar != null && streamMatch && streamValue != -1) { final boolean muted = mAudioManager.isStreamMute(mStreamType) || streamValue == 0; diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java index 095189ad03a7..67ac811287cb 100644 --- a/core/java/android/view/SurfaceControlRegistry.java +++ b/core/java/android/view/SurfaceControlRegistry.java @@ -62,7 +62,6 @@ public class SurfaceControlRegistry { private static class DefaultReporter implements Reporter { public void onMaxLayersExceeded(WeakHashMap<SurfaceControl, Long> surfaceControls, int limit, PrintWriter pw) { - final int size = Math.min(surfaceControls.size(), limit); final long now = SystemClock.elapsedRealtime(); final ArrayList<Map.Entry<SurfaceControl, Long>> entries = new ArrayList<>(); for (Map.Entry<SurfaceControl, Long> entry : surfaceControls.entrySet()) { @@ -71,6 +70,7 @@ public class SurfaceControlRegistry { // Sort entries by time registered when dumping // TODO: Or should it sort by name? entries.sort((o1, o2) -> (int) (o1.getValue() - o2.getValue())); + final int size = Math.min(entries.size(), limit); pw.println("SurfaceControlRegistry"); pw.println("----------------------"); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 441636d9a2d6..f1cde3b4bb7e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -7734,13 +7734,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); boolean handled = false; - final ListenerInfo li = mListenerInfo; + final OnLongClickListener listener = + mListenerInfo == null ? null : mListenerInfo.mOnLongClickListener; boolean shouldPerformHapticFeedback = true; - if (li != null && li.mOnLongClickListener != null) { - handled = li.mOnLongClickListener.onLongClick(View.this); + if (listener != null) { + handled = listener.onLongClick(View.this); if (handled) { - shouldPerformHapticFeedback = - li.mOnLongClickListener.onLongClickUseDefaultHapticFeedback(View.this); + shouldPerformHapticFeedback = listener.onLongClickUseDefaultHapticFeedback( + View.this); } } if (!handled) { diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 7ad2a6898fb7..8135f9cd2f46 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -549,11 +549,6 @@ public final class SystemUiDeviceConfigFlags { "task_manager_inform_job_scheduler_of_pending_app_stop"; /** - * (boolean) Whether to show notification volume control slider separate from ring. - */ - public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification"; - - /** * (boolean) Whether widget provider info would be saved to / loaded from system persistence * layer as opposed to individual manifests in respective apps. */ diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index c5b00c9bfb22..eba7f587bb8b 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -33,6 +33,8 @@ import static android.app.Notification.EXTRA_MESSAGING_PERSON; import static android.app.Notification.EXTRA_PEOPLE_LIST; import static android.app.Notification.EXTRA_PICTURE; import static android.app.Notification.EXTRA_PICTURE_ICON; +import static android.app.Notification.EXTRA_SUMMARY_TEXT; +import static android.app.Notification.EXTRA_TITLE; import static android.app.Notification.MessagingStyle.Message.KEY_DATA_URI; import static android.app.Notification.MessagingStyle.Message.KEY_SENDER_PERSON; import static android.app.Notification.MessagingStyle.Message.KEY_TEXT; @@ -76,6 +78,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemProperties; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; @@ -111,6 +114,9 @@ public class NotificationTest { @Before public void setUp() { mContext = InstrumentationRegistry.getContext(); + // TODO(b/169435530): remove this flag set once resolved. + SystemProperties.set("persist.sysui.notification.builder_extras_override", + Boolean.toString(false)); } @Test @@ -1481,6 +1487,107 @@ public class NotificationTest { Assert.assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second); } + // Ensures that extras in a Notification Builder can be updated. + @Test + public void testExtras_cachedExtrasOverwrittenByUserProvided() { + // Sets the flag to new state. + // TODO(b/169435530): remove this set value once resolved. + SystemProperties.set("persist.sysui.notification.builder_extras_override", + Boolean.toString(true)); + Bundle extras = new Bundle(); + extras.putCharSequence(EXTRA_TITLE, "test title"); + extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text"); + + Notification.Builder builder = new Notification.Builder(mContext, "test id") + .addExtras(extras); + + Notification notification = builder.build(); + assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo( + "test title"); + assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo( + "summary text"); + + extras.putCharSequence(EXTRA_TITLE, "new title"); + builder.addExtras(extras); + notification = builder.build(); + assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo( + "new title"); + assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo( + "summary text"); + } + + // Ensures that extras in a Notification Builder can be updated by an extender. + @Test + public void testExtras_cachedExtrasOverwrittenByExtender() { + // Sets the flag to new state. + // TODO(b/169435530): remove this set value once resolved. + SystemProperties.set("persist.sysui.notification.builder_extras_override", + Boolean.toString(true)); + Notification.CarExtender extender = new Notification.CarExtender().setColor(1234); + + Notification notification = new Notification.Builder(mContext, "test id") + .extend(extender).build(); + + extender.setColor(5678); + + Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build(); + + Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification); + assertThat(recoveredExtender.getColor()).isEqualTo(5678); + } + + // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated. + // TODO(b/169435530): remove this test once resolved. + @Test + public void testExtras_cachedExtrasOverwrittenByUserProvidedOld() { + // Sets the flag to old state. + SystemProperties.set("persist.sysui.notification.builder_extras_override", + Boolean.toString(false)); + + Bundle extras = new Bundle(); + extras.putCharSequence(EXTRA_TITLE, "test title"); + extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text"); + + Notification.Builder builder = new Notification.Builder(mContext, "test id") + .addExtras(extras); + + Notification notification = builder.build(); + assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo( + "test title"); + assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo( + "summary text"); + + extras.putCharSequence(EXTRA_TITLE, "new title"); + builder.addExtras(extras); + notification = builder.build(); + assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo( + "test title"); + assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo( + "summary text"); + } + + // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated + // by an extender. + // TODO(b/169435530): remove this test once resolved. + @Test + public void testExtras_cachedExtrasOverwrittenByExtenderOld() { + // Sets the flag to old state. + SystemProperties.set("persist.sysui.notification.builder_extras_override", + Boolean.toString(false)); + + Notification.CarExtender extender = new Notification.CarExtender().setColor(1234); + + Notification notification = new Notification.Builder(mContext, "test id") + .extend(extender).build(); + + extender.setColor(5678); + + Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build(); + + Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification); + assertThat(recoveredExtender.getColor()).isEqualTo(1234); + } + private void assertValid(Notification.Colors c) { // Assert that all colors are populated assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index fbdbd3e61d92..7b37d5947f17 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.activityembedding; +import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; @@ -111,6 +112,11 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) { return false; } + final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); + if (options != null && options.getType() == ANIM_SCENE_TRANSITION) { + // Scene-transition will be handled by app side. + return false; + } // Start ActivityEmbedding animation. mTransitionCallbacks.put(transition, finishCallback); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java index ac6e4c2a6521..53683c67d825 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java @@ -54,14 +54,6 @@ public class TabletopModeController implements DevicePostureController.OnDevicePostureChangedListener, DisplayController.OnDisplaysChangedListener { /** - * When {@code true}, floating windows like PiP would auto move to the position - * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode. - */ - private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP = - SystemProperties.getBoolean( - "persist.wm.debug.enable_move_floating_window_in_tabletop", true); - - /** * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled, * {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise. * See also {@link #getPreferredHalfInTabletopMode()}. @@ -162,14 +154,6 @@ public class TabletopModeController implements } } - /** - * @return {@code true} if floating windows like PiP would auto move to the position - * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode. - */ - public boolean enableMoveFloatingWindowInTabletop() { - return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP; - } - /** @return Preferred half for floating windows like PiP when in tabletop mode. */ @PreferredTabletopHalf public int getPreferredHalfInTabletopMode() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 63181da46b02..6a861ce97431 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -677,7 +677,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb }); mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> { - if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return; final String tag = "tabletop-mode"; if (!isInTabletopMode) { mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 5c9709c756f7..f35eda6caef0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -371,7 +371,8 @@ public class RecentTasksController implements TaskStackListenerCallback, * Find the background task that match the given component. */ @Nullable - public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName) { + public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName, + int userId) { if (componentName == null) { return null; } @@ -383,7 +384,7 @@ public class RecentTasksController implements TaskStackListenerCallback, if (task.isVisible) { continue; } - if (componentName.equals(task.baseIntent.getComponent())) { + if (componentName.equals(task.baseIntent.getComponent()) && userId == task.userId) { return task; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 34701f1db7b7..ea33a1f1b56d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -723,7 +723,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, // in the background with priority. final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional .map(recentTasks -> recentTasks.findTaskInBackground( - intent.getIntent().getComponent())) + intent.getIntent().getComponent(), userId1)) .orElse(null); if (taskInfo != null) { startTask(taskInfo.taskId, position, options); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 9189d3dd0327..fb17d8799bda 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -223,7 +223,7 @@ public class SplitScreenControllerTests extends ShellTestCase { doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); // Put the same component into a task in the background ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo(); - doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any()); + doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null); @@ -247,7 +247,7 @@ public class SplitScreenControllerTests extends ShellTestCase { SPLIT_POSITION_BOTTOM_OR_RIGHT); // Put the same component into a task in the background doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks) - .findTaskInBackground(any()); + .findTaskInBackground(any(), anyInt()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null); diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index c4f09cecfa1f..f911d35757f6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -111,6 +111,9 @@ public class DreamBackend { public static final int COMPLICATION_TYPE_SMARTSPACE = 7; public static final int COMPLICATION_TYPE_MEDIA_ENTRY = 8; + private static final int SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT = 1; + private static final int LOCKSCREEN_SHOW_CONTROLS_DEFAULT = 0; + private final Context mContext; private final IDreamManager mDreamManager; private final DreamInfoComparator mComparator; @@ -311,8 +314,14 @@ public class DreamBackend { /** Gets whether home controls button is enabled on the dream */ private boolean getHomeControlsEnabled() { - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, 1) == 1; + return Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, + LOCKSCREEN_SHOW_CONTROLS_DEFAULT) == 1 + && Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, + SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT) == 1; } /** diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java index 22ec12d44d6d..2edf403e5c00 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; +import android.provider.Settings; import org.junit.After; import org.junit.Before; @@ -84,6 +85,7 @@ public final class DreamBackendTest { @Test public void testComplicationsEnabledByDefault() { + setControlsEnabledOnLockscreen(true); assertThat(mBackend.getComplicationsEnabled()).isTrue(); assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn( SUPPORTED_DREAM_COMPLICATIONS_LIST); @@ -91,6 +93,7 @@ public final class DreamBackendTest { @Test public void testEnableComplicationExplicitly() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(true); assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn( SUPPORTED_DREAM_COMPLICATIONS_LIST); @@ -99,6 +102,7 @@ public final class DreamBackendTest { @Test public void testDisableComplications() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(false); assertThat(mBackend.getEnabledComplications()) .containsExactly(COMPLICATION_TYPE_HOME_CONTROLS); @@ -107,6 +111,7 @@ public final class DreamBackendTest { @Test public void testHomeControlsDisabled_ComplicationsEnabled() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(true); mBackend.setHomeControlsEnabled(false); // Home controls should not be enabled, only date and time. @@ -118,6 +123,7 @@ public final class DreamBackendTest { @Test public void testHomeControlsDisabled_ComplicationsDisabled() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(false); mBackend.setHomeControlsEnabled(false); assertThat(mBackend.getEnabledComplications()).isEmpty(); @@ -125,9 +131,9 @@ public final class DreamBackendTest { @Test public void testHomeControlsEnabled_ComplicationsDisabled() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(false); mBackend.setHomeControlsEnabled(true); - // Home controls should not be enabled, only date and time. final List<Integer> enabledComplications = Collections.singletonList(COMPLICATION_TYPE_HOME_CONTROLS); assertThat(mBackend.getEnabledComplications()) @@ -136,9 +142,9 @@ public final class DreamBackendTest { @Test public void testHomeControlsEnabled_ComplicationsEnabled() { + setControlsEnabledOnLockscreen(true); mBackend.setComplicationsEnabled(true); mBackend.setHomeControlsEnabled(true); - // Home controls should not be enabled, only date and time. final List<Integer> enabledComplications = Arrays.asList( COMPLICATION_TYPE_HOME_CONTROLS, @@ -148,4 +154,26 @@ public final class DreamBackendTest { assertThat(mBackend.getEnabledComplications()) .containsExactlyElementsIn(enabledComplications); } + + @Test + public void testHomeControlsEnabled_lockscreenDisabled() { + setControlsEnabledOnLockscreen(false); + mBackend.setComplicationsEnabled(true); + mBackend.setHomeControlsEnabled(true); + // Home controls should not be enabled, only date and time. + final List<Integer> enabledComplications = + Arrays.asList( + COMPLICATION_TYPE_DATE, + COMPLICATION_TYPE_TIME + ); + assertThat(mBackend.getEnabledComplications()) + .containsExactlyElementsIn(enabledComplications); + } + + private void setControlsEnabledOnLockscreen(boolean enabled) { + Settings.Secure.putInt( + mContext.getContentResolver(), + Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, + enabled ? 1 : 0); + } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 8dd2c39e7a58..465b73e6de19 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -272,6 +272,7 @@ class AnimatableClockView @JvmOverloads constructor( color = lockScreenColor, animate = isAnimationEnabled, duration = APPEAR_ANIM_DURATION, + interpolator = Interpolators.EMPHASIZED_DECELERATE, delay = 0, onAnimationEnd = null ) @@ -562,7 +563,7 @@ class AnimatableClockView @JvmOverloads constructor( private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm" private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm" private const val DOZE_ANIM_DURATION: Long = 300 - private const val APPEAR_ANIM_DURATION: Long = 350 + private const val APPEAR_ANIM_DURATION: Long = 833 private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500 private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000 private const val COLOR_ANIM_DURATION: Long = 400 diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index cad2c162a589..4b7968953420 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -96,6 +96,7 @@ <!-- additional offset for clock switch area items --> <dimen name="small_clock_height">114dp</dimen> + <dimen name="small_clock_padding_top">28dp</dimen> <dimen name="clock_padding_start">28dp</dimen> <dimen name="below_clock_padding_start">32dp</dimen> <dimen name="below_clock_padding_end">16dp</dimen> diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml index 1112bcdbd14d..9b1fa23081b4 100644 --- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -85,7 +85,7 @@ android:layout_height="@dimen/volume_ringer_drawer_icon_size" android:layout_gravity="center" android:tint="?android:attr/textColorPrimary" - android:src="@drawable/ic_volume_ringer_mute" /> + android:src="@drawable/ic_speaker_mute" /> </FrameLayout> @@ -102,7 +102,7 @@ android:layout_height="@dimen/volume_ringer_drawer_icon_size" android:layout_gravity="center" android:tint="?android:attr/textColorPrimary" - android:src="@drawable/ic_volume_ringer" /> + android:src="@drawable/ic_speaker_on" /> </FrameLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a62dead7834b..8d3ba364da06 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -716,7 +716,7 @@ <!-- Minimum margin between clock and status bar --> <dimen name="keyguard_clock_top_margin">18dp</dimen> <!-- The amount to shift the clocks during a small/large transition --> - <dimen name="keyguard_clock_switch_y_shift">10dp</dimen> + <dimen name="keyguard_clock_switch_y_shift">14dp</dimen> <!-- When large clock is showing, offset the smartspace by this amount --> <dimen name="keyguard_smartspace_top_offset">12dp</dimen> <!-- With the large clock, move up slightly from the center --> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index e54d4739dc97..d9d64ad5a893 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -1,5 +1,8 @@ package com.android.keyguard; +import static android.view.View.ALPHA; +import static android.view.View.TRANSLATION_Y; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -35,11 +38,12 @@ public class KeyguardClockSwitch extends RelativeLayout { private static final String TAG = "KeyguardClockSwitch"; - private static final long CLOCK_OUT_MILLIS = 150; - private static final long CLOCK_IN_MILLIS = 200; - public static final long CLOCK_IN_START_DELAY_MILLIS = CLOCK_OUT_MILLIS / 2; - private static final long STATUS_AREA_START_DELAY_MILLIS = 50; - private static final long STATUS_AREA_MOVE_MILLIS = 350; + private static final long CLOCK_OUT_MILLIS = 133; + private static final long CLOCK_IN_MILLIS = 167; + public static final long CLOCK_IN_START_DELAY_MILLIS = 133; + private static final long STATUS_AREA_START_DELAY_MILLIS = 0; + private static final long STATUS_AREA_MOVE_UP_MILLIS = 967; + private static final long STATUS_AREA_MOVE_DOWN_MILLIS = 467; @IntDef({LARGE, SMALL}) @Retention(RetentionPolicy.SOURCE) @@ -66,6 +70,17 @@ public class KeyguardClockSwitch extends RelativeLayout { top + targetHeight); } + /** Returns a region for the small clock to position itself, based on the given parent. */ + public static Rect getSmallClockRegion(ViewGroup parent) { + int targetHeight = parent.getResources() + .getDimensionPixelSize(R.dimen.small_clock_text_size); + return new Rect( + parent.getLeft(), + parent.getTop(), + parent.getRight(), + parent.getTop() + targetHeight); + } + /** * Frame for small/large clocks */ @@ -90,7 +105,7 @@ public class KeyguardClockSwitch extends RelativeLayout { @VisibleForTesting AnimatorSet mClockInAnim = null; @VisibleForTesting AnimatorSet mClockOutAnim = null; - private ObjectAnimator mStatusAreaAnim = null; + private AnimatorSet mStatusAreaAnim = null; private int mClockSwitchYAmount; @VisibleForTesting boolean mChildrenAreLaidOut = false; @@ -172,13 +187,8 @@ public class KeyguardClockSwitch extends RelativeLayout { void updateClockTargetRegions() { if (mClock != null) { if (mSmallClockFrame.isLaidOut()) { - int targetHeight = getResources() - .getDimensionPixelSize(R.dimen.small_clock_text_size); - mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect( - mSmallClockFrame.getLeft(), - mSmallClockFrame.getTop(), - mSmallClockFrame.getRight(), - mSmallClockFrame.getTop() + targetHeight)); + Rect targetRegion = getSmallClockRegion(mSmallClockFrame); + mClock.getSmallClock().getEvents().onTargetRegionChanged(targetRegion); } if (mLargeClockFrame.isLaidOut()) { @@ -220,39 +230,44 @@ public class KeyguardClockSwitch extends RelativeLayout { mStatusAreaAnim = null; View in, out; - int direction = 1; - float statusAreaYTranslation; + float statusAreaYTranslation, clockInYTranslation, clockOutYTranslation; if (useLargeClock) { out = mSmallClockFrame; in = mLargeClockFrame; if (indexOfChild(in) == -1) addView(in, 0); - direction = -1; statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop() + mSmartspaceTopOffset; + clockInYTranslation = 0; + clockOutYTranslation = 0; // Small clock translation is handled with statusArea } else { in = mSmallClockFrame; out = mLargeClockFrame; statusAreaYTranslation = 0f; + clockInYTranslation = 0f; + clockOutYTranslation = mClockSwitchYAmount * -1f; - // Must remove in order for notifications to appear in the proper place + // Must remove in order for notifications to appear in the proper place, ideally this + // would happen after the out animation runs, but we can't guarantee that the + // nofications won't enter only after the out animation runs. removeView(out); } if (!animate) { out.setAlpha(0f); + out.setTranslationY(clockOutYTranslation); in.setAlpha(1f); - in.setVisibility(VISIBLE); + in.setTranslationY(clockInYTranslation); + in.setVisibility(View.VISIBLE); mStatusArea.setTranslationY(statusAreaYTranslation); return; } mClockOutAnim = new AnimatorSet(); mClockOutAnim.setDuration(CLOCK_OUT_MILLIS); - mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); + mClockOutAnim.setInterpolator(Interpolators.LINEAR); mClockOutAnim.playTogether( - ObjectAnimator.ofFloat(out, View.ALPHA, 0f), - ObjectAnimator.ofFloat(out, View.TRANSLATION_Y, 0, - direction * -mClockSwitchYAmount)); + ObjectAnimator.ofFloat(out, ALPHA, 0f), + ObjectAnimator.ofFloat(out, TRANSLATION_Y, clockOutYTranslation)); mClockOutAnim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { mClockOutAnim = null; @@ -264,8 +279,9 @@ public class KeyguardClockSwitch extends RelativeLayout { mClockInAnim = new AnimatorSet(); mClockInAnim.setDuration(CLOCK_IN_MILLIS); mClockInAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - mClockInAnim.playTogether(ObjectAnimator.ofFloat(in, View.ALPHA, 1f), - ObjectAnimator.ofFloat(in, View.TRANSLATION_Y, direction * mClockSwitchYAmount, 0)); + mClockInAnim.playTogether( + ObjectAnimator.ofFloat(in, ALPHA, 1f), + ObjectAnimator.ofFloat(in, TRANSLATION_Y, clockInYTranslation)); mClockInAnim.setStartDelay(CLOCK_IN_START_DELAY_MILLIS); mClockInAnim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { @@ -273,19 +289,22 @@ public class KeyguardClockSwitch extends RelativeLayout { } }); - mClockInAnim.start(); - mClockOutAnim.start(); - - mStatusAreaAnim = ObjectAnimator.ofFloat(mStatusArea, View.TRANSLATION_Y, - statusAreaYTranslation); - mStatusAreaAnim.setStartDelay(useLargeClock ? STATUS_AREA_START_DELAY_MILLIS : 0L); - mStatusAreaAnim.setDuration(STATUS_AREA_MOVE_MILLIS); - mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mStatusAreaAnim = new AnimatorSet(); + mStatusAreaAnim.setStartDelay(STATUS_AREA_START_DELAY_MILLIS); + mStatusAreaAnim.setDuration( + useLargeClock ? STATUS_AREA_MOVE_UP_MILLIS : STATUS_AREA_MOVE_DOWN_MILLIS); + mStatusAreaAnim.setInterpolator(Interpolators.EMPHASIZED); + mStatusAreaAnim.playTogether( + ObjectAnimator.ofFloat(mStatusArea, TRANSLATION_Y, statusAreaYTranslation), + ObjectAnimator.ofFloat(mSmallClockFrame, TRANSLATION_Y, statusAreaYTranslation)); mStatusAreaAnim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { mStatusAreaAnim = null; } }); + + mClockInAnim.start(); + mClockOutAnim.start(); mStatusAreaAnim.start(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt index ede62acb3255..a3f34ce7471d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt @@ -68,15 +68,15 @@ class CredentialPasswordView(context: Context, attrs: AttributeSet?) : var inputTopBound: Int var headerRightBound = right var headerTopBounds = top + var headerBottomBounds = bottom val subTitleBottom: Int = if (subtitleView.isGone) titleView.bottom else subtitleView.bottom val descBottom = if (descriptionView.isGone) subTitleBottom else descriptionView.bottom if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) { inputTopBound = (bottom - credentialInput.height) / 2 inputLeftBound = (right - left) / 2 headerRightBound = inputLeftBound - headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset) - - if (descriptionView.bottom > bottomInset) { + if (descriptionView.bottom > headerBottomBounds) { + headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset) credentialHeader.layout(left, headerTopBounds, headerRightBound, bottom) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java index a334c1ed2338..0bdc7f1dfb96 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java +++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java @@ -77,6 +77,10 @@ public class ComplicationTypesUpdater extends ConditionalCoreStartable { Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, settingsObserver, UserHandle.myUserId()); + mSecureSettings.registerContentObserverForUser( + Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, + settingsObserver, + UserHandle.myUserId()); settingsObserver.onChange(false); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 27cebc6458d9..efcaa723dd9b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -104,6 +104,16 @@ object Flags { val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim", teamfood = true) + // TODO(b/280783617): Tracking Bug + @Keep + @JvmField + val BUILDER_EXTRAS_OVERRIDE = + sysPropBooleanFlag( + 128, + "persist.sysui.notification.builder_extras_override", + default = false + ) + // 200 - keyguard/lockscreen // ** Flag retired ** // public static final BooleanFlag KEYGUARD_LAYOUT = @@ -136,7 +146,7 @@ object Flags { * the digits when the clock moves. */ @JvmField - val STEP_CLOCK_ANIMATION = unreleasedFlag(212, "step_clock_animation", teamfood = true) + val STEP_CLOCK_ANIMATION = releasedFlag(212, "step_clock_animation") /** * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository @@ -525,13 +535,6 @@ object Flags { val ENABLE_PIP_APP_ICON_OVERLAY = sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true) - // TODO(b/272110828): Tracking bug - @Keep - @JvmField - val ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP = - sysPropBooleanFlag( - 1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = true) - // TODO(b/273443374): Tracking Bug @Keep @JvmField val LOCKSCREEN_LIVE_WALLPAPER = @@ -615,8 +618,6 @@ object Flags { unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false) // 1500 - chooser aka sharesheet - // TODO(b/254512507): Tracking Bug - val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled") // 1700 - clipboard @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 5d3f5f2ee2c2..51a29b00f9db 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -19,7 +19,6 @@ package com.android.systemui.keyguard; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; -import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; @@ -30,13 +29,11 @@ import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_OLD_NONE; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionOldType; import static android.view.WindowManager.TransitionType; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.Service; @@ -116,6 +113,14 @@ public class KeyguardService extends Service { final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); final int taskId = taskInfo != null ? change.getTaskInfo().taskId : -1; + if (taskId != -1 && change.getParent() != null) { + final TransitionInfo.Change parentChange = info.getChange(change.getParent()); + if (parentChange != null && parentChange.getTaskInfo() != null) { + // Only adding the root task as the animation target. + continue; + } + } + final RemoteAnimationTarget target = TransitionUtil.newTarget(change, // wallpapers go into the "below" layer space info.getChanges().size() - i, @@ -123,13 +128,6 @@ public class KeyguardService extends Service { (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0, info, t, leashMap); - // Use hasAnimatingParent to mark the anything below root task - if (taskId != -1 && change.getParent() != null) { - final TransitionInfo.Change parentChange = info.getChange(change.getParent()); - if (parentChange != null && parentChange.getTaskInfo() != null) { - target.hasAnimatingParent = true; - } - } out.add(target); } return out.toArray(new RemoteAnimationTarget[out.size()]); @@ -173,18 +171,15 @@ public class KeyguardService extends Service { wrap(info, true /* wallpapers */, t, mLeashMap); final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0]; - // Sets the alpha to 0 for the opening root task for fade in animation. And since - // the fade in animation can only apply on the first opening app, so set alpha to 1 - // for anything else. - for (RemoteAnimationTarget target : apps) { - if (target.taskId != -1 - && target.mode == RemoteAnimationTarget.MODE_OPENING - && !target.hasAnimatingParent) { - t.setAlpha(target.leash, 0.0f); - } else { - t.setAlpha(target.leash, 1.0f); + // Set alpha back to 1 for the independent changes because we will be animating + // children instead. + for (TransitionInfo.Change chg : info.getChanges()) { + if (TransitionInfo.isIndependent(chg, info)) { + t.setAlpha(chg.getLeash(), 1.f); } } + initAlphaForAnimationTargets(t, apps); + initAlphaForAnimationTargets(t, wallpapers); t.apply(); synchronized (mFinishCallbacks) { mFinishCallbacks.put(transition, finishCallback); @@ -223,6 +218,14 @@ public class KeyguardService extends Service { // nothing, we'll just let it finish on its own I guess. } } + + private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t, + @NonNull RemoteAnimationTarget[] targets) { + for (RemoteAnimationTarget target : targets) { + if (target.mode != MODE_OPENING) continue; + t.setAlpha(target.leash, 0.f); + } + } }; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index f96f337d5cb2..122e25975837 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -812,8 +812,8 @@ class KeyguardUnlockAnimationController @Inject constructor( // Translate up from the bottom. surfaceBehindMatrix.setTranslate( - surfaceBehindRemoteAnimationTarget.localBounds.left.toFloat(), - surfaceBehindRemoteAnimationTarget.localBounds.top.toFloat() + + surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(), + surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() + surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt new file mode 100644 index 000000000000..641e20b4a3fc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import android.os.UserHandle +import android.provider.Settings +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyguard.shared.model.SettingsClockSize +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext + +@SysUISingleton +class KeyguardClockRepository +@Inject +constructor( + private val secureSettings: SecureSettings, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + + val selectedClockSize: Flow<SettingsClockSize> = + secureSettings + .observerFlow( + names = arrayOf(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK), + userId = UserHandle.USER_SYSTEM, + ) + .onStart { emit(Unit) } // Forces an initial update. + .map { getClockSize() } + + private suspend fun getClockSize(): SettingsClockSize { + return withContext(backgroundDispatcher) { + if ( + secureSettings.getIntForUser( + Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, + 1, + UserHandle.USER_CURRENT + ) == 1 + ) { + SettingsClockSize.DYNAMIC + } else { + SettingsClockSize.SMALL + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt new file mode 100644 index 000000000000..98f445c4419a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.KeyguardClockRepository +import com.android.systemui.keyguard.shared.model.SettingsClockSize +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** Encapsulates business-logic related to the keyguard clock. */ +@SysUISingleton +class KeyguardClockInteractor +@Inject +constructor( + repository: KeyguardClockRepository, +) { + val selectedClockSize: Flow<SettingsClockSize> = repository.selectedClockSize +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt new file mode 100644 index 000000000000..c6b0f58a0cd3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.shared.model + +enum class SettingsClockSize { + DYNAMIC, + SMALL, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt new file mode 100644 index 000000000000..57c32b3a56d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import android.view.View +import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.flow.collect + +/** Binder for the small clock view, large clock view and smartspace. */ +object KeyguardPreviewClockSmartspaceViewBinder { + + @JvmStatic + fun bind( + largeClockHostView: View, + smallClockHostView: View, + smartspace: View?, + viewModel: KeyguardPreviewClockSmartspaceViewModel, + ) { + largeClockHostView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.isLargeClockVisible.collect { largeClockHostView.isVisible = it } + } + } + + smallClockHostView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.isSmallClockVisible.collect { smallClockHostView.isVisible = it } + } + } + + smartspace?.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.smartSpaceTopPadding.collect { smartspace.setTopPadding(it) } + } + } + } + + private fun View.setTopPadding(padding: Int) { + setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 555a09baa5b7..4308d843c27a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -22,6 +22,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.res.Resources import android.graphics.Rect import android.hardware.display.DisplayManager import android.os.Bundle @@ -33,6 +34,7 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.FrameLayout +import androidx.core.view.isInvisible import com.android.keyguard.ClockEventController import com.android.keyguard.KeyguardClockSwitch import com.android.systemui.R @@ -40,7 +42,10 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockSmartspaceViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel +import com.android.systemui.plugins.ClockController import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.shared.clocks.DefaultClockController import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants @@ -60,6 +65,7 @@ constructor( @Application private val context: Context, @Main private val mainDispatcher: CoroutineDispatcher, @Main private val mainHandler: Handler, + private val clockSmartspaceViewModel: KeyguardPreviewClockSmartspaceViewModel, private val bottomAreaViewModel: KeyguardBottomAreaViewModel, displayManager: DisplayManager, private val windowManager: WindowManager, @@ -79,6 +85,7 @@ constructor( KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES, false, ) + /** [shouldHideClock] here means that we never create and bind the clock views */ private val shouldHideClock: Boolean = bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false) @@ -87,7 +94,8 @@ constructor( val surfacePackage: SurfaceControlViewHost.SurfacePackage get() = host.surfacePackage - private var clockView: View? = null + private lateinit var largeClockHostView: FrameLayout + private lateinit var smallClockHostView: FrameLayout private var smartSpaceView: View? = null private var colorOverride: Int? = null @@ -126,6 +134,12 @@ constructor( if (!shouldHideClock) { setUpClock(rootView) + KeyguardPreviewClockSmartspaceViewBinder.bind( + largeClockHostView, + smallClockHostView, + smartSpaceView, + clockSmartspaceViewModel, + ) } rootView.measure( @@ -205,11 +219,9 @@ constructor( smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView) val topPadding: Int = - with(context.resources) { - getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) + - getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) + - getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) - } + KeyguardPreviewClockSmartspaceViewModel.getLargeClockSmartspaceTopPadding( + context.resources + ) val startPadding: Int = with(context.resources) { @@ -284,10 +296,19 @@ constructor( } private fun setUpClock(parentView: ViewGroup) { + largeClockHostView = createLargeClockHostView() + largeClockHostView.isInvisible = true + parentView.addView(largeClockHostView) + + smallClockHostView = createSmallClockHostView(parentView.resources) + smallClockHostView.isInvisible = true + parentView.addView(smallClockHostView) + + // TODO (b/283465254): Move the listeners to KeyguardClockRepository val clockChangeListener = object : ClockRegistry.ClockChangeListener { override fun onCurrentClockChanged() { - onClockChanged(parentView) + onClockChanged() } } clockRegistry.registerClockChangeListener(clockChangeListener) @@ -317,62 +338,89 @@ constructor( disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) }) val layoutChangeListener = - object : View.OnLayoutChangeListener { - override fun onLayoutChange( - v: View, - left: Int, - top: Int, - right: Int, - bottom: Int, - oldLeft: Int, - oldTop: Int, - oldRight: Int, - oldBottom: Int - ) { - if (clockController.clock !is DefaultClockController) { - clockController.clock - ?.largeClock - ?.events - ?.onTargetRegionChanged( - KeyguardClockSwitch.getLargeClockRegion(parentView) - ) - } + View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + if (clockController.clock !is DefaultClockController) { + clockController.clock + ?.largeClock + ?.events + ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView)) } } - parentView.addOnLayoutChangeListener(layoutChangeListener) - disposables.add( DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) } ) - onClockChanged(parentView) + onClockChanged() + } + + private fun createLargeClockHostView(): FrameLayout { + val hostView = FrameLayout(context) + hostView.layoutParams = + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT, + ) + return hostView + } + + private fun createSmallClockHostView(resources: Resources): FrameLayout { + val hostView = FrameLayout(context) + val layoutParams = + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + resources.getDimensionPixelSize(R.dimen.small_clock_height) + ) + layoutParams.topMargin = + KeyguardPreviewClockSmartspaceViewModel.getStatusBarHeight(resources) + + resources.getDimensionPixelSize(R.dimen.small_clock_padding_top) + hostView.layoutParams = layoutParams + + hostView.setPaddingRelative( + resources.getDimensionPixelSize(R.dimen.clock_padding_start), + 0, + 0, + 0 + ) + hostView.clipChildren = false + return hostView } - private fun onClockChanged(parentView: ViewGroup) { + private fun onClockChanged() { val clock = clockRegistry.createCurrentClock() clockController.clock = clock colorOverride?.let { clock.events.onSeedColorChanged(it) } - clock.largeClock.events.onTargetRegionChanged( - KeyguardClockSwitch.getLargeClockRegion(parentView) - ) - - clockView?.let { parentView.removeView(it) } - clockView = - clock.largeClock.view.apply { - if (shouldHighlightSelectedAffordance) { - alpha = DIM_ALPHA - } - parentView.addView(this) - visibility = View.VISIBLE - } + updateLargeClock(clock) + updateSmallClock(clock) // Hide smart space if the clock has weather display; otherwise show it hideSmartspace(clock.largeClock.config.hasCustomWeatherDataDisplay) } + private fun updateLargeClock(clock: ClockController) { + clock.largeClock.events.onTargetRegionChanged( + KeyguardClockSwitch.getLargeClockRegion(largeClockHostView) + ) + if (shouldHighlightSelectedAffordance) { + clock.largeClock.view.alpha = DIM_ALPHA + } + largeClockHostView.removeAllViews() + largeClockHostView.addView(clock.largeClock.view) + } + + private fun updateSmallClock(clock: ClockController) { + clock.smallClock.events.onTargetRegionChanged( + KeyguardClockSwitch.getSmallClockRegion(smallClockHostView) + ) + if (shouldHighlightSelectedAffordance) { + clock.smallClock.view.alpha = DIM_ALPHA + } + smallClockHostView.removeAllViews() + smallClockHostView.addView(clock.smallClock.view) + } + companion object { private const val KEY_HOST_TOKEN = "host_token" private const val KEY_VIEW_WIDTH = "width" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt new file mode 100644 index 000000000000..00c603b04ccf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.content.Context +import android.content.res.Resources +import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.shared.model.SettingsClockSize +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** View model for the small clock view, large clock view and smartspace. */ +class KeyguardPreviewClockSmartspaceViewModel +@Inject +constructor( + @Application private val context: Context, + interactor: KeyguardClockInteractor, +) { + + val isLargeClockVisible: Flow<Boolean> = + interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC } + + val isSmallClockVisible: Flow<Boolean> = + interactor.selectedClockSize.map { it == SettingsClockSize.SMALL } + + val smartSpaceTopPadding: Flow<Int> = + interactor.selectedClockSize.map { + when (it) { + SettingsClockSize.DYNAMIC -> getLargeClockSmartspaceTopPadding(context.resources) + SettingsClockSize.SMALL -> getSmallClockSmartspaceTopPadding(context.resources) + } + } + + companion object { + fun getLargeClockSmartspaceTopPadding(resources: Resources): Int { + return with(resources) { + getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) + + getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) + + getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) + } + } + + fun getSmallClockSmartspaceTopPadding(resources: Resources): Int { + return with(resources) { + getStatusBarHeight(this) + + getDimensionPixelSize(R.dimen.small_clock_padding_top) + + getDimensionPixelSize(R.dimen.small_clock_height) + } + } + + fun getStatusBarHeight(resource: Resources): Int { + var result = 0 + val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android") + if (resourceId > 0) { + result = resource.getDimensionPixelSize(resourceId) + } + return result + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 1469d96dd3e8..bce334610f28 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -16,10 +16,12 @@ package com.android.systemui.media.controls.pipeline +import android.annotation.SuppressLint import android.app.BroadcastOptions import android.app.Notification import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME import android.app.PendingIntent +import android.app.StatusBarManager import android.app.smartspace.SmartspaceConfig import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceSession @@ -43,7 +45,6 @@ import android.media.session.PlaybackState import android.net.Uri import android.os.Parcelable import android.os.Process -import android.os.RemoteException import android.os.UserHandle import android.provider.Settings import android.service.notification.StatusBarNotification @@ -53,7 +54,6 @@ import android.util.Log import android.util.Pair as APair import androidx.media.utils.MediaConstants import com.android.internal.logging.InstanceId -import com.android.internal.statusbar.IStatusBarService import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Dumpable import com.android.systemui.R @@ -185,7 +185,6 @@ class MediaDataManager( private val logger: MediaUiEventLogger, private val smartspaceManager: SmartspaceManager, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val statusBarService: IStatusBarService, ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener { companion object { @@ -230,6 +229,10 @@ class MediaDataManager( private val artworkHeight = context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded) + @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE + private val statusBarManager = + context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager + /** Check whether this notification is an RCN */ private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean { return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE) @@ -257,7 +260,6 @@ class MediaDataManager( mediaFlags: MediaFlags, logger: MediaUiEventLogger, smartspaceManager: SmartspaceManager, - statusBarService: IStatusBarService, keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : this( context, @@ -283,7 +285,6 @@ class MediaDataManager( logger, smartspaceManager, keyguardUpdateMonitor, - statusBarService, ) private val appChangeReceiver = @@ -793,27 +794,12 @@ class MediaDataManager( song = HybridGroupManager.resolveTitle(notif) } if (song.isNullOrBlank()) { - if (mediaFlags.isMediaTitleRequired(sbn.packageName, sbn.user)) { - // App is required to provide a title: cancel the underlying notification - try { - statusBarService.onNotificationError( - sbn.packageName, - sbn.tag, - sbn.id, - sbn.uid, - sbn.initialPid, - MEDIA_TITLE_ERROR_MESSAGE, - sbn.user.identifier - ) - } catch (e: RemoteException) { - Log.e(TAG, "cancelNotification failed: $e") - } - // Only add log for media removed if active media is updated with invalid title. - foregroundExecutor.execute { removeEntry(key, !isNewlyActiveEntry) } - return - } else { - // For apps that don't have the title requirement yet, add a placeholder - song = context.getString(R.string.controls_media_empty_title, appName) + // For apps that don't include a title, log and add a placeholder + song = context.getString(R.string.controls_media_empty_title, appName) + try { + statusBarManager.logBlankMediaTitle(sbn.packageName, sbn.user.identifier) + } catch (e: RuntimeException) { + Log.e(TAG, "Error reporting blank media title for package ${sbn.packageName}") } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 3751c60b06d5..9bc66f6c98d0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -64,9 +64,4 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { /** Check whether we allow remote media to generate resume controls */ fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME) - - /** Check whether app is required to provide a non-empty media title */ - fun isMediaTitleRequired(packageName: String, user: UserHandle): Boolean { - return StatusBarManager.isMediaTitleRequiredForApp(packageName, user) - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index 993c3801cecd..b956207190b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -354,7 +354,11 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc @Override protected void snapChild(final View animView, final float targetLeft, float velocity) { - superSnapChild(animView, targetLeft, velocity); + if (animView instanceof SwipeableView) { + // only perform the snapback animation on views that are swipeable inside the shade. + superSnapChild(animView, targetLeft, velocity); + } + mCallback.onDragCancelled(animView); if (targetLeft == 0) { handleMenuCoveredOrDismissed(); diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 5ba02fa20167..5bd965c2f668 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -73,7 +73,6 @@ import android.os.Message; import android.os.SystemClock; import android.os.Trace; import android.os.VibrationEffect; -import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.Global; import android.text.InputFilter; @@ -113,7 +112,6 @@ import androidx.annotation.Nullable; import com.android.app.animation.Interpolators; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.graphics.drawable.BackgroundBlurDrawable; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.view.RotationPolicy; @@ -133,15 +131,11 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.AlphaTintDrawableWrapper; -import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.RoundedCornerProgressDrawable; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -198,9 +192,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private ViewGroup mDialogRowsView; private ViewGroup mRinger; - private DeviceConfigProxy mDeviceConfigProxy; - private Executor mExecutor; - /** * Container for the top part of the dialog, which contains the ringer, the ringer drawer, the * volume rows, and the ellipsis button. This does not include the live caption button. @@ -290,14 +281,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private BackgroundBlurDrawable mDialogRowsViewBackground; private final InteractionJankMonitor mInteractionJankMonitor; - private boolean mSeparateNotification; - private int mWindowGravity; @VisibleForTesting - int mVolumeRingerIconDrawableId; + final int mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on; @VisibleForTesting - int mVolumeRingerMuteIconDrawableId; + final int mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute; private int mOriginalGravity; private final DevicePostureController.Callback mDevicePostureControllerCallback; @@ -315,8 +304,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, VolumePanelFactory volumePanelFactory, ActivityStarter activityStarter, InteractionJankMonitor interactionJankMonitor, - DeviceConfigProxy deviceConfigProxy, - Executor executor, CsdWarningDialog.Factory csdWarningDialogFactory, DevicePostureController devicePostureController, Looper looper, @@ -374,12 +361,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } else { mDevicePostureControllerCallback = null; } - - mDeviceConfigProxy = deviceConfigProxy; - mExecutor = executor; - mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false); - updateRingerModeIconSet(); } /** @@ -401,44 +382,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, return mWindowGravity; } - /** - * If ringer and notification are the same stream (T and earlier), use notification-like bell - * icon set. - * If ringer and notification are separated, then use generic speaker icons. - */ - private void updateRingerModeIconSet() { - if (mSeparateNotification) { - mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on; - mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute; - } else { - mVolumeRingerIconDrawableId = R.drawable.ic_volume_ringer; - mVolumeRingerMuteIconDrawableId = R.drawable.ic_volume_ringer_mute; - } - - if (mRingerDrawerMuteIcon != null) { - mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId); - } - if (mRingerDrawerNormalIcon != null) { - mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId); - } - } - - /** - * Change icon for ring stream (not ringer mode icon) - */ - private void updateRingRowIcon() { - Optional<VolumeRow> volumeRow = mRows.stream().filter(row -> row.stream == STREAM_RING) - .findFirst(); - if (volumeRow.isPresent()) { - VolumeRow volRow = volumeRow.get(); - volRow.iconRes = mSeparateNotification ? R.drawable.ic_ring_volume - : R.drawable.ic_volume_ringer; - volRow.iconMuteRes = mSeparateNotification ? R.drawable.ic_ring_volume_off - : R.drawable.ic_volume_ringer_mute; - volRow.setIcon(volRow.iconRes, mContext.getTheme()); - } - } - @Override public void onUiModeChanged() { mContext.getTheme().applyStyle(mContext.getThemeResId(), true); @@ -454,9 +397,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mConfigurationController.addCallback(this); - mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mExecutor, this::onDeviceConfigChange); - if (mDevicePostureController != null) { mDevicePostureController.addCallback(mDevicePostureControllerCallback); } @@ -467,28 +407,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mController.removeCallback(mControllerCallbackH); mHandler.removeCallbacksAndMessages(null); mConfigurationController.removeCallback(this); - mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange); if (mDevicePostureController != null) { mDevicePostureController.removeCallback(mDevicePostureControllerCallback); } } - /** - * Update ringer mode icon based on the config - */ - private void onDeviceConfigChange(DeviceConfig.Properties properties) { - Set<String> changeSet = properties.getKeyset(); - if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) { - boolean newVal = properties.getBoolean( - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false); - if (newVal != mSeparateNotification) { - mSeparateNotification = newVal; - updateRingerModeIconSet(); - updateRingRowIcon(); - } - } - } - @Override public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo internalInsetsInfo) { // Set touchable region insets on the root dialog view. This tells WindowManager that @@ -699,7 +622,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon); mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background); - updateRingerModeIconSet(); + if (mRingerDrawerMuteIcon != null) { + mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId); + } + if (mRingerDrawerNormalIcon != null) { + mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId); + } setupRingerDrawer(); @@ -724,13 +652,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, addRow(AudioManager.STREAM_MUSIC, R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true); if (!AudioSystem.isSingleVolume(mContext)) { - if (mSeparateNotification) { - addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume, - R.drawable.ic_ring_volume_off, true, false); - } else { - addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer, - R.drawable.ic_volume_ringer, true, false); - } + + addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume, + R.drawable.ic_ring_volume_off, true, false); + addRow(STREAM_ALARM, R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false); diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index bb04f82fcffa..aa4ee545a500 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -21,7 +21,6 @@ import android.media.AudioManager; import android.os.Looper; import com.android.internal.jank.InteractionJankMonitor; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; @@ -31,7 +30,6 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.volume.CsdWarningDialog; import com.android.systemui.volume.VolumeComponent; import com.android.systemui.volume.VolumeDialogComponent; @@ -42,8 +40,6 @@ import dagger.Binds; import dagger.Module; import dagger.Provides; -import java.util.concurrent.Executor; - /** Dagger Module for code in the volume package. */ @Module public interface VolumeModule { @@ -63,8 +59,6 @@ public interface VolumeModule { VolumePanelFactory volumePanelFactory, ActivityStarter activityStarter, InteractionJankMonitor interactionJankMonitor, - DeviceConfigProxy deviceConfigProxy, - @Main Executor executor, CsdWarningDialog.Factory csdFactory, DevicePostureController devicePostureController, DumpManager dumpManager) { @@ -78,8 +72,6 @@ public interface VolumeModule { volumePanelFactory, activityStarter, interactionJankMonitor, - deviceConfigProxy, - executor, csdFactory, devicePostureController, Looper.getMainLooper(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index 3bcefcf6ffff..56698e0ec41c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -41,7 +41,6 @@ import android.testing.TestableLooper.RunWithLooper import androidx.media.utils.MediaConstants import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId -import com.android.internal.statusbar.IStatusBarService import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.InstanceIdSequenceFake import com.android.systemui.R @@ -133,7 +132,6 @@ class MediaDataManagerTest : SysuiTestCase() { @Mock lateinit var activityStarter: ActivityStarter @Mock lateinit var smartspaceManager: SmartspaceManager @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock lateinit var statusBarService: IStatusBarService lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget @Mock private lateinit var mediaRecommendationItem: SmartspaceAction @@ -197,7 +195,6 @@ class MediaDataManagerTest : SysuiTestCase() { logger = logger, smartspaceManager = smartspaceManager, keyguardUpdateMonitor = keyguardUpdateMonitor, - statusBarService = statusBarService, ) verify(tunerService) .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)) @@ -522,143 +519,12 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test - fun testOnNotificationAdded_emptyTitle_isRequired_notLoaded() { - // When the manager has a notification with an empty title, and the app is required - // to include a non-empty title - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE) - .build() - ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - - // Then the media control is not added and we report a notification error - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any()) - verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any()) - } - - @Test - fun testOnNotificationAdded_blankTitle_isRequired_notLoaded() { - // When the manager has a notification with a blank title, and the app is required - // to include a non-empty title - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) - .build() - ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - - // Then the media control is not added and we report a notification error - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any()) - verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any()) - } - - @Test - fun testOnNotificationUpdated_invalidTitle_isRequired_logMediaRemoved() { - // When the app is required to provide a non-blank title, and updates a previously valid - // title to an empty one - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) - addNotificationAndLoad() - val data = mediaDataCaptor.value - - verify(listener) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - - reset(listener) - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) - .build() - ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - - // Then the media control is removed - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) - } - - @Test - fun testOnNotificationAdded_emptyTitle_notRequired_hasPlaceholder() { + fun testOnNotificationAdded_emptyTitle_hasPlaceholder() { // When the manager has a notification with an empty title, and the app is not // required to include a non-empty title val mockPackageManager = mock(PackageManager::class.java) context.setMockPackageManager(mockPackageManager) whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false) whenever(controller.metadata) .thenReturn( metadataBuilder @@ -684,13 +550,12 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test - fun testOnNotificationAdded_blankTitle_notRequired_hasPlaceholder() { + fun testOnNotificationAdded_blankTitle_hasPlaceholder() { // GIVEN that the manager has a notification with a blank title, and the app is not // required to include a non-empty title val mockPackageManager = mock(PackageManager::class.java) context.setMockPackageManager(mockPackageManager) whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false) whenever(controller.metadata) .thenReturn( metadataBuilder @@ -722,7 +587,6 @@ class MediaDataManagerTest : SysuiTestCase() { val mockPackageManager = mock(PackageManager::class.java) context.setMockPackageManager(mockPackageManager) whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) whenever(controller.metadata) .thenReturn( metadataBuilder diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt index 2eca78a0412b..e92368df8663 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.shared.clocks import android.testing.AndroidTestingRunner import android.view.LayoutInflater import androidx.test.filters.SmallTest +import com.android.app.animation.Interpolators import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.TextAnimator @@ -64,8 +65,8 @@ class AnimatableClockViewTest : SysuiTestCase() { color = 200, strokeWidth = -1F, animate = false, - duration = 350L, - interpolator = null, + duration = 833L, + interpolator = Interpolators.EMPHASIZED_DECELERATE, delay = 0L, onAnimationEnd = null ) @@ -98,8 +99,8 @@ class AnimatableClockViewTest : SysuiTestCase() { color = 200, strokeWidth = -1F, animate = true, - duration = 350L, - interpolator = null, + duration = 833L, + interpolator = Interpolators.EMPHASIZED_DECELERATE, delay = 0L, onAnimationEnd = null ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 551499e0fb55..7632d01d4d43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -392,23 +392,32 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { @Test public void testSnapchild_targetIsZero() { - doNothing().when(mSwipeHelper).superSnapChild(mView, 0, 0); - mSwipeHelper.snapChild(mView, 0, 0); + doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 0, 0); + mSwipeHelper.snapChild(mNotificationRow, 0, 0); - verify(mCallback, times(1)).onDragCancelled(mView); - verify(mSwipeHelper, times(1)).superSnapChild(mView, 0, 0); + verify(mCallback, times(1)).onDragCancelled(mNotificationRow); + verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 0, 0); verify(mSwipeHelper, times(1)).handleMenuCoveredOrDismissed(); } @Test public void testSnapchild_targetNotZero() { + doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 10, 0); + mSwipeHelper.snapChild(mNotificationRow, 10, 0); + + verify(mCallback, times(1)).onDragCancelled(mNotificationRow); + verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 10, 0); + verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed(); + } + + @Test + public void testSnapchild_targetNotSwipeable() { doNothing().when(mSwipeHelper).superSnapChild(mView, 10, 0); mSwipeHelper.snapChild(mView, 10, 0); - verify(mCallback, times(1)).onDragCancelled(mView); - verify(mSwipeHelper, times(1)).superSnapChild(mView, 10, 0); - verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed(); + verify(mCallback).onDragCancelled(mView); + verify(mSwipeHelper, never()).superSnapChild(mView, 10, 0); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 45a37cffa588..8f725bebfb16 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -35,7 +35,6 @@ import android.app.KeyguardManager; import android.content.res.Configuration; import android.media.AudioManager; import android.os.SystemClock; -import android.provider.DeviceConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Gravity; @@ -47,7 +46,6 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.Prefs; import com.android.systemui.R; @@ -62,9 +60,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.FakeConfigurationController; -import com.android.systemui.util.DeviceConfigProxyFake; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Before; @@ -88,8 +83,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { View mDrawerVibrate; View mDrawerMute; View mDrawerNormal; - private DeviceConfigProxyFake mDeviceConfigProxy; - private FakeExecutor mExecutor; private TestableLooper mTestableLooper; private ConfigurationController mConfigurationController; private int mOriginalOrientation; @@ -131,8 +124,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { getContext().addMockSystemService(KeyguardManager.class, mKeyguard); mTestableLooper = TestableLooper.get(this); - mDeviceConfigProxy = new DeviceConfigProxyFake(); - mExecutor = new FakeExecutor(new FakeSystemClock()); when(mPostureController.getDevicePosture()) .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); @@ -151,8 +142,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mVolumePanelFactory, mActivityStarter, mInteractionJankMonitor, - mDeviceConfigProxy, - mExecutor, mCsdWarningDialogFactory, mPostureController, mTestableLooper.getLooper(), @@ -173,9 +162,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1); Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false); - - mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); } private State createShellState() { @@ -351,13 +337,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { * API does not exist. So we do the next best thing; we check the cached icon id. */ @Test - public void notificationVolumeSeparated_theRingerIconChanges() { - mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false); - - mExecutor.runAllReady(); // for the config change to take effect - - // assert icon is new based on res id + public void notificationVolumeSeparated_theRingerIconChangesToSpeakerIcon() { + // already separated. assert icon is new based on res id assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_speaker_on); assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, @@ -365,17 +346,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - public void notificationVolumeNotSeparated_theRingerIconRemainsTheSame() { - mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); - - mExecutor.runAllReady(); - - assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_volume_ringer); - assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute); - } - - @Test public void testDialogDismissAnimation_notifyVisibleIsNotCalledBeforeAnimation() { mDialog.dismissH(DISMISS_REASON_UNKNOWN); // notifyVisible(false) should not be called immediately but only after the dismiss @@ -408,8 +378,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mVolumePanelFactory, mActivityStarter, mInteractionJankMonitor, - mDeviceConfigProxy, - mExecutor, mCsdWarningDialogFactory, devicePostureController, mTestableLooper.getLooper(), @@ -447,8 +415,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mVolumePanelFactory, mActivityStarter, mInteractionJankMonitor, - mDeviceConfigProxy, - mExecutor, mCsdWarningDialogFactory, devicePostureController, mTestableLooper.getLooper(), @@ -485,8 +451,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mVolumePanelFactory, mActivityStarter, mInteractionJankMonitor, - mDeviceConfigProxy, - mExecutor, mCsdWarningDialogFactory, devicePostureController, mTestableLooper.getLooper(), @@ -525,8 +489,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mVolumePanelFactory, mActivityStarter, mInteractionJankMonitor, - mDeviceConfigProxy, - mExecutor, mCsdWarningDialogFactory, mPostureController, mTestableLooper.getLooper(), diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 1e5f187fee4a..85a01851187e 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -837,7 +837,7 @@ public class OomAdjuster { */ @GuardedBy("mService") void enqueueOomAdjTargetLocked(ProcessRecord app) { - if (app != null) { + if (app != null && app.mState.getMaxAdj() > FOREGROUND_APP_ADJ) { mPendingProcessSet.add(app); } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 355981a0d109..d0b6cdce037f 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -45,7 +45,6 @@ import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; -import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.BroadcastOptions; @@ -167,7 +166,6 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorManager; -import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.System; import android.service.notification.ZenModeConfig; @@ -187,10 +185,8 @@ import android.view.KeyEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; - import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; @@ -252,7 +248,6 @@ public class AudioService extends IAudioService.Stub AudioSystemAdapter.OnVolRangeInitRequestListener { private static final String TAG = "AS.AudioService"; - private static final boolean CONFIG_DEFAULT_VAL = false; private final AudioSystemAdapter mAudioSystem; private final SystemServerAdapter mSystemServer; @@ -309,7 +304,7 @@ public class AudioService extends IAudioService.Stub * indicates whether STREAM_NOTIFICATION is aliased to STREAM_RING * not final due to test method, see {@link #setNotifAliasRingForTest(boolean)}. */ - private boolean mNotifAliasRing; + private boolean mNotifAliasRing = false; /** * Test method to temporarily override whether STREAM_NOTIFICATION is aliased to STREAM_RING, @@ -1057,13 +1052,6 @@ public class AudioService extends IAudioService.Stub mUseVolumeGroupAliases = mContext.getResources().getBoolean( com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups); - mNotifAliasRing = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false); - - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - ActivityThread.currentApplication().getMainExecutor(), - this::onDeviceConfigChange); - // Initialize volume // Priority 1 - Android Property // Priority 2 - Audio Policy Service @@ -1157,6 +1145,11 @@ public class AudioService extends IAudioService.Stub MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM]; } + int minAssistantVolume = SystemProperties.getInt("ro.config.assistant_vol_min", -1); + if (minAssistantVolume != -1) { + MIN_STREAM_VOLUME[AudioSystem.STREAM_ASSISTANT] = minAssistantVolume; + } + // Read following properties to configure max volume (number of steps) and default volume // for STREAM_NOTIFICATION and STREAM_RING: // config_audio_notif_vol_default @@ -1277,22 +1270,6 @@ public class AudioService extends IAudioService.Stub } /** - * Separating notification volume from ring is NOT of aliasing the corresponding streams - * @param properties - */ - private void onDeviceConfigChange(DeviceConfig.Properties properties) { - Set<String> changeSet = properties.getKeyset(); - if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) { - boolean newNotifAliasRing = !properties.getBoolean( - SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL); - if (mNotifAliasRing != newNotifAliasRing) { - mNotifAliasRing = newNotifAliasRing; - updateStreamVolumeAlias(true, TAG); - } - } - } - - /** * Called by handling of MSG_INIT_STREAMS_VOLUMES */ private void onInitStreamsAndVolumes() { diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index a486d16189fa..694dfd28d0cc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -70,9 +70,11 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible // for that the queue will wait indefinitely until the field is cleared. - if (clientMonitor instanceof StopUserClient<?> && !success) { - Slog.w(getTag(), - "StopUserClient failed(), is the HAL stuck? Clearing mStopUserClient"); + if (clientMonitor instanceof StopUserClient<?>) { + if (!success) { + Slog.w(getTag(), "StopUserClient failed(), is the HAL stuck? " + + "Clearing mStopUserClient"); + } mStopUserClient = null; } if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index ffbf4e12f2ae..2ad41c2a7a02 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -89,7 +89,7 @@ public class Sensor { @NonNull private final Map<Integer, Long> mAuthenticatorIds; @NonNull private final Supplier<AidlSession> mLazySession; - @Nullable private AidlSession mCurrentSession; + @Nullable AidlSession mCurrentSession; @VisibleForTesting public static class HalSessionCallback extends ISessionCallback.Stub { @@ -486,7 +486,7 @@ public class Sensor { Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties, @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull BiometricContext biometricContext) { + @NonNull BiometricContext biometricContext, AidlSession session) { mTag = tag; mProvider = provider; mContext = context; @@ -549,6 +549,14 @@ public class Sensor { mLazySession = () -> mCurrentSession != null ? mCurrentSession : null; } + Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context, + @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties, + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull BiometricContext biometricContext) { + this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher, + biometricContext, null); + } + @NonNull Supplier<AidlSession> getLazySession() { return mLazySession; } @@ -557,7 +565,7 @@ public class Sensor { return mSensorProperties; } - @Nullable AidlSession getSessionForUser(int userId) { + @VisibleForTesting @Nullable AidlSession getSessionForUser(int userId) { if (mCurrentSession != null && mCurrentSession.getUserId() == userId) { return mCurrentSession; } else { @@ -641,6 +649,8 @@ public class Sensor { BiometricsProtoEnums.MODALITY_FACE, BiometricsProtoEnums.ISSUE_HAL_DEATH, -1 /* sensorId */); + } else if (client != null) { + client.cancel(); } mScheduler.recordCrashState(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index c0dde721b962..56b85ceb8e6b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -90,7 +90,7 @@ public class Sensor { @NonNull private final LockoutCache mLockoutCache; @NonNull private final Map<Integer, Long> mAuthenticatorIds; - @Nullable private AidlSession mCurrentSession; + @Nullable AidlSession mCurrentSession; @NonNull private final Supplier<AidlSession> mLazySession; @VisibleForTesting @@ -439,7 +439,7 @@ public class Sensor { @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, - @NonNull BiometricContext biometricContext) { + @NonNull BiometricContext biometricContext, AidlSession session) { mTag = tag; mProvider = provider; mContext = context; @@ -501,6 +501,16 @@ public class Sensor { }); mAuthenticatorIds = new HashMap<>(); mLazySession = () -> mCurrentSession != null ? mCurrentSession : null; + mCurrentSession = session; + } + + Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context, + @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties, + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + @NonNull BiometricContext biometricContext) { + this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher, + gestureAvailabilityDispatcher, biometricContext, null); } @NonNull Supplier<AidlSession> getLazySession() { @@ -599,6 +609,8 @@ public class Sensor { BiometricsProtoEnums.MODALITY_FINGERPRINT, BiometricsProtoEnums.ISSUE_HAL_DEATH, -1 /* sensorId */); + } else if (client != null) { + client.cancel(); } mScheduler.recordCrashState(); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 20f06971b6b2..2e62ef4f8566 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -889,22 +889,31 @@ public class LockSettingsService extends ILockSettings.Stub { } - private void migrateOldDataAfterSystemReady() { - // Migrate the FRP credential to the persistent data block + @VisibleForTesting + void migrateOldDataAfterSystemReady() { + // Write the FRP persistent data block if needed. + // + // The original purpose of this code was to write the FRP block for the first time, when + // upgrading from Android 8.1 or earlier which didn't use the FRP block. This code has + // since been repurposed to also fix the "bad" (non-forwards-compatible) FRP block written + // by Android 14 Beta 2. For this reason, the database key used here has been renamed from + // "migrated_frp" to "migrated_frp2" to cause migrateFrpCredential() to run again on devices + // where it had run before. if (LockPatternUtils.frpCredentialEnabled(mContext) - && !getBoolean("migrated_frp", false, 0)) { + && !getBoolean("migrated_frp2", false, 0)) { migrateFrpCredential(); - setBoolean("migrated_frp", true, 0); + setBoolean("migrated_frp2", true, 0); } } /** - * Migrate the credential for the FRP credential owner user if the following are satisfied: - * - the user has a secure credential - * - the FRP credential is not set up + * Write the FRP persistent data block if the following are satisfied: + * - the user who owns the FRP credential has a nonempty credential + * - the FRP persistent data block doesn't exist or uses the "bad" format from Android 14 Beta 2 */ private void migrateFrpCredential() { - if (mStorage.readPersistentDataBlock() != PersistentData.NONE) { + PersistentData data = mStorage.readPersistentDataBlock(); + if (data != PersistentData.NONE && !data.isBadFormatFromAndroid14Beta()) { return; } for (UserInfo userInfo : mUserManager.getUsers()) { diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 731ecadc1372..2fa637e030f1 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -606,6 +606,11 @@ class LockSettingsStorage { this.payload = payload; } + public boolean isBadFormatFromAndroid14Beta() { + return (this.type == TYPE_SP_GATEKEEPER || this.type == TYPE_SP_WEAVER) + && SyntheticPasswordManager.PasswordData.isBadFormatFromAndroid14Beta(this.payload); + } + public static PersistentData fromBytes(byte[] frpData) { if (frpData == null || frpData.length == 0) { return NONE; diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index 65e7a00ad13f..66f862ac9205 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -370,6 +370,15 @@ class SyntheticPasswordManager { return result; } + /** + * Returns true if the given serialized PasswordData begins with the value 2 as a short. + * This detects the "bad" (non-forwards-compatible) PasswordData format that was temporarily + * used during development of Android 14. For more details, see fromBytes() below. + */ + public static boolean isBadFormatFromAndroid14Beta(byte[] data) { + return data != null && data.length >= 2 && data[0] == 0 && data[1] == 2; + } + public static PasswordData fromBytes(byte[] data) { PasswordData result = new PasswordData(); ByteBuffer buffer = ByteBuffer.allocate(data.length); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index c763cfac4e88..0ce794fdb2ba 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -382,11 +382,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks, // so ChangeInfo#hasChanged() can return true to report the transition info. for (int i = mChanges.size() - 1; i >= 0; --i) { - final WindowContainer<?> wc = mChanges.keyAt(i); - if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) continue; - if (isInTransientHide(wc)) { - mChanges.valueAt(i).mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH; - } + updateTransientFlags(mChanges.valueAt(i)); } } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as " @@ -581,7 +577,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { for (WindowContainer<?> curr = getAnimatableParent(wc); curr != null && !mChanges.containsKey(curr); curr = getAnimatableParent(curr)) { - mChanges.put(curr, new ChangeInfo(curr)); + final ChangeInfo info = new ChangeInfo(curr); + updateTransientFlags(info); + mChanges.put(curr, info); if (isReadyGroup(curr)) { mReadyTracker.addGroup(curr); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" @@ -600,6 +598,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { ChangeInfo info = mChanges.get(wc); if (info == null) { info = new ChangeInfo(wc); + updateTransientFlags(info); mChanges.put(wc, info); } mParticipants.add(wc); @@ -615,6 +614,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } + private void updateTransientFlags(@NonNull ChangeInfo info) { + final WindowContainer<?> wc = info.mContainer; + // Only look at tasks, taskfragments, or activities + if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) return; + if (!isInTransientHide(wc)) return; + info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH; + } + private void recordDisplay(DisplayContent dc) { if (dc == null || mTargetDisplays.contains(dc)) return; mTargetDisplays.add(dc); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index 25bd9bcf8d5c..be9f52e00b16 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -17,12 +17,14 @@ package com.android.server.biometrics.sensors.face.aidl; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -41,6 +43,7 @@ import androidx.test.filters.SmallTest; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -82,6 +85,10 @@ public class SensorTest { private AuthSessionCoordinator mAuthSessionCoordinator; @Mock FaceProvider mFaceProvider; + @Mock + BaseClientMonitor mClientMonitor; + @Mock + AidlSession mCurrentSession; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); @@ -161,6 +168,39 @@ public class SensorTest { assertNull(sensor.getSessionForUser(USER_ID)); } + @Test + public void onBinderDied_cancelNonInterruptableClient() { + mLooper.dispatchAll(); + + when(mCurrentSession.getUserId()).thenReturn(USER_ID); + when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID); + when(mClientMonitor.isInterruptable()).thenReturn(false); + + final SensorProps sensorProps = new SensorProps(); + sensorProps.commonProps = new CommonProps(); + sensorProps.commonProps.sensorId = 1; + final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( + sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, + sensorProps.commonProps.maxEnrollmentsPerUser, null, + sensorProps.sensorType, sensorProps.supportsDetectInteraction, + sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); + final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null, + internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession); + mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler(); + sensor.mCurrentSession = new AidlSession(0, mock(ISession.class), + USER_ID, mHalCallback); + + mScheduler.scheduleClientMonitor(mClientMonitor); + + assertNotNull(mScheduler.getCurrentClient()); + + sensor.onBinderDied(); + + verify(mClientMonitor).cancel(); + assertNull(sensor.getSessionForUser(USER_ID)); + assertNull(mScheduler.getCurrentClient()); + } + private void verifyNotLocked() { assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID)); verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID)); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index 0c1346696b58..15d7601dde34 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -17,17 +17,23 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.biometrics.IBiometricService; +import android.hardware.biometrics.common.CommonProps; +import android.hardware.biometrics.face.SensorProps; import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Handler; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; @@ -37,11 +43,13 @@ import androidx.test.filters.SmallTest; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; +import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import org.junit.Before; import org.junit.Test; @@ -76,6 +84,14 @@ public class SensorTest { private BiometricContext mBiometricContext; @Mock private AuthSessionCoordinator mAuthSessionCoordinator; + @Mock + FingerprintProvider mFingerprintProvider; + @Mock + GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; + @Mock + private AidlSession mCurrentSession; + @Mock + private BaseClientMonitor mClientMonitor; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); @@ -130,6 +146,40 @@ public class SensorTest { verifyNotLocked(); } + @Test + public void onBinderDied_cancelNonInterruptableClient() { + mLooper.dispatchAll(); + + when(mCurrentSession.getUserId()).thenReturn(USER_ID); + when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID); + when(mClientMonitor.isInterruptable()).thenReturn(false); + + final SensorProps sensorProps = new SensorProps(); + sensorProps.commonProps = new CommonProps(); + sensorProps.commonProps.sensorId = 1; + final FingerprintSensorPropertiesInternal internalProp = new + FingerprintSensorPropertiesInternal( + sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, + sensorProps.commonProps.maxEnrollmentsPerUser, null, + sensorProps.sensorType, false /* resetLockoutRequiresHardwareAuthToken */); + final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext, + null /* handler */, internalProp, mLockoutResetDispatcher, + mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession); + mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler(); + sensor.mCurrentSession = new AidlSession(0, mock(ISession.class), + USER_ID, mHalCallback); + + mScheduler.scheduleClientMonitor(mClientMonitor); + + assertNotNull(mScheduler.getCurrentClient()); + + sensor.onBinderDied(); + + verify(mClientMonitor).cancel(); + assertNull(sensor.getSessionForUser(USER_ID)); + assertNull(mScheduler.getCurrentClient()); + } + private void verifyNotLocked() { assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID)); verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID)); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java index 2b49b8ab64d2..a242cdec89db 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java @@ -23,6 +23,8 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; import static com.android.internal.widget.LockPatternUtils.USER_FRP; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import android.app.PropertyInvalidatedCache; import android.app.admin.DevicePolicyManager; @@ -38,8 +40,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.nio.ByteBuffer; -/** Test setting a lockscreen credential and then verify it under USER_FRP */ +/** Tests that involve the Factory Reset Protection (FRP) credential. */ @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) @@ -148,4 +151,68 @@ public class LockscreenFrpTest extends BaseLockSettingsServiceTests { mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */) .getResponseCode()); } + + // The FRP block that gets written by the current version of Android must still be accepted by + // old versions of Android. This test tries to detect non-forward-compatible changes in + // PasswordData#toBytes(), which would break that. + @Test + public void testFrpBlock_isForwardsCompatible() { + mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID); + PersistentData data = mStorage.readPersistentDataBlock(); + ByteBuffer buffer = ByteBuffer.wrap(data.payload); + + final int credentialType = buffer.getInt(); + assertEquals(CREDENTIAL_TYPE_PIN, credentialType); + + final byte scryptLogN = buffer.get(); + assertTrue(scryptLogN >= 0); + + final byte scryptLogR = buffer.get(); + assertTrue(scryptLogR >= 0); + + final byte scryptLogP = buffer.get(); + assertTrue(scryptLogP >= 0); + + final int saltLength = buffer.getInt(); + assertTrue(saltLength > 0); + final byte[] salt = new byte[saltLength]; + buffer.get(salt); + + final int passwordHandleLength = buffer.getInt(); + assertTrue(passwordHandleLength > 0); + final byte[] passwordHandle = new byte[passwordHandleLength]; + buffer.get(passwordHandle); + } + + @Test + public void testFrpBlock_inBadAndroid14FormatIsAutomaticallyFixed() { + mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID); + + // Write a "bad" FRP block with PasswordData beginning with the bytes [0, 2]. + byte[] badPasswordData = new byte[] { + 0, 2, /* version 2 */ + 0, 3, /* CREDENTIAL_TYPE_PIN */ + 11, /* scryptLogN */ + 22, /* scryptLogR */ + 33, /* scryptLogP */ + 0, 0, 0, 5, /* salt.length */ + 1, 2, -1, -2, 55, /* salt */ + 0, 0, 0, 6, /* passwordHandle.length */ + 2, 3, -2, -3, 44, 1, /* passwordHandle */ + 0, 0, 0, 6, /* pinLength */ + }; + mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_GATEKEEPER, PRIMARY_USER_ID, 0, + badPasswordData); + + // Execute the code that should fix the FRP block. + assertFalse(mStorage.getBoolean("migrated_frp2", false, 0)); + mService.migrateOldDataAfterSystemReady(); + assertTrue(mStorage.getBoolean("migrated_frp2", false, 0)); + + // Verify that the FRP block has been fixed. + PersistentData data = mStorage.readPersistentDataBlock(); + assertEquals(PersistentData.TYPE_SP_GATEKEEPER, data.type); + ByteBuffer buffer = ByteBuffer.wrap(data.payload); + assertEquals(CREDENTIAL_TYPE_PIN, buffer.getInt()); + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index 067feae4d36a..ce0347dbe4ac 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -638,6 +638,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { 2, 3, -2, -3, 44, 1, /* passwordHandle */ 0, 0, 0, 6, /* pinLength */ }; + assertFalse(PasswordData.isBadFormatFromAndroid14Beta(serialized)); PasswordData deserialized = PasswordData.fromBytes(serialized); assertEquals(11, deserialized.scryptLogN); @@ -690,6 +691,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { 2, 3, -2, -3, 44, 1, /* passwordHandle */ 0, 0, 0, 6, /* pinLength */ }; + assertTrue(PasswordData.isBadFormatFromAndroid14Beta(serialized)); PasswordData deserialized = PasswordData.fromBytes(serialized); assertEquals(11, deserialized.scryptLogN); |