diff options
193 files changed, 4874 insertions, 1752 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 70a23cdf106b..93c0c4d7a024 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1139,7 +1139,6 @@ package android.hardware.camera2 { public final class CameraManager { method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException; method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException; - field public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L; // 0xef10e60L } public abstract static class CameraManager.AvailabilityCallback { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 2ea0d8235548..a320f1e9509c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -11488,7 +11488,7 @@ public class Intent implements Parcelable, Cloneable { private void toUriInner(StringBuilder uri, String scheme, String defAction, String defPackage, int flags) { if (scheme != null) { - uri.append("scheme=").append(scheme).append(';'); + uri.append("scheme=").append(Uri.encode(scheme)).append(';'); } if (mAction != null && !mAction.equals(defAction)) { uri.append("action=").append(Uri.encode(mAction)).append(';'); diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 5291d2b73891..7ccf07a2a764 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -29,7 +29,6 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.ActivityThread; import android.app.AppOpsManager; -import android.app.compat.CompatChanges; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.ImageFormat; @@ -47,7 +46,6 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.renderscript.Allocation; import android.renderscript.Element; import android.renderscript.RSIllegalArgumentException; @@ -284,14 +282,6 @@ public class Camera { */ public native static int getNumberOfCameras(); - private static final boolean sLandscapeToPortrait = - SystemProperties.getBoolean(CameraManager.LANDSCAPE_TO_PORTRAIT_PROP, false); - - private static boolean shouldOverrideToPortrait() { - return CompatChanges.isChangeEnabled(CameraManager.OVERRIDE_FRONT_CAMERA_APP_COMPAT) - && sLandscapeToPortrait; - } - /** * Returns the information about a particular camera. * If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1. @@ -301,7 +291,8 @@ public class Camera { * low-level failure). */ public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) { - boolean overrideToPortrait = shouldOverrideToPortrait(); + boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait( + ActivityThread.currentApplication().getApplicationContext()); _getCameraInfo(cameraId, overrideToPortrait, cameraInfo); IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); @@ -498,7 +489,8 @@ public class Camera { mEventHandler = null; } - boolean overrideToPortrait = shouldOverrideToPortrait(); + boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait( + ActivityThread.currentApplication().getApplicationContext()); return native_setup(new WeakReference<Camera>(this), cameraId, ActivityThread.currentOpPackageName(), overrideToPortrait); } diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index be99f0fefee7..5e2b40cbaf75 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -115,8 +115,14 @@ public final class CameraManager { @ChangeId @Overridable @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE) - @TestApi - public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L; + public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L; + + /** + * Package-level opt in/out for the above. + * @hide + */ + public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT = + "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT"; /** * System property for allowing the above @@ -602,7 +608,7 @@ public final class CameraManager { try { Size displaySize = getDisplaySize(); - boolean overrideToPortrait = shouldOverrideToPortrait(); + boolean overrideToPortrait = shouldOverrideToPortrait(mContext); CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId, mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait); try { @@ -722,7 +728,7 @@ public final class CameraManager { "Camera service is currently unavailable"); } - boolean overrideToPortrait = shouldOverrideToPortrait(); + boolean overrideToPortrait = shouldOverrideToPortrait(mContext); cameraUser = cameraService.connectDevice(callbacks, cameraId, mContext.getOpPackageName(), mContext.getAttributionTag(), uid, oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion, @@ -1154,9 +1160,26 @@ public final class CameraManager { return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId); } - private static boolean shouldOverrideToPortrait() { - return CompatChanges.isChangeEnabled(OVERRIDE_FRONT_CAMERA_APP_COMPAT) - && CameraManagerGlobal.sLandscapeToPortrait; + /** + * @hide + */ + public static boolean shouldOverrideToPortrait(@Nullable Context context) { + if (!CameraManagerGlobal.sLandscapeToPortrait) { + return false; + } + + if (context != null) { + PackageManager packageManager = context.getPackageManager(); + + try { + return packageManager.getProperty(context.getOpPackageName(), + PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + // No such property + } + } + + return CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT); } /** @@ -2313,6 +2336,15 @@ public final class CameraManager { final AvailabilityCallback callback = mCallbackMap.keyAt(i); postSingleUpdate(callback, executor, id, null /*physicalId*/, status); + + // Send the NOT_PRESENT state for unavailable physical cameras + if (isAvailable(status) && mUnavailablePhysicalDevices.containsKey(id)) { + ArrayList<String> unavailableIds = mUnavailablePhysicalDevices.get(id); + for (String unavailableId : unavailableIds) { + postSingleUpdate(callback, executor, id, unavailableId, + ICameraServiceListener.STATUS_NOT_PRESENT); + } + } } } // onStatusChangedLocked diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index a6c79b3a289f..0c2468e65577 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -87,6 +87,7 @@ public class CameraDeviceImpl extends CameraDevice // TODO: guard every function with if (!mRemoteDevice) check (if it was closed) private ICameraDeviceUserWrapper mRemoteDevice; + private boolean mRemoteDeviceInit = false; // Lock to synchronize cross-thread access to device public interface final Object mInterfaceLock = new Object(); // access from this class and Session only! @@ -338,6 +339,8 @@ public class CameraDeviceImpl extends CameraDevice mDeviceExecutor.execute(mCallOnOpened); mDeviceExecutor.execute(mCallOnUnconfigured); + + mRemoteDeviceInit = true; } } @@ -1754,8 +1757,8 @@ public class CameraDeviceImpl extends CameraDevice } synchronized(mInterfaceLock) { - if (mRemoteDevice == null) { - return; // Camera already closed + if (mRemoteDevice == null && mRemoteDeviceInit) { + return; // Camera already closed, user is not interested in errors anymore. } // Redirect device callback to the offline session in case we are in the middle diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index dba1a5e8dfc6..6a667fe39974 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -251,6 +251,10 @@ public final class DeviceStateManager { @Nullable private Boolean lastResult; + public FoldStateListener(Context context) { + this(context, folded -> {}); + } + public FoldStateListener(Context context, Consumer<Boolean> listener) { mFoldedDeviceStates = context.getResources().getIntArray( com.android.internal.R.array.config_foldedDeviceStates); @@ -266,5 +270,10 @@ public final class DeviceStateManager { mDelegate.accept(folded); } } + + @Nullable + public Boolean getFolded() { + return lastResult; + } } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 94a6382227f3..b21187af1271 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9819,11 +9819,10 @@ public final class Settings { "fingerprint_side_fps_auth_downtime"; /** - * Whether or not a SFPS device is required to be interactive for auth to unlock the device. + * Whether or not a SFPS device is enabling the performant auth setting. * @hide */ - public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED = - "sfps_require_screen_on_to_auth_enabled"; + public static final String SFPS_PERFORMANT_AUTH_ENABLED = "sfps_performant_auth_enabled"; /** * Whether or not debugging is enabled. diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 1fcfe7dd5b6f..011232fe1915 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -2953,12 +2953,24 @@ public class ChooserActivity extends ResolverActivity implements private boolean shouldShowStickyContentPreviewNoOrientationCheck() { return shouldShowTabs() - && mMultiProfilePagerAdapter.getListAdapterForUserHandle( - UserHandle.of(UserHandle.myUserId())).getCount() > 0 + && (mMultiProfilePagerAdapter.getListAdapterForUserHandle( + UserHandle.of(UserHandle.myUserId())).getCount() > 0 + || shouldShowContentPreviewWhenEmpty()) && shouldShowContentPreview(); } /** + * This method could be used to override the default behavior when we hide the preview area + * when the current tab doesn't have any items. + * + * @return true if we want to show the content preview area even if the tab for the current + * user is empty + */ + protected boolean shouldShowContentPreviewWhenEmpty() { + return false; + } + + /** * @return true if we want to show the content preview area */ protected boolean shouldShowContentPreview() { diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index f8b764be582b..19e4ba405feb 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -209,7 +209,7 @@ public class ResolverActivity extends Activity implements * <p>Can only be used if there is a work profile. * <p>Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}. */ - static final String EXTRA_SELECTED_PROFILE = + protected static final String EXTRA_SELECTED_PROFILE = "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE"; /** @@ -224,8 +224,8 @@ public class ResolverActivity extends Activity implements static final String EXTRA_CALLING_USER = "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER"; - static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL; - static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK; + protected static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL; + protected static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK; private BroadcastReceiver mWorkProfileStateReceiver; private UserHandle mHeaderCreatorUser; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index af31391fec96..6230d22ebe12 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -17,6 +17,7 @@ package com.android.wm.shell.bubbles; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; +import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; @@ -684,7 +685,8 @@ public class BubbleData { if (bubble.getPendingIntentCanceled() || !(reason == Bubbles.DISMISS_AGED || reason == Bubbles.DISMISS_USER_GESTURE - || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) { + || reason == Bubbles.DISMISS_RELOAD_FROM_DISK) + || KEY_APP_BUBBLE.equals(bubble.getKey())) { return; } if (DEBUG_BUBBLE_DATA) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 8ddc3c04d991..1488469759cd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -605,9 +605,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); + if (taskId2 == INVALID_TASK_ID) { + // Launching a solo task. + ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); + activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); + options1 = activityOptions.toBundle(); + addActivityOptions(options1, null /* launchTarget */); + wct.startTask(taskId1, options1); + mSyncQueue.queue(wct); + return; + } + addActivityOptions(options1, mSideStage); wct.startTask(taskId1, options1); - startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter, instanceId); } @@ -632,9 +642,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); + if (taskId == INVALID_TASK_ID) { + // Launching a solo task. + ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); + activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); + options1 = activityOptions.toBundle(); + addActivityOptions(options1, null /* launchTarget */); + wct.sendPendingIntent(pendingIntent, fillInIntent, options1); + mSyncQueue.queue(wct); + return; + } + addActivityOptions(options1, mSideStage); wct.sendPendingIntent(pendingIntent, fillInIntent, options1); - startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter, instanceId); } @@ -696,6 +716,34 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mShouldUpdateRecents = false; mIsSplitEntering = true; + setSideStagePosition(sidePosition, wct); + if (!mMainStage.isActive()) { + mMainStage.activate(wct, false /* reparent */); + } + + if (mainOptions == null) mainOptions = new Bundle(); + addActivityOptions(mainOptions, mMainStage); + mainOptions = wrapAsSplitRemoteAnimation(adapter, mainOptions); + + updateWindowBounds(mSplitLayout, wct); + if (mainTaskId == INVALID_TASK_ID) { + wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions); + } else { + wct.startTask(mainTaskId, mainOptions); + } + + wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); + + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> { + setDividerVisibility(true, t); + }); + + setEnterInstanceId(instanceId); + } + + private Bundle wrapAsSplitRemoteAnimation(RemoteAnimationAdapter adapter, Bundle options) { final WindowContainerTransaction evictWct = new WindowContainerTransaction(); if (isSplitScreenVisible()) { mMainStage.evictAllChildren(evictWct); @@ -739,37 +787,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, }; RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter( wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay()); - - if (mainOptions == null) { - mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle(); - } else { - ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions); - mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); - mainOptions = mainActivityOptions.toBundle(); - } - - setSideStagePosition(sidePosition, wct); - if (!mMainStage.isActive()) { - mMainStage.activate(wct, false /* reparent */); - } - - if (mainOptions == null) mainOptions = new Bundle(); - addActivityOptions(mainOptions, mMainStage); - updateWindowBounds(mSplitLayout, wct); - if (mainTaskId == INVALID_TASK_ID) { - wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions); - } else { - wct.startTask(mainTaskId, mainOptions); - } - wct.reorder(mRootTaskInfo.token, true); - wct.setForceTranslucent(mRootTaskInfo.token, false); - - mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> { - setDividerVisibility(true, t); - }); - - setEnterInstanceId(instanceId); + ActivityOptions activityOptions = ActivityOptions.fromBundle(options); + activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); + return activityOptions.toBundle(); } private void setEnterInstanceId(InstanceId instanceId) { @@ -1228,8 +1248,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } - private void addActivityOptions(Bundle opts, StageTaskListener stage) { - opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token); + private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) { + if (launchTarget != null) { + opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, launchTarget.mRootTaskInfo.token); + } // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split // will be canceled. opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index e6711aca19c1..8b025cd7c246 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -16,6 +16,8 @@ package com.android.wm.shell.bubbles; +import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE; + import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -32,6 +34,7 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; +import android.content.Intent; import android.content.LocusId; import android.graphics.drawable.Icon; import android.os.Bundle; @@ -94,6 +97,7 @@ public class BubbleDataTest extends ShellTestCase { private Bubble mBubbleInterruptive; private Bubble mBubbleDismissed; private Bubble mBubbleLocusId; + private Bubble mAppBubble; private BubbleData mBubbleData; private TestableBubblePositioner mPositioner; @@ -178,6 +182,11 @@ public class BubbleDataTest extends ShellTestCase { mBubbleMetadataFlagListener, mPendingIntentCanceledListener, mMainExecutor); + + Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class); + appBubbleIntent.setPackage(mContext.getPackageName()); + mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mMainExecutor); + mPositioner = new TestableBubblePositioner(mContext, mock(WindowManager.class)); mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, @@ -1089,6 +1098,18 @@ public class BubbleDataTest extends ShellTestCase { assertOverflowChangedTo(ImmutableList.of()); } + @Test + public void test_removeAppBubble_skipsOverflow() { + mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/, + false /* showInShade */); + assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isEqualTo(mAppBubble); + + mBubbleData.dismissBubbleWithKey(KEY_APP_BUBBLE, Bubbles.DISMISS_USER_GESTURE); + + assertThat(mBubbleData.getOverflowBubbleWithKey(KEY_APP_BUBBLE)).isNull(); + assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull(); + } + private void verifyUpdateReceived() { verify(mListener).applyUpdate(mUpdateCaptor.capture()); reset(mListener); diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java index f6914efd6d83..23d6e34db4f1 100644 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java +++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java @@ -22,8 +22,8 @@ import static com.android.server.backup.encryption.protos.nano.ChunksMetadataPro import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java index 096b2da10c98..bfc5d0dca3ff 100644 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java +++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java @@ -18,9 +18,9 @@ package com.android.server.backup.encryption.tasks; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTaskTest.java index fa4fef50ac1a..222b88221ba2 100644 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTaskTest.java +++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTaskTest.java @@ -19,8 +19,8 @@ package com.android.server.backup.encryption.tasks; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertFalse; @@ -41,13 +41,6 @@ import com.android.server.backup.encryption.protos.nano.KeyValueListingProto; import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; import com.android.server.backup.testing.CryptoTestUtils; -import java.io.IOException; -import java.util.Arrays; -import java.util.Map; -import java.util.Map.Entry; - -import javax.crypto.SecretKey; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -59,6 +52,14 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.Map.Entry; + +import javax.crypto.SecretKey; + + @RunWith(RobolectricTestRunner.class) public class EncryptedKvBackupTaskTest { private static final boolean INCREMENTAL = true; diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index 468a97630e19..15920940140c 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -59,6 +59,18 @@ public class IllustrationPreference extends Preference { private Uri mImageUri; private Drawable mImageDrawable; private View mMiddleGroundView; + private OnBindListener mOnBindListener; + + /** + * Interface to listen in on when {@link #onBindViewHolder(PreferenceViewHolder)} occurs. + */ + public interface OnBindListener { + /** + * Called when when {@link #onBindViewHolder(PreferenceViewHolder)} occurs. + * @param animationView the animation view for this preference. + */ + void onBind(LottieAnimationView animationView); + } private final Animatable2.AnimationCallback mAnimationCallback = new Animatable2.AnimationCallback() { @@ -133,6 +145,17 @@ public class IllustrationPreference extends Preference { if (IS_ENABLED_LOTTIE_ADAPTIVE_COLOR) { ColorUtils.applyDynamicColors(getContext(), illustrationView); } + + if (mOnBindListener != null) { + mOnBindListener.onBind(illustrationView); + } + } + + /** + * Sets a listener to be notified when the views are binded. + */ + public void setOnBindListener(OnBindListener listener) { + mOnBindListener = listener; } /** diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index 1f2297ba3a0c..fc2bf0a9bd93 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -21,10 +21,10 @@ import static android.os.UserHandle.MU_ENABLED; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java index 95f7ef41b10b..508dffc2fa21 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java @@ -18,7 +18,7 @@ package com.android.settingslib.net; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java index f28572f5f71d..cf07c6bb7cb9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import android.app.Activity; @@ -143,7 +143,7 @@ public class EditUserInfoControllerTest { dialog.show(); dialog.cancel(); - verifyZeroInteractions(successCallback); + verifyNoInteractions(successCallback); verify(cancelCallback, times(1)) .run(); } @@ -159,7 +159,7 @@ public class EditUserInfoControllerTest { dialog.show(); dialog.getButton(Dialog.BUTTON_NEGATIVE).performClick(); - verifyZeroInteractions(successCallback); + verifyNoInteractions(successCallback); verify(cancelCallback, times(1)) .run(); } @@ -180,7 +180,7 @@ public class EditUserInfoControllerTest { verify(successCallback, times(1)) .accept("test", oldUserIcon); - verifyZeroInteractions(cancelCallback); + verifyNoInteractions(cancelCallback); } @Test @@ -198,7 +198,7 @@ public class EditUserInfoControllerTest { verify(successCallback, times(1)) .accept("test", null); - verifyZeroInteractions(cancelCallback); + verifyNoInteractions(cancelCallback); } @Test @@ -219,7 +219,7 @@ public class EditUserInfoControllerTest { verify(successCallback, times(1)) .accept(expectedNewName, mCurrentIcon); - verifyZeroInteractions(cancelCallback); + verifyNoInteractions(cancelCallback); } @Test @@ -238,7 +238,7 @@ public class EditUserInfoControllerTest { verify(successCallback, times(1)) .accept("test", newPhoto); - verifyZeroInteractions(cancelCallback); + verifyNoInteractions(cancelCallback); } @Test @@ -257,7 +257,7 @@ public class EditUserInfoControllerTest { verify(successCallback, times(1)) .accept("test", newPhoto); - verifyZeroInteractions(cancelCallback); + verifyNoInteractions(cancelCallback); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java index 29549d9a7fa7..103512d4a28a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java @@ -61,6 +61,8 @@ public class IllustrationPreferenceTest { private PreferenceViewHolder mViewHolder; private FrameLayout mMiddleGroundLayout; private final Context mContext = ApplicationProvider.getApplicationContext(); + private IllustrationPreference.OnBindListener mOnBindListener; + private LottieAnimationView mOnBindListenerAnimationView; @Before public void setUp() { @@ -82,6 +84,12 @@ public class IllustrationPreferenceTest { final AttributeSet attributeSet = Robolectric.buildAttributeSet().build(); mPreference = new IllustrationPreference(mContext, attributeSet); + mOnBindListener = new IllustrationPreference.OnBindListener() { + @Override + public void onBind(LottieAnimationView animationView) { + mOnBindListenerAnimationView = animationView; + } + }; } @Test @@ -186,4 +194,25 @@ public class IllustrationPreferenceTest { assertThat(mBackgroundView.getMaxHeight()).isEqualTo(restrictedHeight); assertThat(mAnimationView.getMaxHeight()).isEqualTo(restrictedHeight); } + + @Test + public void setOnBindListener_isNotified() { + mOnBindListenerAnimationView = null; + mPreference.setOnBindListener(mOnBindListener); + + mPreference.onBindViewHolder(mViewHolder); + + assertThat(mOnBindListenerAnimationView).isNotNull(); + assertThat(mOnBindListenerAnimationView).isEqualTo(mAnimationView); + } + + @Test + public void setOnBindListener_notNotified() { + mOnBindListenerAnimationView = null; + mPreference.setOnBindListener(null); + + mPreference.onBindViewHolder(mViewHolder); + + assertThat(mOnBindListenerAnimationView).isNull(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragmentTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragmentTest.java index 0b3495def21f..ca0aa0d7b900 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragmentTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragmentTest.java @@ -56,7 +56,7 @@ public class UpdatableListPreferenceDialogFragmentTest { mUpdatableListPrefDlgFragment = spy(UpdatableListPreferenceDialogFragment .newInstance(KEY, MetricsProto.MetricsEvent.DIALOG_SWITCH_A2DP_DEVICES)); - mEntries = spy(new ArrayList<>()); + mEntries = new ArrayList<>(); mUpdatableListPrefDlgFragment.setEntries(mEntries); mUpdatableListPrefDlgFragment .setMetricsCategory(mUpdatableListPrefDlgFragment.getArguments()); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 1b0b6b4ff796..211030a90c47 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -123,7 +123,7 @@ public class SecureSettings { Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, - Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, + Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED, Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 4fa490ffaed5..0539f09e20d3 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -178,7 +178,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR); - VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.SFPS_PERFORMANT_AUTH_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 810dd33ae17a..75c92e000c4d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -292,7 +292,7 @@ <queries> <intent> - <action android:name="android.intent.action.NOTES" /> + <action android:name="android.intent.action.CREATE_NOTE" /> </intent> </queries> @@ -411,7 +411,6 @@ <service android:name=".screenshot.ScreenshotCrossProfileService" android:permission="com.android.systemui.permission.SELF" - android:process=":screenshot_cross_profile" android:exported="false" /> <service android:name=".screenrecord.RecordingService" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml index 2b7bdc2dc4cb..c772c9649cc7 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml @@ -27,7 +27,7 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" - androidprv:layout_maxWidth="@dimen/keyguard_security_width" + androidprv:layout_maxWidth="@dimen/biometric_auth_pattern_view_max_size" android:layout_gravity="center_horizontal|bottom" android:clipChildren="false" android:clipToPadding="false"> diff --git a/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml b/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml deleted file mode 100644 index a3c37e420f29..000000000000 --- a/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/assets/res/any/dimens.xml -** -** Copyright 2013, 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. -*/ ---> -<resources> - <!-- Height of the sliding KeyguardSecurityContainer - (includes 2x keyguard_security_view_top_margin) --> - <dimen name="keyguard_security_height">550dp</dimen> -</resources> diff --git a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml index 1dc61c501beb..b7a1bb46c317 100644 --- a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml @@ -17,10 +17,5 @@ */ --> <resources> - - <!-- Height of the sliding KeyguardSecurityContainer - (includes 2x keyguard_security_view_top_margin) --> - <dimen name="keyguard_security_height">470dp</dimen> - <dimen name="widget_big_font_size">100dp</dimen> </resources> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index c5ffdc0051da..6cc5b9d7b7e8 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -29,9 +29,6 @@ (includes 2x keyguard_security_view_top_margin) --> <dimen name="keyguard_security_height">420dp</dimen> - <!-- Max Height of the sliding KeyguardSecurityContainer - (includes 2x keyguard_security_view_top_margin) --> - <!-- pin/password field max height --> <dimen name="keyguard_password_height">80dp</dimen> diff --git a/packages/SystemUI/res/drawable/ic_media_explicit_indicator.xml b/packages/SystemUI/res/drawable/ic_media_explicit_indicator.xml new file mode 100644 index 000000000000..08c5aaf56bf7 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_media_explicit_indicator.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="13dp" + android:height="13dp" + android:viewportWidth="48" + android:viewportHeight="48" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M18.3,34H29.65V31H21.3V25.7H29.65V22.7H21.3V17.35H29.65V14.35H18.3ZM9,42Q7.8,42 6.9,41.1Q6,40.2 6,39V9Q6,7.8 6.9,6.9Q7.8,6 9,6H39Q40.2,6 41.1,6.9Q42,7.8 42,9V39Q42,40.2 41.1,41.1Q40.2,42 39,42ZM9,39H39Q39,39 39,39Q39,39 39,39V9Q39,9 39,9Q39,9 39,9H9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39ZM9,9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39Q9,39 9,39Q9,39 9,39V9Q9,9 9,9Q9,9 9,9Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/overlay_badge_background.xml b/packages/SystemUI/res/drawable/overlay_badge_background.xml index 857632edcf0d..53122c17e320 100644 --- a/packages/SystemUI/res/drawable/overlay_badge_background.xml +++ b/packages/SystemUI/res/drawable/overlay_badge_background.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2020 The Android Open Source Project + ~ Copyright (C) 2022 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. @@ -14,8 +14,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="oval"> - <solid android:color="?androidprv:attr/colorSurface"/> -</shape> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:pathData="M0,0M48,48"/> +</vector> diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml index a3dd334bd667..3505a3e6b6bf 100644 --- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml +++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml @@ -71,8 +71,8 @@ <com.android.internal.widget.LockPatternView android:id="@+id/lockPattern" android:layout_gravity="center" - android:layout_width="match_parent" - android:layout_height="match_parent"/> + android:layout_width="@dimen/biometric_auth_pattern_view_size" + android:layout_height="@dimen/biometric_auth_pattern_view_size"/> <TextView android:id="@+id/error" diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml index 4af997017bba..147ea8221beb 100644 --- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml @@ -67,8 +67,8 @@ <com.android.internal.widget.LockPatternView android:id="@+id/lockPattern" android:layout_gravity="center" - android:layout_width="match_parent" - android:layout_height="match_parent"/> + android:layout_width="@dimen/biometric_auth_pattern_view_size" + android:layout_height="@dimen/biometric_auth_pattern_view_size"/> <TextView android:id="@+id/error" diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml index bc97e511e7f4..8cf4f4de27da 100644 --- a/packages/SystemUI/res/layout/chipbar.xml +++ b/packages/SystemUI/res/layout/chipbar.xml @@ -23,6 +23,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> + <!-- Extra marginBottom to give room for the drop shadow. --> <LinearLayout android:id="@+id/chipbar_inner" android:orientation="horizontal" @@ -33,6 +34,8 @@ android:layout_marginTop="20dp" android:layout_marginStart="@dimen/notification_side_paddings" android:layout_marginEnd="@dimen/notification_side_paddings" + android:translationZ="4dp" + android:layout_marginBottom="8dp" android:clipToPadding="false" android:gravity="center_vertical" android:alpha="0.0" diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 9134f96f59e1..eec3b11519b1 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -32,26 +32,26 @@ android:elevation="4dp" android:background="@drawable/action_chip_container_background" android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" - app:layout_constraintBottom_toBottomOf="@+id/actions_container" + android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/actions_container" - app:layout_constraintEnd_toEndOf="@+id/actions_container"/> + app:layout_constraintEnd_toEndOf="@+id/actions_container" + app:layout_constraintBottom_toBottomOf="parent"/> <HorizontalScrollView android:id="@+id/actions_container" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" - android:paddingEnd="@dimen/overlay_action_container_padding_right" + android:paddingEnd="@dimen/overlay_action_container_padding_end" android:paddingVertical="@dimen/overlay_action_container_padding_vertical" android:elevation="4dp" android:scrollbars="none" - android:layout_marginBottom="4dp" app:layout_constraintHorizontal_bias="0" app:layout_constraintWidth_percent="1.0" app:layout_constraintWidth_max="wrap" - app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/preview_border" - app:layout_constraintEnd_toEndOf="parent"> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background"> <LinearLayout android:id="@+id/actions" android:layout_width="wrap_content" @@ -69,44 +69,30 @@ android:id="@+id/preview_border" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginStart="@dimen/overlay_offset_x" - android:layout_marginBottom="12dp" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginStart="@dimen/overlay_preview_container_margin" + android:layout_marginTop="@dimen/overlay_border_width_neg" + android:layout_marginEnd="@dimen/overlay_border_width_neg" + android:layout_marginBottom="@dimen/overlay_preview_container_margin" android:elevation="7dp" - app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end" - app:layout_constraintTop_toTopOf="@id/clipboard_preview_top" - android:background="@drawable/overlay_border"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/clipboard_preview_end" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierMargin="@dimen/overlay_border_width" - app:barrierDirection="end" - app:constraint_referenced_ids="clipboard_preview"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/clipboard_preview_top" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierDirection="top" - app:barrierMargin="@dimen/overlay_border_width_neg" - app:constraint_referenced_ids="clipboard_preview"/> + android:background="@drawable/overlay_border" + app:layout_constraintStart_toStartOf="@id/actions_container_background" + app:layout_constraintTop_toTopOf="@id/clipboard_preview" + app:layout_constraintEnd_toEndOf="@id/clipboard_preview" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/> <FrameLayout android:id="@+id/clipboard_preview" + android:layout_width="@dimen/clipboard_preview_size" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/overlay_border_width" + android:layout_marginBottom="@dimen/overlay_border_width" + android:layout_gravity="center" android:elevation="7dp" android:background="@drawable/overlay_preview_background" android:clipChildren="true" android:clipToOutline="true" android:clipToPadding="true" - android:layout_width="@dimen/clipboard_preview_size" - android:layout_margin="@dimen/overlay_border_width" - android:layout_height="wrap_content" - android:layout_gravity="center" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintBottom_toBottomOf="@id/preview_border" app:layout_constraintStart_toStartOf="@id/preview_border" - app:layout_constraintEnd_toEndOf="@id/preview_border" - app:layout_constraintTop_toTopOf="@id/preview_border"> + app:layout_constraintBottom_toBottomOf="@id/preview_border"> <TextView android:id="@+id/text_preview" android:textFontWeight="500" android:padding="8dp" diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index a565988c14ad..d68982876448 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -148,9 +148,4 @@ <include layout="@layout/ongoing_privacy_chip"/> </FrameLayout> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:id="@+id/space" - /> </com.android.systemui.util.NoRemeasureMotionLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index 95aefab328df..abc83379950a 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -147,6 +147,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" /> + <!-- Explicit Indicator --> + <com.android.internal.widget.CachingIconView + android:id="@+id/media_explicit_indicator" + android:layout_width="@dimen/qs_media_explicit_indicator_icon_size" + android:layout_height="@dimen/qs_media_explicit_indicator_icon_size" + android:src="@drawable/ic_media_explicit_indicator" + /> + <!-- Artist name --> <TextView android:id="@+id/header_artist" diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml index e4e0bd45a2db..496eb6e6130e 100644 --- a/packages/SystemUI/res/layout/screenshot_static.xml +++ b/packages/SystemUI/res/layout/screenshot_static.xml @@ -27,26 +27,26 @@ android:elevation="4dp" android:background="@drawable/action_chip_container_background" android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" - app:layout_constraintBottom_toBottomOf="@+id/actions_container" + android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/actions_container" - app:layout_constraintEnd_toEndOf="@+id/actions_container"/> + app:layout_constraintEnd_toEndOf="@+id/actions_container" + app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"/> <HorizontalScrollView android:id="@+id/actions_container" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" - android:layout_marginBottom="4dp" - android:paddingEnd="@dimen/overlay_action_container_padding_right" + android:paddingEnd="@dimen/overlay_action_container_padding_end" android:paddingVertical="@dimen/overlay_action_container_padding_vertical" android:elevation="4dp" android:scrollbars="none" app:layout_constraintHorizontal_bias="0" app:layout_constraintWidth_percent="1.0" app:layout_constraintWidth_max="wrap" - app:layout_constraintBottom_toTopOf="@id/screenshot_message_container" app:layout_constraintStart_toEndOf="@+id/screenshot_preview_border" - app:layout_constraintEnd_toEndOf="parent"> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background"> <LinearLayout android:id="@+id/screenshot_actions" android:layout_width="wrap_content" @@ -64,35 +64,24 @@ android:id="@+id/screenshot_preview_border" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginStart="@dimen/overlay_offset_x" - android:layout_marginBottom="12dp" + android:layout_marginStart="@dimen/overlay_preview_container_margin" + android:layout_marginTop="@dimen/overlay_border_width_neg" + android:layout_marginEnd="@dimen/overlay_border_width_neg" + android:layout_marginBottom="@dimen/overlay_preview_container_margin" android:elevation="7dp" android:alpha="0" android:background="@drawable/overlay_border" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toTopOf="@id/screenshot_message_container" - app:layout_constraintEnd_toEndOf="@id/screenshot_preview_end" - app:layout_constraintTop_toTopOf="@id/screenshot_preview_top"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/screenshot_preview_end" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierMargin="@dimen/overlay_border_width" - app:barrierDirection="end" - app:constraint_referenced_ids="screenshot_preview"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/screenshot_preview_top" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierDirection="top" - app:barrierMargin="@dimen/overlay_border_width_neg" - app:constraint_referenced_ids="screenshot_preview"/> + app:layout_constraintStart_toStartOf="@id/actions_container_background" + app:layout_constraintTop_toTopOf="@id/screenshot_preview" + app:layout_constraintEnd_toEndOf="@id/screenshot_preview" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/> <ImageView android:id="@+id/screenshot_preview" android:visibility="invisible" android:layout_width="@dimen/overlay_x_scale" - android:layout_margin="@dimen/overlay_border_width" android:layout_height="wrap_content" + android:layout_marginStart="@dimen/overlay_border_width" + android:layout_marginBottom="@dimen/overlay_border_width" android:layout_gravity="center" android:elevation="7dp" android:contentDescription="@string/screenshot_edit_description" @@ -100,20 +89,14 @@ android:background="@drawable/overlay_preview_background" android:adjustViewBounds="true" android:clickable="true" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border" app:layout_constraintStart_toStartOf="@id/screenshot_preview_border" - app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border" - app:layout_constraintTop_toTopOf="@id/screenshot_preview_border"/> + app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/> <ImageView android:id="@+id/screenshot_badge" - android:layout_width="24dp" - android:layout_height="24dp" - android:padding="4dp" + android:layout_width="48dp" + android:layout_height="48dp" android:visibility="gone" - android:background="@drawable/overlay_badge_background" android:elevation="8dp" - android:src="@drawable/overlay_cancel" app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border" app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/> <FrameLayout @@ -150,7 +133,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal" android:layout_marginVertical="4dp" - android:paddingHorizontal="@dimen/overlay_action_container_padding_right" + android:paddingHorizontal="@dimen/overlay_action_container_padding_end" android:paddingVertical="@dimen/overlay_action_container_padding_vertical" android:elevation="4dp" android:background="@drawable/action_chip_container_background" diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 49ef330dcc52..fff25448b2e4 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -40,6 +40,10 @@ <dimen name="biometric_dialog_button_negative_max_width">140dp</dimen> <dimen name="biometric_dialog_button_positive_max_width">116dp</dimen> + <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size --> + <dimen name="biometric_auth_pattern_view_size">248dp</dimen> + <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen> + <dimen name="global_actions_power_dialog_item_height">130dp</dimen> <dimen name="global_actions_power_dialog_item_bottom_margin">35dp</dimen> diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml index aefd9981d02e..a0e721e571d8 100644 --- a/packages/SystemUI/res/values-land/styles.xml +++ b/packages/SystemUI/res/values-land/styles.xml @@ -29,11 +29,11 @@ <style name="AuthCredentialPatternContainerStyle"> <item name="android:gravity">center</item> - <item name="android:maxHeight">320dp</item> - <item name="android:maxWidth">320dp</item> - <item name="android:minHeight">200dp</item> - <item name="android:minWidth">200dp</item> - <item name="android:paddingHorizontal">60dp</item> + <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item> + <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item> + <item name="android:paddingHorizontal">32dp</item> <item name="android:paddingVertical">20dp</item> </style> diff --git a/packages/SystemUI/res/values-sw360dp/dimens.xml b/packages/SystemUI/res/values-sw360dp/dimens.xml index 65ca70bac0dc..03365b3d8c4f 100644 --- a/packages/SystemUI/res/values-sw360dp/dimens.xml +++ b/packages/SystemUI/res/values-sw360dp/dimens.xml @@ -25,5 +25,8 @@ <!-- Home Controls --> <dimen name="global_actions_side_margin">12dp</dimen> + + <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> + <dimen name="biometric_auth_pattern_view_size">298dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw392dp-land/dimens.xml b/packages/SystemUI/res/values-sw392dp-land/dimens.xml new file mode 100644 index 000000000000..1e26a699f85a --- /dev/null +++ b/packages/SystemUI/res/values-sw392dp-land/dimens.xml @@ -0,0 +1,21 @@ +<!-- + ~ Copyright (C) 2022 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. + --> + +<resources> + <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size --> + <dimen name="biometric_auth_pattern_view_size">248dp</dimen> + <dimen name="biometric_auth_pattern_view_max_size">248dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-sw392dp/dimens.xml b/packages/SystemUI/res/values-sw392dp/dimens.xml index 78279ca4f520..96af3c13f32e 100644 --- a/packages/SystemUI/res/values-sw392dp/dimens.xml +++ b/packages/SystemUI/res/values-sw392dp/dimens.xml @@ -24,5 +24,8 @@ <!-- Home Controls --> <dimen name="global_actions_side_margin">16dp</dimen> + + <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> + <dimen name="biometric_auth_pattern_view_size">298dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw410dp-land/dimens.xml b/packages/SystemUI/res/values-sw410dp-land/dimens.xml new file mode 100644 index 000000000000..c4d9b9b92f57 --- /dev/null +++ b/packages/SystemUI/res/values-sw410dp-land/dimens.xml @@ -0,0 +1,21 @@ +<!-- + ~ Copyright (C) 2022 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. + --> + +<resources> + <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size --> + <dimen name="biometric_auth_pattern_view_size">248dp</dimen> + <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml index 7da47e5089be..ff6e005a94c7 100644 --- a/packages/SystemUI/res/values-sw410dp/dimens.xml +++ b/packages/SystemUI/res/values-sw410dp/dimens.xml @@ -27,4 +27,6 @@ <dimen name="global_actions_grid_item_side_margin">12dp</dimen> <dimen name="global_actions_grid_item_height">72dp</dimen> + <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> + <dimen name="biometric_auth_pattern_view_size">348dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml index 8148d3dfaf7d..c535c6416106 100644 --- a/packages/SystemUI/res/values-sw600dp-land/styles.xml +++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml @@ -18,10 +18,10 @@ <style name="AuthCredentialPatternContainerStyle"> <item name="android:gravity">center</item> - <item name="android:maxHeight">420dp</item> - <item name="android:maxWidth">420dp</item> - <item name="android:minHeight">200dp</item> - <item name="android:minWidth">200dp</item> + <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item> + <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item> <item name="android:paddingHorizontal">120dp</item> <item name="android:paddingVertical">40dp</item> </style> diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml index 771de08cb360..32eefa7316b2 100644 --- a/packages/SystemUI/res/values-sw600dp-port/styles.xml +++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml @@ -26,10 +26,10 @@ <style name="AuthCredentialPatternContainerStyle"> <item name="android:gravity">center</item> - <item name="android:maxHeight">420dp</item> - <item name="android:maxWidth">420dp</item> - <item name="android:minHeight">200dp</item> - <item name="android:minWidth">200dp</item> + <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item> + <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item> <item name="android:paddingHorizontal">180dp</item> <item name="android:paddingVertical">80dp</item> </style> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 599bf30a5135..9bc0dde49b4c 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -92,4 +92,6 @@ <dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen> <dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen> + <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> + <dimen name="biometric_auth_pattern_view_size">348dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml index f9ed67d89de7..6a70ebd07ad2 100644 --- a/packages/SystemUI/res/values-sw720dp-land/styles.xml +++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml @@ -18,10 +18,10 @@ <style name="AuthCredentialPatternContainerStyle"> <item name="android:gravity">center</item> - <item name="android:maxHeight">420dp</item> - <item name="android:maxWidth">420dp</item> - <item name="android:minHeight">200dp</item> - <item name="android:minWidth">200dp</item> + <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item> + <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item> <item name="android:paddingHorizontal">120dp</item> <item name="android:paddingVertical">40dp</item> </style> diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml index 78d299c483e6..0a46e08da22a 100644 --- a/packages/SystemUI/res/values-sw720dp-port/styles.xml +++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml @@ -26,10 +26,10 @@ <style name="AuthCredentialPatternContainerStyle"> <item name="android:gravity">center</item> - <item name="android:maxHeight">420dp</item> - <item name="android:maxWidth">420dp</item> - <item name="android:minHeight">200dp</item> - <item name="android:minWidth">200dp</item> + <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item> + <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item> <item name="android:paddingHorizontal">240dp</item> <item name="android:paddingVertical">120dp</item> </style> diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml index 07050171470a..927059aa7e40 100644 --- a/packages/SystemUI/res/values-sw720dp/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp/dimens.xml @@ -22,5 +22,8 @@ <dimen name="controls_padding_horizontal">75dp</dimen> <dimen name="large_screen_shade_header_height">56dp</dimen> + + <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> + <dimen name="biometric_auth_pattern_view_size">348dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw800dp/dimens.xml b/packages/SystemUI/res/values-sw800dp/dimens.xml new file mode 100644 index 000000000000..0d82217456e4 --- /dev/null +++ b/packages/SystemUI/res/values-sw800dp/dimens.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 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. + --> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + + <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> + <dimen name="biometric_auth_pattern_view_size">348dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 077ef0f16fe2..e8a5e7ed8546 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -668,6 +668,16 @@ <item>17</item> <!-- WAKE_REASON_BIOMETRIC --> </integer-array> + <!-- Whether to support posture listening for face auth, default is 0(DEVICE_POSTURE_UNKNOWN) + means systemui will try listening on all postures. + 0 : DEVICE_POSTURE_UNKNOWN + 1 : DEVICE_POSTURE_CLOSED + 2 : DEVICE_POSTURE_HALF_OPENED + 3 : DEVICE_POSTURE_OPENED + 4 : DEVICE_POSTURE_FLIPPED + --> + <integer name="config_face_auth_supported_posture">0</integer> + <!-- Whether the communal service should be enabled --> <bool name="config_communalServiceEnabled">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 6cb012a85cbf..890d96444b04 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -334,15 +334,22 @@ <dimen name="overlay_action_chip_spacing">8dp</dimen> <dimen name="overlay_action_chip_text_size">14sp</dimen> <dimen name="overlay_offset_x">16dp</dimen> + <!-- Used for both start and bottom margin of the preview, relative to the action container --> + <dimen name="overlay_preview_container_margin">8dp</dimen> <dimen name="overlay_action_container_margin_horizontal">8dp</dimen> + <dimen name="overlay_action_container_margin_bottom">4dp</dimen> <dimen name="overlay_bg_protection_height">242dp</dimen> <dimen name="overlay_action_container_corner_radius">18dp</dimen> <dimen name="overlay_action_container_padding_vertical">4dp</dimen> <dimen name="overlay_action_container_padding_right">8dp</dimen> + <dimen name="overlay_action_container_padding_end">8dp</dimen> <dimen name="overlay_dismiss_button_tappable_size">48dp</dimen> <dimen name="overlay_dismiss_button_margin">8dp</dimen> + <!-- must be kept aligned with overlay_border_width_neg, below; + overlay_border_width = overlay_border_width_neg * -1 --> <dimen name="overlay_border_width">4dp</dimen> - <!-- need a negative margin for some of the constraints. should be overlay_border_width * -1 --> + <!-- some constraints use a negative margin. must be aligned with overlay_border_width, above; + overlay_border_width_neg = overlay_border_width * -1 --> <dimen name="overlay_border_width_neg">-4dp</dimen> <dimen name="clipboard_preview_size">@dimen/overlay_x_scale</dimen> @@ -966,6 +973,10 @@ <!-- Biometric Auth Credential values --> <dimen name="biometric_auth_icon_size">48dp</dimen> + <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> + <dimen name="biometric_auth_pattern_view_size">348dp</dimen> + <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen> + <!-- Starting text size in sp of batteryLevel for wireless charging animation --> <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen"> 0 @@ -1030,8 +1041,6 @@ <dimen name="ongoing_appops_dialog_side_padding">16dp</dimen> - <!-- Size of the RAT type for CellularTile --> - <!-- Size of media cards in the QSPanel carousel --> <dimen name="qs_media_padding">16dp</dimen> <dimen name="qs_media_album_radius">14dp</dimen> @@ -1046,6 +1055,7 @@ <dimen name="qs_media_disabled_seekbar_height">1dp</dimen> <dimen name="qs_media_enabled_seekbar_height">2dp</dimen> <dimen name="qs_media_app_icon_size">24dp</dimen> + <dimen name="qs_media_explicit_indicator_icon_size">13dp</dimen> <dimen name="qs_media_session_enabled_seekbar_vertical_padding">15dp</dimen> <dimen name="qs_media_session_disabled_seekbar_vertical_padding">16dp</dimen> @@ -1282,6 +1292,9 @@ <!-- LOCKSCREEN -> DREAMING transition: Amount to shift lockscreen content on entering --> <dimen name="lockscreen_to_dreaming_transition_lockscreen_translation_y">-40dp</dimen> + <!-- GONE -> DREAMING transition: Amount to shift lockscreen content on entering --> + <dimen name="gone_to_dreaming_transition_lockscreen_translation_y">-40dp</dimen> + <!-- LOCKSCREEN -> OCCLUDED transition: Amount to shift lockscreen content on entering --> <dimen name="lockscreen_to_occluded_transition_lockscreen_translation_y">-40dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 22d4c6d651e2..2de16a417d20 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2359,10 +2359,10 @@ <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play media on the different device. [CHAR LIMIT=75] --> <string name="media_move_closer_to_start_cast">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string> <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to transfer media from the different device and back onto the current device. [CHAR LIMIT=75] --> - <string name="media_move_closer_to_end_cast">Move closer to <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g> to play here</string> + <string name="media_move_closer_to_end_cast">To play here, move closer to <xliff:g id="deviceName" example="tablet">%1$s</xliff:g></string> <!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] --> <string name="media_transfer_playing_different_device">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string> - <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIsMIT=50] --> + <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIMIT=50] --> <string name="media_transfer_failed">Something went wrong. Try again.</string> <!-- Text to indicate that a media transfer is currently in-progress, aka loading. [CHAR LIMIT=NONE] --> <string name="media_transfer_loading">Loading</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index b11b6d633f14..9846fc251a27 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -251,11 +251,12 @@ <style name="AuthCredentialPatternContainerStyle"> <item name="android:gravity">center</item> - <item name="android:maxHeight">420dp</item> - <item name="android:maxWidth">420dp</item> - <item name="android:minHeight">200dp</item> - <item name="android:minWidth">200dp</item> - <item name="android:padding">20dp</item> + <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item> + <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item> + <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item> + <item name="android:paddingHorizontal">32dp</item> + <item name="android:paddingVertical">20dp</item> </style> <style name="AuthCredentialPinPasswordContainerStyle"> diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml index 1eb621e0368b..d9c81af54a12 100644 --- a/packages/SystemUI/res/xml/media_session_collapsed.xml +++ b/packages/SystemUI/res/xml/media_session_collapsed.xml @@ -66,6 +66,21 @@ app:layout_constraintTop_toBottomOf="@id/icon" app:layout_constraintStart_toStartOf="parent" app:layout_constraintHorizontal_bias="0" /> + + <Constraint + android:id="@+id/media_explicit_indicator" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/qs_media_info_spacing" + android:layout_marginBottom="@dimen/qs_media_padding" + android:layout_marginTop="0dp" + app:layout_constraintStart_toStartOf="@id/header_title" + app:layout_constraintEnd_toStartOf="@id/header_artist" + app:layout_constraintTop_toTopOf="@id/header_artist" + app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintHorizontal_chainStyle="packed" /> + <Constraint android:id="@+id/header_artist" android:layout_width="wrap_content" @@ -75,9 +90,8 @@ app:layout_constraintEnd_toStartOf="@id/action_button_guideline" app:layout_constrainedWidth="true" app:layout_constraintTop_toBottomOf="@id/header_title" - app:layout_constraintStart_toStartOf="@id/header_title" - app:layout_constraintVertical_bias="0" - app:layout_constraintHorizontal_bias="0" /> + app:layout_constraintStart_toEndOf="@id/media_explicit_indicator" + app:layout_constraintVertical_bias="0" /> <Constraint android:id="@+id/actionPlayPause" diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml index 7de0a5e0e8c4..0cdc0f9505bc 100644 --- a/packages/SystemUI/res/xml/media_session_expanded.xml +++ b/packages/SystemUI/res/xml/media_session_expanded.xml @@ -58,6 +58,21 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toTopOf="@id/header_artist" app:layout_constraintHorizontal_bias="0" /> + + <Constraint + android:id="@+id/media_explicit_indicator" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/qs_media_info_spacing" + android:layout_marginBottom="@dimen/qs_media_padding" + android:layout_marginTop="0dp" + app:layout_constraintStart_toStartOf="@id/header_title" + app:layout_constraintEnd_toStartOf="@id/header_artist" + app:layout_constraintTop_toTopOf="@id/header_artist" + app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintHorizontal_chainStyle="packed"/> + <Constraint android:id="@+id/header_artist" android:layout_width="wrap_content" @@ -67,10 +82,9 @@ android:layout_marginTop="0dp" app:layout_constrainedWidth="true" app:layout_constraintEnd_toStartOf="@id/actionPlayPause" - app:layout_constraintStart_toStartOf="@id/header_title" + app:layout_constraintStart_toEndOf="@id/media_explicit_indicator" app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top" - app:layout_constraintVertical_bias="0" - app:layout_constraintHorizontal_bias="0" /> + app:layout_constraintVertical_bias="0" /> <Constraint android:id="@+id/actionPlayPause" diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml index eca2b2acb079..d97031f35d6b 100644 --- a/packages/SystemUI/res/xml/qs_header.xml +++ b/packages/SystemUI/res/xml/qs_header.xml @@ -56,13 +56,9 @@ <Layout android:layout_width="wrap_content" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" - app:layout_constrainedWidth="true" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/space" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/carrier_group" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintHorizontal_chainStyle="spread_inside" /> </Constraint> @@ -87,39 +83,27 @@ <Constraint android:id="@+id/statusIcons"> <Layout - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" - app:layout_constraintStart_toEndOf="@id/space" + app:layout_constraintWidth_default="wrap" + app:layout_constraintStart_toEndOf="@id/date" app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" app:layout_constraintTop_toTopOf="@id/date" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintHorizontal_bias="1" + app:layout_constraintBottom_toBottomOf="@id/date" /> </Constraint> <Constraint android:id="@+id/batteryRemainingIcon"> <Layout - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" + app:layout_constraintWidth_default="wrap" app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height" - app:layout_constraintStart_toEndOf="@id/statusIcons" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/date" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintHorizontal_bias="1" - app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintBottom_toBottomOf="@id/date" /> </Constraint> - - <Constraint - android:id="@id/space"> - <Layout - android:layout_width="0dp" - android:layout_height="0dp" - app:layout_constraintStart_toEndOf="@id/date" - app:layout_constraintEnd_toStartOf="@id/statusIcons" - /> - </Constraint> </ConstraintSet>
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index fd41cb0630dd..6bfaf5e49820 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -283,17 +283,6 @@ public class ActivityManagerWrapper { } /** - * @return whether screen pinning is active. - */ - public boolean isScreenPinningActive() { - try { - return getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED; - } catch (RemoteException e) { - return false; - } - } - - /** * @return whether screen pinning is enabled. */ public boolean isScreenPinningEnabled() { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java index 8af934f66b2a..dd52cfbdc80f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java @@ -16,6 +16,7 @@ package com.android.systemui.shared.system; +import android.annotation.NonNull; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.app.TaskStackListener; @@ -27,6 +28,8 @@ import android.os.Trace; import android.util.Log; import android.window.TaskSnapshot; +import androidx.annotation.VisibleForTesting; + import com.android.internal.os.SomeArgs; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -43,15 +46,51 @@ public class TaskStackChangeListeners { private final Impl mImpl; + /** + * Proxies calls to the given handler callback synchronously for testing purposes. + */ + private static class TestSyncHandler extends Handler { + private Handler.Callback mCb; + + public TestSyncHandler() { + super(Looper.getMainLooper()); + } + + public void setCallback(Handler.Callback cb) { + mCb = cb; + } + + @Override + public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { + return mCb.handleMessage(msg); + } + } + private TaskStackChangeListeners() { mImpl = new Impl(Looper.getMainLooper()); } + private TaskStackChangeListeners(Handler h) { + mImpl = new Impl(h); + } + public static TaskStackChangeListeners getInstance() { return INSTANCE; } /** + * Returns an instance of the listeners that can be called upon synchronously for testsing + * purposes. + */ + @VisibleForTesting + public static TaskStackChangeListeners getTestInstance() { + TestSyncHandler h = new TestSyncHandler(); + TaskStackChangeListeners l = new TaskStackChangeListeners(h); + h.setCallback(l.mImpl); + return l; + } + + /** * Registers a task stack listener with the system. * This should be called on the main thread. */ @@ -71,7 +110,15 @@ public class TaskStackChangeListeners { } } - private static class Impl extends TaskStackListener implements Handler.Callback { + /** + * Returns an instance of the listener to call upon from tests. + */ + @VisibleForTesting + public TaskStackListener getListenerImpl() { + return mImpl; + } + + private class Impl extends TaskStackListener implements Handler.Callback { private static final int ON_TASK_STACK_CHANGED = 1; private static final int ON_TASK_SNAPSHOT_CHANGED = 2; @@ -104,10 +151,14 @@ public class TaskStackChangeListeners { private final Handler mHandler; private boolean mRegistered; - Impl(Looper looper) { + private Impl(Looper looper) { mHandler = new Handler(looper, this); } + private Impl(Handler handler) { + mHandler = handler; + } + public void addListener(TaskStackChangeListener listener) { synchronized (mTaskStackListeners) { mTaskStackListeners.add(listener); diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt index 5bb9367fa4a5..e0cf7b6a2bc4 100644 --- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt +++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt @@ -50,6 +50,7 @@ import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_RESET import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_VISIBILITY_CHANGED import com.android.keyguard.InternalFaceAuthReasons.NON_STRONG_BIOMETRIC_ALLOWED_CHANGED import com.android.keyguard.InternalFaceAuthReasons.OCCLUDING_APP_REQUESTED +import com.android.keyguard.InternalFaceAuthReasons.POSTURE_CHANGED import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN import com.android.keyguard.InternalFaceAuthReasons.RETRY_AFTER_HW_UNAVAILABLE @@ -126,6 +127,7 @@ private object InternalFaceAuthReasons { const val STRONG_AUTH_ALLOWED_CHANGED = "Face auth stopped because strong auth allowed changed" const val NON_STRONG_BIOMETRIC_ALLOWED_CHANGED = "Face auth stopped because non strong biometric allowed changed" + const val POSTURE_CHANGED = "Face auth started/stopped due to device posture changed." } /** @@ -173,6 +175,7 @@ constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) : return PowerManager.wakeReasonToString(extraInfo) } }, + @UiEvent(doc = POSTURE_CHANGED) FACE_AUTH_UPDATED_POSTURE_CHANGED(1265, POSTURE_CHANGED), @Deprecated( "Not a face auth trigger.", ReplaceWith( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt index deead1959b8a..1a06b5f1c767 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt @@ -39,6 +39,7 @@ data class KeyguardFaceListenModel( var keyguardGoingAway: Boolean = false, var listeningForFaceAssistant: Boolean = false, var occludingAppRequestingFaceAuth: Boolean = false, + val postureAllowsListening: Boolean = false, var primaryUser: Boolean = false, var secureCameraLaunched: Boolean = false, var supportsDetect: Boolean = false, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 8de1368091ea..204f09ee4678 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -63,11 +63,13 @@ import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_RE import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_KEYGUARD_INIT; +import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_POSTURE_CHANGED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING; import static com.android.systemui.DejankUtils.whitelistIpcs; +import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; import android.annotation.AnyThread; import android.annotation.MainThread; @@ -155,6 +157,7 @@ import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.Assert; import com.android.systemui.util.settings.SecureSettings; @@ -357,6 +360,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final TrustManager mTrustManager; private final UserManager mUserManager; private final DevicePolicyManager mDevicePolicyManager; + private final DevicePostureController mPostureController; private final BroadcastDispatcher mBroadcastDispatcher; private final SecureSettings mSecureSettings; private final InteractionJankMonitor mInteractionJankMonitor; @@ -374,6 +378,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final FaceManager mFaceManager; private final LockPatternUtils mLockPatternUtils; private final boolean mWakeOnFingerprintAcquiredStart; + @VisibleForTesting + @DevicePostureController.DevicePostureInt + protected int mConfigFaceAuthSupportedPosture; private KeyguardBypassController mKeyguardBypassController; private List<SubscriptionInfo> mSubscriptionInfo; @@ -384,6 +391,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mLogoutEnabled; private boolean mIsFaceEnrolled; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private int mPostureState = DEVICE_POSTURE_UNKNOWN; private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider; /** @@ -712,8 +720,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void setKeyguardGoingAway(boolean goingAway) { mKeyguardGoingAway = goingAway; - // This is set specifically to stop face authentication from running. - updateBiometricListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY); + if (mKeyguardGoingAway) { + updateFaceListeningState(BIOMETRIC_ACTION_STOP, + FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY); + } + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } /** @@ -1792,6 +1803,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab }; @VisibleForTesting + final DevicePostureController.Callback mPostureCallback = + new DevicePostureController.Callback() { + @Override + public void onPostureChanged(int posture) { + mPostureState = posture; + updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, + FACE_AUTH_UPDATED_POSTURE_CHANGED); + } + }; + + @VisibleForTesting CancellationSignal mFingerprintCancelSignal; @VisibleForTesting CancellationSignal mFaceCancelSignal; @@ -1951,9 +1973,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onFinishedGoingToSleep(arg1); } } - // This is set specifically to stop face authentication from running. - updateBiometricListeningState(BIOMETRIC_ACTION_STOP, + updateFaceListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } private void handleScreenTurnedOff() { @@ -2057,6 +2079,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Nullable FingerprintManager fingerprintManager, @Nullable BiometricManager biometricManager, FaceWakeUpTriggersConfig faceWakeUpTriggersConfig, + DevicePostureController devicePostureController, Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider) { mContext = context; mSubscriptionManager = subscriptionManager; @@ -2086,6 +2109,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mDreamManager = dreamManager; mTelephonyManager = telephonyManager; mDevicePolicyManager = devicePolicyManager; + mPostureController = devicePostureController; mPackageManager = packageManager; mFpm = fingerprintManager; mFaceManager = faceManager; @@ -2097,6 +2121,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab R.array.config_face_acquire_device_entry_ignorelist)) .boxed() .collect(Collectors.toSet()); + mConfigFaceAuthSupportedPosture = mContext.getResources().getInteger( + R.integer.config_face_auth_supported_posture); mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig; mHandler = new Handler(mainLooper) { @@ -2287,6 +2313,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED)); } }); + if (mConfigFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) { + mPostureController.addCallback(mPostureCallback); + } updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ON_KEYGUARD_INIT); TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); @@ -2719,7 +2748,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintInteractiveToAuthProvider != null && mFingerprintInteractiveToAuthProvider.isEnabled(getCurrentUser()); shouldListenSideFpsState = - interactiveToAuthEnabled ? isDeviceInteractive() : true; + interactiveToAuthEnabled ? isDeviceInteractive() && !mGoingToSleep : true; } boolean shouldListen = shouldListenKeyguardState && shouldListenUserState @@ -2731,7 +2760,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab user, shouldListen, biometricEnabledForUser, - mPrimaryBouncerIsOrWillBeShowing, + mPrimaryBouncerIsOrWillBeShowing, userCanSkipBouncer, mCredentialAttempted, mDeviceInteractive, @@ -2791,6 +2820,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user); final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant(); final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown(); + final boolean isPostureAllowedForFaceAuth = + mConfigFaceAuthSupportedPosture == 0 /* DEVICE_POSTURE_UNKNOWN */ ? true + : (mPostureState == mConfigFaceAuthSupportedPosture); // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. @@ -2807,7 +2839,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && faceAuthAllowedOrDetectionIsNeeded && mIsPrimaryUser && (!mSecureCameraLaunched || mOccludingAppRequestingFace) && faceAndFpNotAuthenticated - && !mGoingToSleep; + && !mGoingToSleep + && isPostureAllowedForFaceAuth; // Aggregate relevant fields for debug logging. logListenerModelData( @@ -2827,6 +2860,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mKeyguardGoingAway, shouldListenForFaceAssistant, mOccludingAppRequestingFace, + isPostureAllowedForFaceAuth, mIsPrimaryUser, mSecureCameraLaunched, supportsDetect, @@ -2912,7 +2946,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab getKeyguardSessionId(), faceAuthUiEvent.getExtraInfo() ); - + mLogger.logFaceUnlockPossible(unlockPossible); if (unlockPossible) { mFaceCancelSignal = new CancellationSignal(); diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 21d3b24174b6..5b4245595be9 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -132,6 +132,12 @@ class KeyguardUpdateMonitorLogger @Inject constructor( logBuffer.log(TAG, DEBUG, { int1 = faceRunningState }, { "faceRunningState: $int1" }) } + fun logFaceUnlockPossible(isFaceUnlockPossible: Boolean) { + logBuffer.log(TAG, DEBUG, + { bool1 = isFaceUnlockPossible }, + {"isUnlockWithFacePossible: $bool1"}) + } + fun logFingerprintAuthForWrongUser(authUserId: Int) { logBuffer.log(TAG, DEBUG, { int1 = authUserId }, diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java index 0fc9ef96f6e9..632fcdc16259 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java @@ -22,8 +22,6 @@ import android.os.Handler; import android.os.HandlerThread; import android.util.Log; -import androidx.annotation.Nullable; - import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dagger.WMComponent; @@ -55,7 +53,6 @@ public abstract class SystemUIInitializer { mContext = context; } - @Nullable protected abstract GlobalRootComponent.Builder getGlobalRootComponentBuilder(); /** @@ -72,11 +69,6 @@ public abstract class SystemUIInitializer { * Starts the initialization process. This stands up the Dagger graph. */ public void init(boolean fromTest) throws ExecutionException, InterruptedException { - GlobalRootComponent.Builder globalBuilder = getGlobalRootComponentBuilder(); - if (globalBuilder == null) { - return; - } - mRootComponent = getGlobalRootComponentBuilder() .context(mContext) .instrumentationTest(fromTest) @@ -127,7 +119,6 @@ public abstract class SystemUIInitializer { .setBackAnimation(Optional.ofNullable(null)) .setDesktopMode(Optional.ofNullable(null)); } - mSysUIComponent = builder.build(); if (initializeComponents) { mSysUIComponent.init(); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt index 55c095b0be25..8aa3040c6015 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt @@ -16,7 +16,6 @@ package com.android.systemui -import android.app.Application import android.content.Context import com.android.systemui.dagger.DaggerReferenceGlobalRootComponent import com.android.systemui.dagger.GlobalRootComponent @@ -25,17 +24,7 @@ import com.android.systemui.dagger.GlobalRootComponent * {@link SystemUIInitializer} that stands up AOSP SystemUI. */ class SystemUIInitializerImpl(context: Context) : SystemUIInitializer(context) { - - override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder? { - return when (Application.getProcessName()) { - SCREENSHOT_CROSS_PROFILE_PROCESS -> null - else -> DaggerReferenceGlobalRootComponent.builder() - } - } - - companion object { - private const val SYSTEMUI_PROCESS = "com.android.systemui" - private const val SCREENSHOT_CROSS_PROFILE_PROCESS = - "$SYSTEMUI_PROCESS:screenshot_cross_profile" + override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder { + return DaggerReferenceGlobalRootComponent.builder() } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt index 857224290752..682d38a8f1a8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt @@ -18,6 +18,7 @@ package com.android.systemui.biometrics.udfps import android.graphics.Point import android.graphics.Rect +import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import kotlin.math.cos import kotlin.math.pow @@ -50,7 +51,8 @@ class EllipseOverlapDetector(private val neededPoints: Int = 2) : OverlapDetecto return result <= 1 } - private fun calculateSensorPoints(sensorBounds: Rect): List<Point> { + @VisibleForTesting + fun calculateSensorPoints(sensorBounds: Rect): List<Point> { val sensorX = sensorBounds.centerX() val sensorY = sensorBounds.centerY() val cornerOffset: Int = sensorBounds.width() / 4 diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index e8e1f2e95f5d..e9ac840cf4f4 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -176,7 +176,8 @@ public class BrightLineFalsingManager implements FalsingManager { private @Classifier.InteractionType int mPriorInteractionType = Classifier.GENERIC; @Inject - public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider, + public BrightLineFalsingManager( + FalsingDataProvider falsingDataProvider, MetricsLogger metricsLogger, @Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers, SingleTapClassifier singleTapClassifier, LongTapClassifier longTapClassifier, @@ -399,7 +400,9 @@ public class BrightLineFalsingManager implements FalsingManager { || mDataProvider.isJustUnlockedWithFace() || mDataProvider.isDocked() || mAccessibilityManager.isTouchExplorationEnabled() - || mDataProvider.isA11yAction(); + || mDataProvider.isA11yAction() + || (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED) + && !mDataProvider.isFolded()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index 09ebeeac163f..5f347c158818 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -16,6 +16,7 @@ package com.android.systemui.classifier; +import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; @@ -42,6 +43,7 @@ public class FalsingDataProvider { private final int mWidthPixels; private final int mHeightPixels; private BatteryController mBatteryController; + private final FoldStateListener mFoldStateListener; private final DockManager mDockManager; private final float mXdpi; private final float mYdpi; @@ -65,12 +67,14 @@ public class FalsingDataProvider { public FalsingDataProvider( DisplayMetrics displayMetrics, BatteryController batteryController, + FoldStateListener foldStateListener, DockManager dockManager) { mXdpi = displayMetrics.xdpi; mYdpi = displayMetrics.ydpi; mWidthPixels = displayMetrics.widthPixels; mHeightPixels = displayMetrics.heightPixels; mBatteryController = batteryController; + mFoldStateListener = foldStateListener; mDockManager = dockManager; FalsingClassifier.logInfo("xdpi, ydpi: " + getXdpi() + ", " + getYdpi()); @@ -376,6 +380,10 @@ public class FalsingDataProvider { return mBatteryController.isWirelessCharging() || mDockManager.isDocked(); } + public boolean isFolded() { + return Boolean.TRUE.equals(mFoldStateListener.getFolded()); + } + /** Implement to be alerted abotu the beginning and ending of falsing tracking. */ public interface SessionListener { /** Called when the lock screen is shown and falsing-tracking begins. */ diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt index eed55315e836..9b2a224f17e0 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt @@ -51,13 +51,22 @@ interface ControlsBindingController : UserAwareController { fun bindAndLoadSuggested(component: ComponentName, callback: LoadCallback) /** - * Request to bind to the given service. + * Request to bind to the given service. This should only be used for services using the full + * [ControlsProviderService] API, where SystemUI renders the devices' UI. * * @param component The [ComponentName] of the service to bind */ fun bindService(component: ComponentName) /** + * Bind to a service that provides a Device Controls panel (embedded activity). This will allow + * the app to remain "warm", and reduce latency. + * + * @param component The [ComponentName] of the [ControlsProviderService] to bind. + */ + fun bindServiceForPanel(component: ComponentName) + + /** * Send a subscribe message to retrieve status of a set of controls. * * @param structureInfo structure containing the controls to update diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt index 2f0fd99337e5..3d6d3356fb55 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -170,6 +170,10 @@ open class ControlsBindingControllerImpl @Inject constructor( retrieveLifecycleManager(component).bindService() } + override fun bindServiceForPanel(component: ComponentName) { + retrieveLifecycleManager(component).bindServiceForPanel() + } + override fun changeUser(newUser: UserHandle) { if (newUser == currentUser) return diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index 2f49c3fe863e..f29f6d0dd0cb 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -189,6 +189,14 @@ interface ControlsController : UserAwareController { fun getPreferredSelection(): SelectedItem /** + * Bind to a service that provides a Device Controls panel (embedded activity). This will allow + * the app to remain "warm", and reduce latency. + * + * @param component The [ComponentName] of the [ControlsProviderService] to bind. + */ + fun bindComponentForPanel(componentName: ComponentName) + + /** * Interface for structure to pass data to [ControlsFavoritingActivity]. */ interface LoadData { diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 80c5f661f9a3..111fcbbe30be 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -477,6 +477,10 @@ class ControlsControllerImpl @Inject constructor ( bindingController.unsubscribe() } + override fun bindComponentForPanel(componentName: ComponentName) { + bindingController.bindServiceForPanel(componentName) + } + override fun addFavorite( componentName: ComponentName, structureName: CharSequence, diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt index 5b38e5b28be9..72c3a943c30b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt @@ -78,6 +78,10 @@ class ControlsProviderLifecycleManager( private const val DEBUG = true private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or Context.BIND_NOT_PERCEPTIBLE + // Use BIND_NOT_PERCEPTIBLE so it will be at lower priority from SystemUI. + // However, don't use WAIVE_PRIORITY, as by itself, it will kill the app + // once the Task is finished in the device controls panel. + private val BIND_FLAGS_PANEL = Context.BIND_AUTO_CREATE or Context.BIND_NOT_PERCEPTIBLE } private val intent = Intent().apply { @@ -87,18 +91,19 @@ class ControlsProviderLifecycleManager( }) } - private fun bindService(bind: Boolean) { + private fun bindService(bind: Boolean, forPanel: Boolean = false) { executor.execute { requiresBound = bind if (bind) { - if (bindTryCount != MAX_BIND_RETRIES) { + if (bindTryCount != MAX_BIND_RETRIES && wrapper == null) { if (DEBUG) { Log.d(TAG, "Binding service $intent") } bindTryCount++ try { + val flags = if (forPanel) BIND_FLAGS_PANEL else BIND_FLAGS val bound = context - .bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user) + .bindServiceAsUser(intent, serviceConnection, flags, user) if (!bound) { context.unbindService(serviceConnection) } @@ -279,6 +284,10 @@ class ControlsProviderLifecycleManager( bindService(true) } + fun bindServiceForPanel() { + bindService(bind = true, forPanel = true) + } + /** * Request unbind from the service. */ diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 1e3e5cd1c31c..6289788f650a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -232,6 +232,8 @@ class ControlsUiControllerImpl @Inject constructor ( ControlKey(selected.structure.componentName, it.ci.controlId) } controlsController.get().subscribeToFavorites(selected.structure) + } else { + controlsController.get().bindComponentForPanel(selected.componentName) } listingCallback = createCallback(::showControlsView) } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index e79484b72065..6958f3b1bfe1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -200,13 +200,16 @@ object Flags { /** A different path for unocclusion transitions back to keyguard */ // TODO(b/262859270): Tracking Bug @JvmField - val UNOCCLUSION_TRANSITION = unreleasedFlag(223, "unocclusion_transition", teamfood = false) + val UNOCCLUSION_TRANSITION = unreleasedFlag(223, "unocclusion_transition", teamfood = true) // flag for controlling auto pin confirmation and material u shapes in bouncer @JvmField val AUTO_PIN_CONFIRMATION = unreleasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation") + // TODO(b/262859270): Tracking Bug + @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -260,10 +263,11 @@ object Flags { // TODO(b/256614751): Tracking Bug val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND = - unreleasedFlag(608, "new_status_bar_mobile_icons_backend") + unreleasedFlag(608, "new_status_bar_mobile_icons_backend", teamfood = true) // TODO(b/256613548): Tracking Bug - val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend") + val NEW_STATUS_BAR_WIFI_ICON_BACKEND = + unreleasedFlag(609, "new_status_bar_wifi_icon_backend", teamfood = true) // TODO(b/256623670): Tracking Bug @JvmField @@ -302,7 +306,7 @@ object Flags { // 900 - media // TODO(b/254512697): Tracking Bug - val MEDIA_TAP_TO_TRANSFER = unreleasedFlag(900, "media_tap_to_transfer", teamfood = true) + val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer") // TODO(b/254512502): Tracking Bug val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions") @@ -332,6 +336,9 @@ object Flags { val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true) + // TODO(b/263512203): Tracking Bug + val MEDIA_EXPLICIT_INDICATOR = unreleasedFlag(911, "media_explicit_indicator", teamfood = true) + // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 8200f259a992..fe84ac5a32bc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1926,13 +1926,23 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, return; } - // if the keyguard is already showing, don't bother. check flags in both files - // to account for the hiding animation which results in a delay and discrepancy - // between flags + // If the keyguard is already showing, see if we don't need to bother re-showing it. Check + // flags in both files to account for the hiding animation which results in a delay and + // discrepancy between flags. if (mShowing && mKeyguardStateController.isShowing()) { - if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing"); - resetStateLocked(); - return; + if (mPM.isInteractive()) { + // It's already showing, and we're not trying to show it while the screen is off. + // We can simply reset all of the views. + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing"); + resetStateLocked(); + return; + } else { + // We are trying to show the keyguard while the screen is off - this results from + // race conditions involving locking while unlocking. Don't short-circuit here and + // ensure the keyguard is fully re-shown. + Log.e(TAG, + "doKeyguard: already showing, but re-showing since we're not interactive"); + } } // In split system user mode, we never unlock system user. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java index 017b65acd1d2..ffd8a0244a86 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java @@ -33,6 +33,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; +import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -63,6 +64,7 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe private final Context mContext; private final DisplayMetrics mDisplayMetrics; + private final SystemClock mSystemClock; @Nullable private final IWallpaperManager mWallpaperManagerService; @@ -71,6 +73,9 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe private @PowerManager.WakeReason int mLastWakeReason = PowerManager.WAKE_REASON_UNKNOWN; + public static final long UNKNOWN_LAST_WAKE_TIME = -1; + private long mLastWakeTime = UNKNOWN_LAST_WAKE_TIME; + @Nullable private Point mLastWakeOriginLocation = null; @@ -84,10 +89,12 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe public WakefulnessLifecycle( Context context, @Nullable IWallpaperManager wallpaperManagerService, + SystemClock systemClock, DumpManager dumpManager) { mContext = context; mDisplayMetrics = context.getResources().getDisplayMetrics(); mWallpaperManagerService = wallpaperManagerService; + mSystemClock = systemClock; dumpManager.registerDumpable(getClass().getSimpleName(), this); } @@ -104,6 +111,14 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe } /** + * Returns the most recent time (in device uptimeMillis) the display woke up. + * Returns {@link UNKNOWN_LAST_WAKE_TIME} if there hasn't been a wakeup yet. + */ + public long getLastWakeTime() { + return mLastWakeTime; + } + + /** * Returns the most recent reason the device went to sleep up. This is one of * PowerManager.GO_TO_SLEEP_REASON_*. */ @@ -117,6 +132,7 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe } setWakefulness(WAKEFULNESS_WAKING); mLastWakeReason = pmWakeReason; + mLastWakeTime = mSystemClock.uptimeMillis(); updateLastWakeOriginLocation(); if (mWallpaperManagerService != null) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index d14b66a68f11..0c4bca616e12 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -209,7 +209,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio return } - if (state == TransitionState.FINISHED) { + if (state == TransitionState.FINISHED || state == TransitionState.CANCELED) { updateTransitionId = null } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 3b09ae7ba8ea..7134ec0d64f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -21,7 +21,7 @@ import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff import com.android.systemui.keyguard.shared.model.KeyguardState @@ -56,7 +56,7 @@ constructor( scope.launch { // Using isDreamingWithOverlay provides an optimized path to LOCKSCREEN state, which // otherwise would have gone through OCCLUDED first - keyguardInteractor.isDreamingWithOverlay + keyguardInteractor.isAbleToDream .sample( combine( keyguardInteractor.dozeTransitionModel, @@ -65,8 +65,7 @@ constructor( ), ::toTriple ) - .collect { triple -> - val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple + .collect { (isDreaming, dozeTransitionModel, lastStartedTransition) -> if ( !isDreaming && isDozeOff(dozeTransitionModel.to) && @@ -96,8 +95,7 @@ constructor( ), ::toTriple ) - .collect { triple -> - val (isDreaming, isOccluded, lastStartedTransition) = triple + .collect { (isDreaming, isOccluded, lastStartedTransition) -> if ( isOccluded && !isDreaming && @@ -123,24 +121,18 @@ constructor( private fun listenForDreamingToGone() { scope.launch { - keyguardInteractor.biometricUnlockState - .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair) - .collect { pair -> - val (biometricUnlockState, keyguardState) = pair - if ( - keyguardState == KeyguardState.DREAMING && - isWakeAndUnlock(biometricUnlockState) - ) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - name, - KeyguardState.DREAMING, - KeyguardState.GONE, - getAnimator(), - ) + keyguardInteractor.biometricUnlockState.collect { biometricUnlockState -> + if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + KeyguardState.GONE, + getAnimator(), ) - } + ) } + } } } @@ -151,8 +143,7 @@ constructor( keyguardTransitionInteractor.finishedKeyguardState, ::Pair ) - .collect { pair -> - val (dozeTransitionModel, keyguardState) = pair + .collect { (dozeTransitionModel, keyguardState) -> if ( dozeTransitionModel.to == DozeStateModel.DOZE && keyguardState == KeyguardState.DREAMING diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 64028ceb2fbe..5674e2a15271 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -48,8 +48,6 @@ constructor( private val keyguardTransitionRepository: KeyguardTransitionRepository, ) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) { - private var transitionId: UUID? = null - override fun start() { listenForLockscreenToGone() listenForLockscreenToOccluded() @@ -104,6 +102,7 @@ constructor( /* Starts transitions when manually dragging up the bouncer from the lockscreen. */ private fun listenForLockscreenToBouncerDragging() { + var transitionId: UUID? = null scope.launch { shadeRepository.shadeModel .sample( @@ -114,25 +113,43 @@ constructor( ), ::toTriple ) - .collect { triple -> - val (shadeModel, keyguardState, statusBarState) = triple - + .collect { (shadeModel, keyguardState, statusBarState) -> val id = transitionId if (id != null) { // An existing `id` means a transition is started, and calls to - // `updateTransition` will control it until FINISHED - keyguardTransitionRepository.updateTransition( - id, - 1f - shadeModel.expansionAmount, - if ( - shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f - ) { - transitionId = null + // `updateTransition` will control it until FINISHED or CANCELED + var nextState = + if (shadeModel.expansionAmount == 0f) { TransitionState.FINISHED + } else if (shadeModel.expansionAmount == 1f) { + TransitionState.CANCELED } else { TransitionState.RUNNING } + keyguardTransitionRepository.updateTransition( + id, + 1f - shadeModel.expansionAmount, + nextState, ) + + if ( + nextState == TransitionState.CANCELED || + nextState == TransitionState.FINISHED + ) { + transitionId = null + } + + // If canceled, just put the state back + if (nextState == TransitionState.CANCELED) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.BOUNCER, + to = KeyguardState.LOCKSCREEN, + animator = getAnimator(0.milliseconds) + ) + ) + } } else { // TODO (b/251849525): Remove statusbarstate check when that state is // integrated into KeyguardTransitionRepository diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 490d22eb0820..4cf56fe2c031 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -32,12 +32,15 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.CommandQueue.Callbacks -import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.merge /** @@ -89,15 +92,23 @@ constructor( /** * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means * that doze mode is not running and DREAMING is ok to commence. + * + * Allow a brief moment to prevent rapidly oscillating between true/false signals. */ val isAbleToDream: Flow<Boolean> = merge(isDreaming, isDreamingWithOverlay) - .sample( + .combine( dozeTransitionModel, { isDreaming, dozeTransitionModel -> isDreaming && isDozeOff(dozeTransitionModel.to) } ) + .flatMapLatest { isAbleToDream -> + flow { + delay(50) + emit(isAbleToDream) + } + } .distinctUntilChanged() /** Whether the keyguard is showing or not. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 9cdbcda1343d..ad6dbea7ae43 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -22,13 +22,17 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.AnimationParams import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import javax.inject.Inject +import kotlin.math.max +import kotlin.math.min import kotlin.time.Duration import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter @@ -53,9 +57,16 @@ constructor( val dreamingToLockscreenTransition: Flow<TransitionStep> = repository.transition(DREAMING, LOCKSCREEN) + /** GONE->DREAMING transition information. */ + val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING) + /** LOCKSCREEN->AOD transition information. */ val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD) + /** LOCKSCREEN->BOUNCER transition information. */ + val lockscreenToBouncerTransition: Flow<TransitionStep> = + repository.transition(LOCKSCREEN, BOUNCER) + /** LOCKSCREEN->DREAMING transition information. */ val lockscreenToDreamingTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, DREAMING) @@ -106,13 +117,23 @@ constructor( ): Flow<Float> { val start = (params.startTime / totalDuration).toFloat() val chunks = (totalDuration / params.duration).toFloat() + var isRunning = false return flow - // When starting, emit a value of 0f to give animations a chance to set initial state .map { step -> + val value = (step.value - start) * chunks if (step.transitionState == STARTED) { - 0f + // When starting, make sure to always emit. If a transition is started from the + // middle, it is possible this animation is being skipped but we need to inform + // the ViewModels of the last update + isRunning = true + max(0f, min(1f, value)) + } else if (isRunning && value >= 1f) { + // Always send a final value of 1. Because of rounding, [value] may never be + // exactly 1. + isRunning = false + 1f } else { - (step.value - start) * chunks + value } } .filter { value -> value >= 0f && value <= 1f } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 0e4058bf8f6d..9d8bf7deb03e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -45,7 +45,6 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewMod import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.VibratorHelper -import com.android.systemui.util.kotlin.pairwise import kotlin.math.pow import kotlin.math.sqrt import kotlin.time.Duration.Companion.milliseconds @@ -129,18 +128,6 @@ object KeyguardBottomAreaViewBinder { } launch { - viewModel.startButton - .map { it.isActivated } - .pairwise() - .collect { (prev, next) -> - when { - !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated) - prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated) - } - } - } - - launch { viewModel.endButton.collect { buttonModel -> updateButton( view = endButton, @@ -153,18 +140,6 @@ object KeyguardBottomAreaViewBinder { } launch { - viewModel.endButton - .map { it.isActivated } - .pairwise() - .collect { (prev, next) -> - when { - !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated) - prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated) - } - } - } - - launch { viewModel.isOverlayContainerVisible.collect { isVisible -> overlayContainer.visibility = if (isVisible) { @@ -383,6 +358,13 @@ object KeyguardBottomAreaViewBinder { .setDuration(longPressDurationMs) .withEndAction { view.setOnClickListener { + vibratorHelper?.vibrate( + if (viewModel.isActivated) { + Vibrations.Activated + } else { + Vibrations.Deactivated + } + ) viewModel.onClicked( KeyguardQuickAffordanceViewModel.OnClickedParameters( configKey = viewModel.configKey, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index e164f5d58b07..6627865ecc79 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -22,10 +22,14 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.AnimationParams +import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge /** * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to @@ -49,9 +53,15 @@ constructor( /** Lockscreen views y-translation */ fun lockscreenTranslationY(translatePx: Int): Flow<Float> { - return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> - -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx) - } + return merge( + flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> + -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx) + }, + // On end, reset the translation to 0 + interactor.dreamingToLockscreenTransition + .filter { it.transitionState == FINISHED || it.transitionState == CANCELED } + .map { 0f } + ) } /** Lockscreen views alpha */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt new file mode 100644 index 000000000000..5a4796096eeb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt @@ -0,0 +1,69 @@ +/* + * 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 com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.AnimationParams +import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** Breaks down GONE->DREAMING transition into discrete steps for corresponding views to consume. */ +@SysUISingleton +class GoneToDreamingTransitionViewModel +@Inject +constructor( + private val interactor: KeyguardTransitionInteractor, +) { + + /** Lockscreen views y-translation */ + fun lockscreenTranslationY(translatePx: Int): Flow<Float> { + return merge( + flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> + (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx) + }, + // On end, reset the translation to 0 + interactor.goneToDreamingTransition + .filter { it.transitionState == FINISHED || it.transitionState == CANCELED } + .map { 0f } + ) + } + + /** Lockscreen views alpha */ + val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it } + + private fun flowForAnimation(params: AnimationParams): Flow<Float> { + return interactor.transitionStepAnimation( + interactor.goneToDreamingTransition, + params, + totalDuration = TO_DREAMING_DURATION + ) + } + + companion object { + val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds) + val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt index d48f87deaaf4..e05adbdab583 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt @@ -21,7 +21,8 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.AnimationParams -import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -48,7 +49,7 @@ constructor( }, // On end, reset the translation to 0 interactor.lockscreenToDreamingTransition - .filter { step -> step.transitionState == TransitionState.FINISHED } + .filter { it.transitionState == FINISHED || it.transitionState == CANCELED } .map { 0f } ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt index f006442906e7..be18cbec7163 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt @@ -88,7 +88,10 @@ data class MediaData( val instanceId: InstanceId, /** The UID of the app, used for logging */ - val appUid: Int + val appUid: Int, + + /** Whether explicit indicator exists */ + val isExplicit: Boolean = false, ) { companion object { /** Media is playing on the local device */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt index a8f39fa9a456..1c8bfd1fc468 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt @@ -24,6 +24,7 @@ import android.widget.ImageView import android.widget.SeekBar import android.widget.TextView import androidx.constraintlayout.widget.Barrier +import com.android.internal.widget.CachingIconView import com.android.systemui.R import com.android.systemui.media.controls.models.GutsViewHolder import com.android.systemui.surfaceeffects.ripple.MultiRippleView @@ -44,6 +45,7 @@ class MediaViewHolder constructor(itemView: View) { val appIcon = itemView.requireViewById<ImageView>(R.id.icon) val titleText = itemView.requireViewById<TextView>(R.id.header_title) val artistText = itemView.requireViewById<TextView>(R.id.header_artist) + val explicitIndicator = itemView.requireViewById<CachingIconView>(R.id.media_explicit_indicator) // Output switcher val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless) @@ -123,6 +125,7 @@ class MediaViewHolder constructor(itemView: View) { R.id.app_name, R.id.header_title, R.id.header_artist, + R.id.media_explicit_indicator, R.id.media_seamless, R.id.media_progress_bar, R.id.actionPlayPause, 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 2dd339d409a6..415ebeedfbde 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 @@ -45,6 +45,7 @@ import android.os.Process import android.os.UserHandle import android.provider.Settings import android.service.notification.StatusBarNotification +import android.support.v4.media.MediaMetadataCompat import android.text.TextUtils import android.util.Log import androidx.media.utils.MediaConstants @@ -660,6 +661,10 @@ class MediaDataManager( val currentEntry = mediaEntries.get(packageName) val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId() val appUid = currentEntry?.appUid ?: Process.INVALID_UID + val isExplicit = + desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) == + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT && + mediaFlags.isExplicitIndicatorEnabled() val mediaAction = getResumeMediaAction(resumeAction) val lastActive = systemClock.elapsedRealtime() @@ -689,7 +694,8 @@ class MediaDataManager( hasCheckedForResume = true, lastActive = lastActive, instanceId = instanceId, - appUid = appUid + appUid = appUid, + isExplicit = isExplicit, ) ) } @@ -750,6 +756,15 @@ class MediaDataManager( song = HybridGroupManager.resolveTitle(notif) } + // Explicit Indicator + var isExplicit = false + if (mediaFlags.isExplicitIndicatorEnabled()) { + val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata) + isExplicit = + mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) == + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + } + // Artist name var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST) if (artist == null) { @@ -851,7 +866,8 @@ class MediaDataManager( isClearable = sbn.isClearable(), lastActive = lastActive, instanceId = instanceId, - appUid = appUid + appUid = appUid, + isExplicit = isExplicit, ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index 899148b0014c..8f1c9048026f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -130,7 +130,12 @@ constructor( private var splitShadeContainer: ViewGroup? = null /** Track the media player setting status on lock screen. */ - private var allowMediaPlayerOnLockScreen: Boolean = true + private var allowMediaPlayerOnLockScreen: Boolean = + secureSettings.getBoolForUser( + Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, + true, + UserHandle.USER_CURRENT + ) private val lockScreenMediaPlayerUri = secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index d5558b27ef1a..e7f7647797cd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt @@ -94,7 +94,7 @@ constructor( private var currentCarouselWidth: Int = 0 /** The current height of the carousel */ - private var currentCarouselHeight: Int = 0 + @VisibleForTesting var currentCarouselHeight: Int = 0 /** Are we currently showing only active players */ private var currentlyShowingOnlyActive: Boolean = false @@ -128,14 +128,14 @@ constructor( /** The measured height of the carousel */ private var carouselMeasureHeight: Int = 0 private var desiredHostState: MediaHostState? = null - private val mediaCarousel: MediaScrollView + @VisibleForTesting var mediaCarousel: MediaScrollView val mediaCarouselScrollHandler: MediaCarouselScrollHandler val mediaFrame: ViewGroup @VisibleForTesting lateinit var settingsButton: View private set private val mediaContent: ViewGroup - @VisibleForTesting val pageIndicator: PageIndicator + @VisibleForTesting var pageIndicator: PageIndicator private val visualStabilityCallback: OnReorderingAllowedListener private var needsReordering: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() @@ -160,25 +160,20 @@ constructor( } companion object { - const val ANIMATION_BASE_DURATION = 2200f - const val DURATION = 167f - const val DETAILS_DELAY = 1067f - const val CONTROLS_DELAY = 1400f - const val PAGINATION_DELAY = 1900f - const val MEDIATITLES_DELAY = 1000f - const val MEDIACONTAINERS_DELAY = 967f val TRANSFORM_BEZIER = PathInterpolator(0.68F, 0F, 0F, 1F) - val REVERSE_BEZIER = PathInterpolator(0F, 0.68F, 1F, 0F) - - fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float { - val transformStartFraction = delay / ANIMATION_BASE_DURATION - val transformDurationFraction = duration / ANIMATION_BASE_DURATION - val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction) - return MathUtils.constrain( - (squishinessToTime - transformStartFraction) / transformDurationFraction, - 0F, - 1F - ) + + fun calculateAlpha( + squishinessFraction: Float, + startPosition: Float, + endPosition: Float + ): Float { + val transformFraction = + MathUtils.constrain( + (squishinessFraction - startPosition) / (endPosition - startPosition), + 0F, + 1F + ) + return TRANSFORM_BEZIER.getInterpolation(transformFraction) } } @@ -813,7 +808,12 @@ constructor( val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F val endAlpha = (if (endIsVisible) 1.0f else 0.0f) * - calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION) + calculateAlpha( + squishFraction, + (pageIndicator.translationY + pageIndicator.height) / + mediaCarousel.measuredHeight, + 1F + ) var alpha = 1.0f if (!endIsVisible || !startIsVisible) { var progress = currentTransitionProgress @@ -839,7 +839,8 @@ constructor( pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams pageIndicator.translationY = - (currentCarouselHeight - pageIndicator.height - layoutParams.bottomMargin).toFloat() + (mediaCarousel.measuredHeight - pageIndicator.height - layoutParams.bottomMargin) + .toFloat() } /** Update the dimension of this carousel. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index 15c34430f455..f58090b2d433 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -50,7 +50,6 @@ import android.os.Process; import android.os.Trace; import android.text.TextUtils; import android.util.Log; -import android.util.Pair; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -68,6 +67,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.InstanceId; +import com.android.internal.widget.CachingIconView; import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.ActivityIntentHelper; import com.android.systemui.R; @@ -113,6 +113,8 @@ import com.android.systemui.util.ColorUtilKt; import com.android.systemui.util.animation.TransitionLayout; import com.android.systemui.util.time.SystemClock; +import dagger.Lazy; + import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; @@ -120,7 +122,7 @@ import java.util.concurrent.Executor; import javax.inject.Inject; -import dagger.Lazy; +import kotlin.Triple; import kotlin.Unit; /** @@ -398,10 +400,11 @@ public class MediaControlPanel { TextView titleText = mMediaViewHolder.getTitleText(); TextView artistText = mMediaViewHolder.getArtistText(); + CachingIconView explicitIndicator = mMediaViewHolder.getExplicitIndicator(); AnimatorSet enter = loadAnimator(R.anim.media_metadata_enter, - Interpolators.EMPHASIZED_DECELERATE, titleText, artistText); + Interpolators.EMPHASIZED_DECELERATE, titleText, artistText, explicitIndicator); AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit, - Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText); + Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText, explicitIndicator); MultiRippleView multiRippleView = vh.getMultiRippleView(); mMultiRippleController = new MultiRippleController(multiRippleView); @@ -664,11 +667,15 @@ public class MediaControlPanel { private boolean bindSongMetadata(MediaData data) { TextView titleText = mMediaViewHolder.getTitleText(); TextView artistText = mMediaViewHolder.getArtistText(); + ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); + ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); return mMetadataAnimationHandler.setNext( - Pair.create(data.getSong(), data.getArtist()), + new Triple(data.getSong(), data.getArtist(), data.isExplicit()), () -> { titleText.setText(data.getSong()); artistText.setText(data.getArtist()); + setVisibleAndAlpha(expandedSet, R.id.media_explicit_indicator, data.isExplicit()); + setVisibleAndAlpha(collapsedSet, R.id.media_explicit_indicator, data.isExplicit()); // refreshState is required here to resize the text views (and prevent ellipsis) mMediaViewController.refreshState(); diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index 322421318cb8..2ec7be6eaa32 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -24,11 +24,6 @@ import com.android.systemui.R import com.android.systemui.media.controls.models.GutsViewHolder import com.android.systemui.media.controls.models.player.MediaViewHolder import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.CONTROLS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.MeasurementOutput @@ -36,6 +31,8 @@ import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.animation.TransitionLayoutController import com.android.systemui.util.animation.TransitionViewState import com.android.systemui.util.traceSection +import java.lang.Float.max +import java.lang.Float.min import javax.inject.Inject /** @@ -80,6 +77,7 @@ constructor( setOf( R.id.header_title, R.id.header_artist, + R.id.media_explicit_indicator, R.id.actionPlayPause, ) @@ -304,39 +302,106 @@ constructor( val squishedViewState = viewState.copy() val squishedHeight = (squishedViewState.measureHeight * squishFraction).toInt() squishedViewState.height = squishedHeight - controlIds.forEach { id -> - squishedViewState.widgetStates.get(id)?.let { state -> - state.alpha = calculateAlpha(squishFraction, CONTROLS_DELAY, DURATION) - } - } - - detailIds.forEach { id -> - squishedViewState.widgetStates.get(id)?.let { state -> - state.alpha = calculateAlpha(squishFraction, DETAILS_DELAY, DURATION) - } - } - // We are not overriding the squishedViewStates height but only the children to avoid // them remeasuring the whole view. Instead it just remains as the original size backgroundIds.forEach { id -> - squishedViewState.widgetStates.get(id)?.let { state -> - state.height = squishedHeight - } + squishedViewState.widgetStates.get(id)?.let { state -> state.height = squishedHeight } } - RecommendationViewHolder.mediaContainersIds.forEach { id -> + // media player + val controlsTop = + calculateWidgetGroupAlphaForSquishiness( + controlIds, + squishedViewState.measureHeight.toFloat(), + squishedViewState, + squishFraction + ) + calculateWidgetGroupAlphaForSquishiness( + detailIds, + controlsTop, + squishedViewState, + squishFraction + ) + // recommendation card + val titlesTop = + calculateWidgetGroupAlphaForSquishiness( + RecommendationViewHolder.mediaTitlesAndSubtitlesIds, + squishedViewState.measureHeight.toFloat(), + squishedViewState, + squishFraction + ) + calculateWidgetGroupAlphaForSquishiness( + RecommendationViewHolder.mediaContainersIds, + titlesTop, + squishedViewState, + squishFraction + ) + return squishedViewState + } + + /** + * This function is to make each widget in UMO disappear before being clipped by squished UMO + * + * The general rule is that widgets in UMO has been divided into several groups, and widgets in + * one group have the same alpha during squishing It will change from alpha 0.0 when the visible + * bottom of UMO reach the bottom of this group It will change to alpha 1.0 when the visible + * bottom of UMO reach the top of the group below e.g.Album title, artist title and play-pause + * button will change alpha together. + * ``` + * And their alpha becomes 1.0 when the visible bottom of UMO reach the top of controls, + * including progress bar, next button, previous button + * ``` + * widgetGroupIds: a group of widgets have same state during UMO is squished, + * ``` + * e.g. Album title, artist title and play-pause button + * ``` + * groupEndPosition: the height of UMO, when the height reaches this value, + * ``` + * widgets in this group should have 1.0 as alpha + * e.g., the group of album title, artist title and play-pause button will become fully + * visible when the height of UMO reaches the top of controls group + * (progress bar, previous button and next button) + * ``` + * squishedViewState: hold the widgetState of each widget, which will be modified + * squishFraction: the squishFraction of UMO + */ + private fun calculateWidgetGroupAlphaForSquishiness( + widgetGroupIds: Set<Int>, + groupEndPosition: Float, + squishedViewState: TransitionViewState, + squishFraction: Float + ): Float { + val nonsquishedHeight = squishedViewState.measureHeight + var groupTop = squishedViewState.measureHeight.toFloat() + var groupBottom = 0F + widgetGroupIds.forEach { id -> squishedViewState.widgetStates.get(id)?.let { state -> - state.alpha = calculateAlpha(squishFraction, MEDIACONTAINERS_DELAY, DURATION) + groupTop = min(groupTop, state.y) + groupBottom = max(groupBottom, state.y + state.height) } } - - RecommendationViewHolder.mediaTitlesAndSubtitlesIds.forEach { id -> + // startPosition means to the height of squished UMO where the widget alpha should start + // changing from 0.0 + // generally, it equals to the bottom of widgets, so that we can meet the requirement that + // widget should not go beyond the bounds of background + // endPosition means to the height of squished UMO where the widget alpha should finish + // changing alpha to 1.0 + var startPosition = groupBottom + val endPosition = groupEndPosition + if (startPosition == endPosition) { + startPosition = (endPosition - 0.2 * (groupBottom - groupTop)).toFloat() + } + widgetGroupIds.forEach { id -> squishedViewState.widgetStates.get(id)?.let { state -> - state.alpha = calculateAlpha(squishFraction, MEDIATITLES_DELAY, DURATION) + state.alpha = + calculateAlpha( + squishFraction, + startPosition / nonsquishedHeight, + endPosition / nonsquishedHeight + ) } } - - return squishedViewState + return groupTop // used for the widget group above this group } /** @@ -544,11 +609,13 @@ constructor( overrideSize?.let { // To be safe we're using a maximum here. The override size should always be set // properly though. - if (result.measureHeight != it.measuredHeight - || result.measureWidth != it.measuredWidth) { + if ( + result.measureHeight != it.measuredHeight || result.measureWidth != it.measuredWidth + ) { result.measureHeight = Math.max(it.measuredHeight, result.measureHeight) result.measureWidth = Math.max(it.measuredWidth, result.measureWidth) - // The measureHeight and the shown height should both be set to the overridden height + // The measureHeight and the shown height should both be set to the overridden + // height result.height = result.measureHeight result.width = result.measureWidth // Make sure all background views are also resized such that their size is correct 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 8d4931a5d08c..5bc35caed515 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 @@ -42,4 +42,7 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { * [android.app.StatusBarManager.registerNearbyMediaDevicesProvider] for more information. */ fun areNearbyMediaDevicesEnabled() = featureFlags.isEnabled(Flags.MEDIA_NEARBY_DEVICES) + + /** Check whether we show explicit indicator on UMO */ + fun isExplicitIndicatorEnabled() = featureFlags.isEnabled(Flags.MEDIA_EXPLICIT_INDICATOR) } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 5b306c98912d..2647600e8684 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -16,6 +16,7 @@ package com.android.systemui.navigationbar; +import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; @@ -44,6 +45,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; @@ -136,9 +138,10 @@ import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.rotation.RotationButton; import com.android.systemui.shared.rotation.RotationButtonController; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.shared.system.TaskStackChangeListener; +import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; @@ -252,6 +255,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final AutoHideController.Factory mAutoHideControllerFactory; private final Optional<TelecomManager> mTelecomManagerOptional; private final InputMethodManager mInputMethodManager; + private final TaskStackChangeListeners mTaskStackChangeListeners; @VisibleForTesting public int mDisplayId; @@ -488,6 +492,18 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } }; + private boolean mScreenPinningActive = false; + private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { + @Override + public void onLockTaskModeChanged(int mode) { + mScreenPinningActive = (mode == LOCK_TASK_MODE_PINNED); + mSysUiFlagsContainer.setFlag(SYSUI_STATE_SCREEN_PINNING, mScreenPinningActive) + .commitUpdate(mDisplayId); + mView.setInScreenPinning(mScreenPinningActive); + updateScreenPinningGestures(); + } + }; + @Inject NavigationBar( NavigationBarView navigationBarView, @@ -529,7 +545,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements EdgeBackGestureHandler edgeBackGestureHandler, Optional<BackAnimation> backAnimation, UserContextProvider userContextProvider, - WakefulnessLifecycle wakefulnessLifecycle) { + WakefulnessLifecycle wakefulnessLifecycle, + TaskStackChangeListeners taskStackChangeListeners) { super(navigationBarView); mFrame = navigationBarFrame; mContext = context; @@ -568,6 +585,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mInputMethodManager = inputMethodManager; mUserContextProvider = userContextProvider; mWakefulnessLifecycle = wakefulnessLifecycle; + mTaskStackChangeListeners = taskStackChangeListeners; mNavColorSampleMargin = getResources() .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin); @@ -676,6 +694,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mCommandQueue.recomputeDisableFlags(mDisplayId, false); mNotificationShadeDepthController.addListener(mDepthListener); + mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener); } public void destroyView() { @@ -689,6 +708,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mNotificationShadeDepthController.removeListener(mDepthListener); mDeviceConfigProxy.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); + mTaskStackChangeListeners.unregisterTaskStackListener(mTaskStackListener); } @Override @@ -990,6 +1010,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements pw.println(" mTransientShown=" + mTransientShown); pw.println(" mTransientShownFromGestureOnSystemBar=" + mTransientShownFromGestureOnSystemBar); + pw.println(" mScreenPinningActive=" + mScreenPinningActive); dumpBarTransitions(pw, "mNavigationBarView", getBarTransitions()); pw.println(" mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion); @@ -1213,10 +1234,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private void updateScreenPinningGestures() { // Change the cancel pin gesture to home and back if recents button is invisible - boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive(); ButtonDispatcher backButton = mView.getBackButton(); ButtonDispatcher recentsButton = mView.getRecentsButton(); - if (pinningActive) { + if (mScreenPinningActive) { boolean recentsVisible = mView.isRecentsButtonVisible(); backButton.setOnLongClickListener(recentsVisible ? this::onLongPressBackRecents @@ -1227,8 +1247,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements recentsButton.setOnLongClickListener(null); } // Note, this needs to be set after even if we're setting the listener to null - backButton.setLongClickable(pinningActive); - recentsButton.setLongClickable(pinningActive); + backButton.setLongClickable(mScreenPinningActive); + recentsButton.setLongClickable(mScreenPinningActive); } private void notifyNavigationBarScreenOn() { @@ -1311,8 +1331,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements @VisibleForTesting boolean onHomeLongClick(View v) { - if (!mView.isRecentsButtonVisible() - && ActivityManagerWrapper.getInstance().isScreenPinningActive()) { + if (!mView.isRecentsButtonVisible() && mScreenPinningActive) { return onLongPressBackHome(v); } if (shouldDisableNavbarGestures()) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 891455249867..5d43c5dc19fa 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -57,6 +57,7 @@ import com.android.systemui.flags.Flags; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.phone.AutoHideController; @@ -88,7 +89,6 @@ public class NavigationBarController implements private FeatureFlags mFeatureFlags; private final DisplayManager mDisplayManager; private final TaskbarDelegate mTaskbarDelegate; - private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private int mNavMode; @VisibleForTesting boolean mIsTablet; @@ -112,10 +112,10 @@ public class NavigationBarController implements NavBarHelper navBarHelper, TaskbarDelegate taskbarDelegate, NavigationBarComponent.Factory navigationBarComponentFactory, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, DumpManager dumpManager, AutoHideController autoHideController, LightBarController lightBarController, + TaskStackChangeListeners taskStackChangeListeners, Optional<Pip> pipOptional, Optional<BackAnimation> backAnimation, FeatureFlags featureFlags) { @@ -129,11 +129,10 @@ public class NavigationBarController implements mConfigChanges.applyNewConfig(mContext.getResources()); mNavMode = navigationModeController.addListener(this); mTaskbarDelegate = taskbarDelegate; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService, navBarHelper, navigationModeController, sysUiFlagsContainer, dumpManager, autoHideController, lightBarController, pipOptional, - backAnimation.orElse(null)); + backAnimation.orElse(null), taskStackChangeListeners); mIsTablet = isTablet(mContext); dumpManager.registerDumpable(this); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 403d276f8cbc..88c4fd524b79 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -16,6 +16,7 @@ package com.android.systemui.navigationbar; +import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; @@ -80,6 +81,7 @@ import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdates import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarTransitionsController; @@ -160,6 +162,7 @@ public class NavigationBarView extends FrameLayout { * fully locked mode we only show that unlocking is blocked. */ private ScreenPinningNotify mScreenPinningNotify; + private boolean mScreenPinningActive = false; /** * {@code true} if the IME can render the back button and the IME switcher button. @@ -636,14 +639,13 @@ public class NavigationBarView extends FrameLayout { // When screen pinning, don't hide back and home when connected service or back and // recents buttons when disconnected from launcher service in screen pinning mode, // as they are used for exiting. - final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive(); if (mOverviewProxyEnabled) { // Force disable recents when not in legacy mode disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode); - if (pinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) { + if (mScreenPinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) { disableBack = disableHome = false; } - } else if (pinningActive) { + } else if (mScreenPinningActive) { disableBack = disableRecent = false; } @@ -738,9 +740,7 @@ public class NavigationBarView extends FrameLayout { public void updateDisabledSystemUiStateFlags(SysUiState sysUiState) { int displayId = mContext.getDisplayId(); - sysUiState.setFlag(SYSUI_STATE_SCREEN_PINNING, - ActivityManagerWrapper.getInstance().isScreenPinningActive()) - .setFlag(SYSUI_STATE_OVERVIEW_DISABLED, + sysUiState.setFlag(SYSUI_STATE_OVERVIEW_DISABLED, (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0) .setFlag(SYSUI_STATE_HOME_DISABLED, (mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0) @@ -749,6 +749,10 @@ public class NavigationBarView extends FrameLayout { .commitUpdate(displayId); } + public void setInScreenPinning(boolean active) { + mScreenPinningActive = active; + } + private void updatePanelSystemUiStateFlags() { if (SysUiState.DEBUG) { Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 5e26e6050eaa..6ee86aa021a3 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -16,6 +16,7 @@ package com.android.systemui.navigationbar; +import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; @@ -40,6 +41,7 @@ import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode import android.app.StatusBarManager; import android.app.StatusBarManager.WindowVisibleState; +import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; @@ -68,6 +70,8 @@ import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.TaskStackChangeListener; +import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; @@ -101,6 +105,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private AutoHideController mAutoHideController; private LightBarController mLightBarController; private LightBarTransitionsController mLightBarTransitionsController; + private TaskStackChangeListeners mTaskStackChangeListeners; private Optional<Pip> mPipOptional; private int mDisplayId; private int mNavigationIconHints; @@ -127,6 +132,14 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private final DisplayManager mDisplayManager; private Context mWindowContext; private ScreenPinningNotify mScreenPinningNotify; + private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { + @Override + public void onLockTaskModeChanged(int mode) { + mSysUiState.setFlag(SYSUI_STATE_SCREEN_PINNING, mode == LOCK_TASK_MODE_PINNED) + .commitUpdate(mDisplayId); + } + }; + private int mNavigationMode = -1; private final Consumer<Rect> mPipListener; @@ -176,7 +189,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, AutoHideController autoHideController, LightBarController lightBarController, Optional<Pip> pipOptional, - BackAnimation backAnimation) { + BackAnimation backAnimation, + TaskStackChangeListeners taskStackChangeListeners) { // TODO: adding this in the ctor results in a dagger dependency cycle :( mCommandQueue = commandQueue; mOverviewProxyService = overviewProxyService; @@ -189,6 +203,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mPipOptional = pipOptional; mBackAnimation = backAnimation; mLightBarTransitionsController = createLightBarTransitionsController(); + mTaskStackChangeListeners = taskStackChangeListeners; } // Separated into a method to keep setDependencies() clean/readable. @@ -234,6 +249,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener); mEdgeBackGestureHandler.setBackAnimation(mBackAnimation); mEdgeBackGestureHandler.onConfigurationChanged(mContext.getResources().getConfiguration()); + mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener); mInitialized = true; } @@ -253,6 +269,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mLightBarTransitionsController.destroy(); mLightBarController.setNavigationBar(null); mPipOptional.ifPresent(this::removePipExclusionBoundsChangeListener); + mTaskStackChangeListeners.unregisterTaskStackListener(mTaskStackListener); mInitialized = false; } @@ -300,8 +317,6 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isWindowVisible()) .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, allowSystemGestureIgnoringBarVisibility()) - .setFlag(SYSUI_STATE_SCREEN_PINNING, - ActivityManagerWrapper.getInstance().isScreenPinningActive()) .setFlag(SYSUI_STATE_IMMERSIVE_MODE, isImmersiveMode()) .commitUpdate(mDisplayId); } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 8356440714e6..08d18575da79 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -104,4 +104,9 @@ constructor( PackageManager.DONT_KILL_APP, ) } + + companion object { + // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead. + const val NOTE_TASK_KEY_EVENT = 311 + } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index d14b7a766762..d5f4a5a5d351 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -16,7 +16,6 @@ package com.android.systemui.notetask -import android.view.KeyEvent import androidx.annotation.VisibleForTesting import com.android.systemui.statusbar.CommandQueue import com.android.wm.shell.bubbles.Bubbles @@ -37,7 +36,7 @@ constructor( val callbacks = object : CommandQueue.Callbacks { override fun handleSystemKey(keyCode: Int) { - if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) { + if (keyCode == NoteTaskController.NOTE_TASK_KEY_EVENT) { noteTaskController.showNoteTask() } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt index 98d69910aac3..26e3f49828c7 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt @@ -21,12 +21,12 @@ import android.content.Intent import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.ResolveInfoFlags -import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION +import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE import javax.inject.Inject /** - * Class responsible to query all apps and find one that can handle the [NOTES_ACTION]. If found, an - * [Intent] ready for be launched will be returned. Otherwise, returns null. + * Class responsible to query all apps and find one that can handle the [ACTION_CREATE_NOTE]. If + * found, an [Intent] ready for be launched will be returned. Otherwise, returns null. * * TODO(b/248274123): should be revisited once the notes role is implemented. */ @@ -37,15 +37,16 @@ constructor( ) { fun resolveIntent(): Intent? { - val intent = Intent(NOTES_ACTION) + val intent = Intent(ACTION_CREATE_NOTE) val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()) val infoList = packageManager.queryIntentActivities(intent, flags) for (info in infoList) { - val packageName = info.serviceInfo.applicationInfo.packageName ?: continue + val packageName = info.activityInfo.applicationInfo.packageName ?: continue val activityName = resolveActivityNameForNotesAction(packageName) ?: continue - return Intent(NOTES_ACTION) + return Intent(ACTION_CREATE_NOTE) + .setPackage(packageName) .setComponent(ComponentName(packageName, activityName)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } @@ -54,7 +55,7 @@ constructor( } private fun resolveActivityNameForNotesAction(packageName: String): String? { - val intent = Intent(NOTES_ACTION).setPackage(packageName) + val intent = Intent(ACTION_CREATE_NOTE).setPackage(packageName) val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()) val resolveInfo = packageManager.resolveActivity(intent, flags) @@ -69,8 +70,8 @@ constructor( } companion object { - // TODO(b/254606432): Use Intent.ACTION_NOTES and Intent.ACTION_NOTES_LOCKED instead. - const val NOTES_ACTION = "android.intent.action.NOTES" + // TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead. + const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE" } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt index 47fe67638cd0..f203e7a51643 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt @@ -45,8 +45,8 @@ constructor( fun newIntent(context: Context): Intent { return Intent(context, LaunchNoteTaskActivity::class.java).apply { // Intent's action must be set in shortcuts, or an exception will be thrown. - // TODO(b/254606432): Use Intent.ACTION_NOTES instead. - action = NoteTaskIntentResolver.NOTES_ACTION + // TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead. + action = NoteTaskIntentResolver.ACTION_CREATE_NOTE } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index 7cf63f678c1d..1da30ade951b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java @@ -36,7 +36,6 @@ public interface QSHost { void removeCallback(Callback callback); void removeTile(String tileSpec); void removeTiles(Collection<String> specs); - void unmarkTileAsAutoAdded(String tileSpec); int indexOf(String tileSpec); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index cad296b671b3..100853caa2d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -427,11 +427,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs))); } - @Override - public void unmarkTileAsAutoAdded(String spec) { - if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec); - } - /** * Add a tile to the end * diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt index 30f81243e8d0..19215867e678 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt @@ -219,9 +219,9 @@ object FooterActionsViewBinder { // Small button with the number only. foregroundServicesWithTextView.isVisible = false - foregroundServicesWithNumberView.visibility = View.VISIBLE + foregroundServicesWithNumberView.isVisible = true foregroundServicesWithNumberView.setOnClickListener { - foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView)) + foregroundServices.onClick(Expandable.fromView(foregroundServicesWithNumberView)) } foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString() foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index 9f376ae75efe..d32ef327e90e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -49,109 +49,135 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : } fun logTileAdded(tileSpec: String) { - log(DEBUG, { - str1 = tileSpec - }, { - "[$str1] Tile added" - }) + buffer.log(TAG, DEBUG, { str1 = tileSpec }, { "[$str1] Tile added" }) } fun logTileDestroyed(tileSpec: String, reason: String) { - log(DEBUG, { - str1 = tileSpec - str2 = reason - }, { - "[$str1] Tile destroyed. Reason: $str2" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + str2 = reason + }, + { "[$str1] Tile destroyed. Reason: $str2" } + ) } fun logTileChangeListening(tileSpec: String, listening: Boolean) { - log(VERBOSE, { - bool1 = listening - str1 = tileSpec - }, { - "[$str1] Tile listening=$bool1" - }) + buffer.log( + TAG, + VERBOSE, + { + bool1 = listening + str1 = tileSpec + }, + { "[$str1] Tile listening=$bool1" } + ) } fun logAllTilesChangeListening(listening: Boolean, containerName: String, allSpecs: String) { - log(DEBUG, { - bool1 = listening - str1 = containerName - str2 = allSpecs - }, { - "Tiles listening=$bool1 in $str1. $str2" - }) + buffer.log( + TAG, + DEBUG, + { + bool1 = listening + str1 = containerName + str2 = allSpecs + }, + { "Tiles listening=$bool1 in $str1. $str2" } + ) } fun logTileClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - str2 = StatusBarState.toString(statusBarState) - str3 = toStateString(state) - }, { - "[$str1][$int1] Tile clicked. StatusBarState=$str2. TileState=$str3" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + str2 = StatusBarState.toString(statusBarState) + str3 = toStateString(state) + }, + { "[$str1][$int1] Tile clicked. StatusBarState=$str2. TileState=$str3" } + ) } fun logHandleClick(tileSpec: String, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - }, { - "[$str1][$int1] Tile handling click." - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + }, + { "[$str1][$int1] Tile handling click." } + ) } fun logTileSecondaryClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - str2 = StatusBarState.toString(statusBarState) - str3 = toStateString(state) - }, { - "[$str1][$int1] Tile secondary clicked. StatusBarState=$str2. TileState=$str3" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + str2 = StatusBarState.toString(statusBarState) + str3 = toStateString(state) + }, + { "[$str1][$int1] Tile secondary clicked. StatusBarState=$str2. TileState=$str3" } + ) } fun logHandleSecondaryClick(tileSpec: String, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - }, { - "[$str1][$int1] Tile handling secondary click." - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + }, + { "[$str1][$int1] Tile handling secondary click." } + ) } fun logTileLongClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - str2 = StatusBarState.toString(statusBarState) - str3 = toStateString(state) - }, { - "[$str1][$int1] Tile long clicked. StatusBarState=$str2. TileState=$str3" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + str2 = StatusBarState.toString(statusBarState) + str3 = toStateString(state) + }, + { "[$str1][$int1] Tile long clicked. StatusBarState=$str2. TileState=$str3" } + ) } fun logHandleLongClick(tileSpec: String, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - }, { - "[$str1][$int1] Tile handling long click." - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + }, + { "[$str1][$int1] Tile handling long click." } + ) } fun logInternetTileUpdate(tileSpec: String, lastType: Int, callback: String) { - log(VERBOSE, { - str1 = tileSpec - int1 = lastType - str2 = callback - }, { - "[$str1] mLastTileState=$int1, Callback=$str2." - }) + buffer.log( + TAG, + VERBOSE, + { + str1 = tileSpec + int1 = lastType + str2 = callback + }, + { "[$str1] mLastTileState=$int1, Callback=$str2." } + ) } // TODO(b/250618218): Remove this method once we know the root cause of b/250618218. @@ -167,58 +193,75 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : if (tileSpec != "internet") { return } - log(VERBOSE, { - str1 = tileSpec - int1 = state - bool1 = disabledByPolicy - int2 = color - }, { - "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2." - }) + buffer.log( + TAG, + VERBOSE, + { + str1 = tileSpec + int1 = state + bool1 = disabledByPolicy + int2 = color + }, + { "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2." } + ) } fun logTileUpdated(tileSpec: String, state: QSTile.State) { - log(VERBOSE, { - str1 = tileSpec - str2 = state.label?.toString() - str3 = state.icon?.toString() - int1 = state.state - if (state is QSTile.SignalState) { - bool1 = true - bool2 = state.activityIn - bool3 = state.activityOut + buffer.log( + TAG, + VERBOSE, + { + str1 = tileSpec + str2 = state.label?.toString() + str3 = state.icon?.toString() + int1 = state.state + if (state is QSTile.SignalState) { + bool1 = true + bool2 = state.activityIn + bool3 = state.activityOut + } + }, + { + "[$str1] Tile updated. Label=$str2. State=$int1. Icon=$str3." + + if (bool1) " Activity in/out=$bool2/$bool3" else "" } - }, { - "[$str1] Tile updated. Label=$str2. State=$int1. Icon=$str3." + - if (bool1) " Activity in/out=$bool2/$bool3" else "" - }) + ) } fun logPanelExpanded(expanded: Boolean, containerName: String) { - log(DEBUG, { - str1 = containerName - bool1 = expanded - }, { - "$str1 expanded=$bool1" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = containerName + bool1 = expanded + }, + { "$str1 expanded=$bool1" } + ) } fun logOnViewAttached(orientation: Int, containerName: String) { - log(DEBUG, { - str1 = containerName - int1 = orientation - }, { - "onViewAttached: $str1 orientation $int1" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = containerName + int1 = orientation + }, + { "onViewAttached: $str1 orientation $int1" } + ) } fun logOnViewDetached(orientation: Int, containerName: String) { - log(DEBUG, { - str1 = containerName - int1 = orientation - }, { - "onViewDetached: $str1 orientation $int1" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = containerName + int1 = orientation + }, + { "onViewDetached: $str1 orientation $int1" } + ) } fun logOnConfigurationChanged( @@ -226,13 +269,16 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : newOrientation: Int, containerName: String ) { - log(DEBUG, { - str1 = containerName - int1 = lastOrientation - int2 = newOrientation - }, { - "configuration change: $str1 orientation was $int1, now $int2" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = containerName + int1 = lastOrientation + int2 = newOrientation + }, + { "configuration change: $str1 orientation was $int1, now $int2" } + ) } fun logSwitchTileLayout( @@ -241,32 +287,41 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : force: Boolean, containerName: String ) { - log(DEBUG, { - str1 = containerName - bool1 = after - bool2 = before - bool3 = force - }, { - "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = containerName + bool1 = after + bool2 = before + bool3 = force + }, + { "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3" } + ) } fun logTileDistributionInProgress(tilesPerPageCount: Int, totalTilesCount: Int) { - log(DEBUG, { - int1 = tilesPerPageCount - int2 = totalTilesCount - }, { - "Distributing tiles: [tilesPerPageCount=$int1] [totalTilesCount=$int2]" - }) + buffer.log( + TAG, + DEBUG, + { + int1 = tilesPerPageCount + int2 = totalTilesCount + }, + { "Distributing tiles: [tilesPerPageCount=$int1] [totalTilesCount=$int2]" } + ) } fun logTileDistributed(tileName: String, pageIndex: Int) { - log(DEBUG, { - str1 = tileName - int1 = pageIndex - }, { - "Adding $str1 to page number $int1" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileName + int1 = pageIndex + }, + { "Adding $str1 to page number $int1" } + ) } private fun toStateString(state: Int): String { @@ -277,12 +332,4 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : else -> "wrong state" } } - - private inline fun log( - logLevel: LogLevel, - initializer: LogMessage.() -> Unit, - noinline printer: LogMessage.() -> String - ) { - buffer.log(TAG, logLevel, initializer, printer) - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index a92c7e3c8554..24a4f60b7c00 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -33,7 +33,6 @@ import com.android.systemui.qs.tiles.BatterySaverTile; import com.android.systemui.qs.tiles.BluetoothTile; import com.android.systemui.qs.tiles.CameraToggleTile; import com.android.systemui.qs.tiles.CastTile; -import com.android.systemui.qs.tiles.CellularTile; import com.android.systemui.qs.tiles.ColorCorrectionTile; import com.android.systemui.qs.tiles.ColorInversionTile; import com.android.systemui.qs.tiles.DataSaverTile; @@ -54,7 +53,6 @@ import com.android.systemui.qs.tiles.ReduceBrightColorsTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.qs.tiles.ScreenRecordTile; import com.android.systemui.qs.tiles.UiModeNightTile; -import com.android.systemui.qs.tiles.WifiTile; import com.android.systemui.qs.tiles.WorkModeTile; import com.android.systemui.util.leak.GarbageMonitor; @@ -68,10 +66,8 @@ public class QSFactoryImpl implements QSFactory { private static final String TAG = "QSFactory"; - private final Provider<WifiTile> mWifiTileProvider; private final Provider<InternetTile> mInternetTileProvider; private final Provider<BluetoothTile> mBluetoothTileProvider; - private final Provider<CellularTile> mCellularTileProvider; private final Provider<DndTile> mDndTileProvider; private final Provider<ColorCorrectionTile> mColorCorrectionTileProvider; private final Provider<ColorInversionTile> mColorInversionTileProvider; @@ -106,10 +102,8 @@ public class QSFactoryImpl implements QSFactory { public QSFactoryImpl( Lazy<QSHost> qsHostLazy, Provider<CustomTile.Builder> customTileBuilderProvider, - Provider<WifiTile> wifiTileProvider, Provider<InternetTile> internetTileProvider, Provider<BluetoothTile> bluetoothTileProvider, - Provider<CellularTile> cellularTileProvider, Provider<DndTile> dndTileProvider, Provider<ColorInversionTile> colorInversionTileProvider, Provider<AirplaneModeTile> airplaneModeTileProvider, @@ -139,10 +133,8 @@ public class QSFactoryImpl implements QSFactory { mQsHostLazy = qsHostLazy; mCustomTileBuilderProvider = customTileBuilderProvider; - mWifiTileProvider = wifiTileProvider; mInternetTileProvider = internetTileProvider; mBluetoothTileProvider = bluetoothTileProvider; - mCellularTileProvider = cellularTileProvider; mDndTileProvider = dndTileProvider; mColorInversionTileProvider = colorInversionTileProvider; mAirplaneModeTileProvider = airplaneModeTileProvider; @@ -186,14 +178,10 @@ public class QSFactoryImpl implements QSFactory { protected QSTileImpl createTileInternal(String tileSpec) { // Stock tiles. switch (tileSpec) { - case "wifi": - return mWifiTileProvider.get(); case "internet": return mInternetTileProvider.get(); case "bt": return mBluetoothTileProvider.get(); - case "cell": - return mCellularTileProvider.get(); case "dnd": return mDndTileProvider.get(); case "inversion": diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java deleted file mode 100644 index 04a25fc193b3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.tiles; - -import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA; - -import android.annotation.NonNull; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.provider.Settings; -import android.service.quicksettings.Tile; -import android.telephony.SubscriptionManager; -import android.text.Html; -import android.text.TextUtils; -import android.view.View; -import android.view.WindowManager.LayoutParams; -import android.widget.Switch; - -import androidx.annotation.Nullable; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settingslib.net.DataUsageController; -import com.android.systemui.Prefs; -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.QSIconView; -import com.android.systemui.plugins.qs.QSTile.SignalState; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.SignalTileView; -import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.statusbar.connectivity.IconState; -import com.android.systemui.statusbar.connectivity.MobileDataIndicators; -import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.SignalCallback; -import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.statusbar.policy.KeyguardStateController; - -import javax.inject.Inject; - -/** Quick settings tile: Cellular **/ -public class CellularTile extends QSTileImpl<SignalState> { - private static final String ENABLE_SETTINGS_DATA_PLAN = "enable.settings.data.plan"; - - private final NetworkController mController; - private final DataUsageController mDataController; - private final KeyguardStateController mKeyguard; - private final CellSignalCallback mSignalCallback = new CellSignalCallback(); - - @Inject - public CellularTile( - QSHost host, - @Background Looper backgroundLooper, - @Main Handler mainHandler, - FalsingManager falsingManager, - MetricsLogger metricsLogger, - StatusBarStateController statusBarStateController, - ActivityStarter activityStarter, - QSLogger qsLogger, - NetworkController networkController, - KeyguardStateController keyguardStateController - - ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, - statusBarStateController, activityStarter, qsLogger); - mController = networkController; - mKeyguard = keyguardStateController; - mDataController = mController.getMobileDataController(); - mController.observe(getLifecycle(), mSignalCallback); - } - - @Override - public SignalState newTileState() { - return new SignalState(); - } - - @Override - public QSIconView createTileView(Context context) { - return new SignalTileView(context); - } - - @Override - public Intent getLongClickIntent() { - if (getState().state == Tile.STATE_UNAVAILABLE) { - return new Intent(Settings.ACTION_WIRELESS_SETTINGS); - } - return getCellularSettingIntent(); - } - - @Override - protected void handleClick(@Nullable View view) { - if (getState().state == Tile.STATE_UNAVAILABLE) { - return; - } - if (mDataController.isMobileDataEnabled()) { - maybeShowDisableDialog(); - } else { - mDataController.setMobileDataEnabled(true); - } - } - - private void maybeShowDisableDialog() { - if (Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, false)) { - // Directly turn off mobile data if the user has seen the dialog before. - mDataController.setMobileDataEnabled(false); - return; - } - String carrierName = mController.getMobileDataNetworkName(); - boolean isInService = mController.isMobileDataNetworkInService(); - if (TextUtils.isEmpty(carrierName) || !isInService) { - carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier); - } - AlertDialog dialog = new Builder(mContext) - .setTitle(R.string.mobile_data_disable_title) - .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName)) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton( - com.android.internal.R.string.alert_windows_notification_turn_off_action, - (d, w) -> { - mDataController.setMobileDataEnabled(false); - Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true); - }) - .create(); - dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG); - SystemUIDialog.setShowForAllUsers(dialog, true); - SystemUIDialog.registerDismissListener(dialog); - SystemUIDialog.setWindowOnTop(dialog, mKeyguard.isShowing()); - dialog.show(); - } - - @Override - protected void handleSecondaryClick(@Nullable View view) { - handleLongClick(view); - } - - @Override - public CharSequence getTileLabel() { - return mContext.getString(R.string.quick_settings_cellular_detail_title); - } - - @Override - protected void handleUpdateState(SignalState state, Object arg) { - CallbackInfo cb = (CallbackInfo) arg; - if (cb == null) { - cb = mSignalCallback.mInfo; - } - - final Resources r = mContext.getResources(); - state.label = r.getString(R.string.mobile_data); - boolean mobileDataEnabled = mDataController.isMobileDataSupported() - && mDataController.isMobileDataEnabled(); - state.value = mobileDataEnabled; - state.activityIn = mobileDataEnabled && cb.activityIn; - state.activityOut = mobileDataEnabled && cb.activityOut; - state.expandedAccessibilityClassName = Switch.class.getName(); - if (cb.noSim) { - state.icon = ResourceIcon.get(R.drawable.ic_qs_no_sim); - } else { - state.icon = ResourceIcon.get(R.drawable.ic_swap_vert); - } - - if (cb.noSim) { - state.state = Tile.STATE_UNAVAILABLE; - state.secondaryLabel = r.getString(R.string.keyguard_missing_sim_message_short); - } else if (cb.airplaneModeEnabled) { - state.state = Tile.STATE_UNAVAILABLE; - state.secondaryLabel = r.getString(R.string.status_bar_airplane); - } else if (mobileDataEnabled) { - state.state = Tile.STATE_ACTIVE; - state.secondaryLabel = appendMobileDataType( - // Only show carrier name if there are more than 1 subscription - cb.multipleSubs ? cb.dataSubscriptionName : "", - getMobileDataContentName(cb)); - } else { - state.state = Tile.STATE_INACTIVE; - state.secondaryLabel = r.getString(R.string.cell_data_off); - } - - state.contentDescription = state.label; - if (state.state == Tile.STATE_INACTIVE) { - // This information is appended later by converting the Tile.STATE_INACTIVE state. - state.stateDescription = ""; - } else { - state.stateDescription = state.secondaryLabel; - } - } - - private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) { - if (TextUtils.isEmpty(dataType)) { - return Html.fromHtml(current.toString(), 0); - } - if (TextUtils.isEmpty(current)) { - return Html.fromHtml(dataType.toString(), 0); - } - String concat = mContext.getString(R.string.mobile_carrier_text_format, current, dataType); - return Html.fromHtml(concat, 0); - } - - private CharSequence getMobileDataContentName(CallbackInfo cb) { - if (cb.roaming && !TextUtils.isEmpty(cb.dataContentDescription)) { - String roaming = mContext.getString(R.string.data_connection_roaming); - String dataDescription = cb.dataContentDescription.toString(); - return mContext.getString(R.string.mobile_data_text_format, roaming, dataDescription); - } - if (cb.roaming) { - return mContext.getString(R.string.data_connection_roaming); - } - return cb.dataContentDescription; - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.QS_CELLULAR; - } - - @Override - public boolean isAvailable() { - return mController.hasMobileDataFeature() - && mHost.getUserContext().getUserId() == UserHandle.USER_SYSTEM; - } - - private static final class CallbackInfo { - boolean airplaneModeEnabled; - @Nullable - CharSequence dataSubscriptionName; - @Nullable - CharSequence dataContentDescription; - boolean activityIn; - boolean activityOut; - boolean noSim; - boolean roaming; - boolean multipleSubs; - } - - private final class CellSignalCallback implements SignalCallback { - private final CallbackInfo mInfo = new CallbackInfo(); - - @Override - public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) { - if (indicators.qsIcon == null) { - // Not data sim, don't display. - return; - } - mInfo.dataSubscriptionName = mController.getMobileDataNetworkName(); - mInfo.dataContentDescription = indicators.qsDescription != null - ? indicators.typeContentDescriptionHtml : null; - mInfo.activityIn = indicators.activityIn; - mInfo.activityOut = indicators.activityOut; - mInfo.roaming = indicators.roaming; - mInfo.multipleSubs = mController.getNumberSubscriptions() > 1; - refreshState(mInfo); - } - - @Override - public void setNoSims(boolean show, boolean simDetected) { - mInfo.noSim = show; - refreshState(mInfo); - } - - @Override - public void setIsAirplaneMode(@NonNull IconState icon) { - mInfo.airplaneModeEnabled = icon.visible; - refreshState(mInfo); - } - } - - static Intent getCellularSettingIntent() { - Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS); - int dataSub = SubscriptionManager.getDefaultDataSubscriptionId(); - if (dataSub != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - intent.putExtra(Settings.EXTRA_SUB_ID, - SubscriptionManager.getDefaultDataSubscriptionId()); - } - return intent; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java deleted file mode 100644 index b2be56cca51e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.tiles; - -import android.annotation.NonNull; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.os.Handler; -import android.os.Looper; -import android.provider.Settings; -import android.service.quicksettings.Tile; -import android.text.TextUtils; -import android.util.Log; -import android.view.View; -import android.widget.Switch; - -import androidx.annotation.Nullable; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.QSIconView; -import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.plugins.qs.QSTile.SignalState; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.AlphaControlledSignalTileView; -import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.qs.tileimpl.QSIconViewImpl; -import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.statusbar.connectivity.AccessPointController; -import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.SignalCallback; -import com.android.systemui.statusbar.connectivity.WifiIcons; -import com.android.systemui.statusbar.connectivity.WifiIndicators; - -import javax.inject.Inject; - -/** Quick settings tile: Wifi **/ -public class WifiTile extends QSTileImpl<SignalState> { - private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS); - - protected final NetworkController mController; - private final AccessPointController mWifiController; - private final QSTile.SignalState mStateBeforeClick = newTileState(); - - protected final WifiSignalCallback mSignalCallback = new WifiSignalCallback(); - private boolean mExpectDisabled; - - @Inject - public WifiTile( - QSHost host, - @Background Looper backgroundLooper, - @Main Handler mainHandler, - FalsingManager falsingManager, - MetricsLogger metricsLogger, - StatusBarStateController statusBarStateController, - ActivityStarter activityStarter, - QSLogger qsLogger, - NetworkController networkController, - AccessPointController accessPointController - ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, - statusBarStateController, activityStarter, qsLogger); - mController = networkController; - mWifiController = accessPointController; - mController.observe(getLifecycle(), mSignalCallback); - mStateBeforeClick.spec = "wifi"; - } - - @Override - public SignalState newTileState() { - return new SignalState(); - } - - @Override - public QSIconView createTileView(Context context) { - return new AlphaControlledSignalTileView(context); - } - - @Override - public Intent getLongClickIntent() { - return WIFI_SETTINGS; - } - - @Override - protected void handleClick(@Nullable View view) { - // Secondary clicks are header clicks, just toggle. - mState.copyTo(mStateBeforeClick); - boolean wifiEnabled = mState.value; - // Immediately enter transient state when turning on wifi. - refreshState(wifiEnabled ? null : ARG_SHOW_TRANSIENT_ENABLING); - mController.setWifiEnabled(!wifiEnabled); - mExpectDisabled = wifiEnabled; - if (mExpectDisabled) { - mHandler.postDelayed(() -> { - if (mExpectDisabled) { - mExpectDisabled = false; - refreshState(); - } - }, QSIconViewImpl.QS_ANIM_LENGTH); - } - } - - @Override - protected void handleSecondaryClick(@Nullable View view) { - if (!mWifiController.canConfigWifi()) { - mActivityStarter.postStartActivityDismissingKeyguard( - new Intent(Settings.ACTION_WIFI_SETTINGS), 0); - return; - } - if (!mState.value) { - mController.setWifiEnabled(true); - } - } - - @Override - public CharSequence getTileLabel() { - return mContext.getString(R.string.quick_settings_wifi_label); - } - - @Override - protected void handleUpdateState(SignalState state, Object arg) { - if (DEBUG) Log.d(TAG, "handleUpdateState arg=" + arg); - final CallbackInfo cb = mSignalCallback.mInfo; - if (mExpectDisabled) { - if (cb.enabled) { - return; // Ignore updates until disabled event occurs. - } else { - mExpectDisabled = false; - } - } - boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING; - boolean wifiConnected = cb.enabled && (cb.wifiSignalIconId > 0) - && (cb.ssid != null || cb.wifiSignalIconId != WifiIcons.QS_WIFI_NO_NETWORK); - boolean wifiNotConnected = (cb.ssid == null) - && (cb.wifiSignalIconId == WifiIcons.QS_WIFI_NO_NETWORK); - if (state.slash == null) { - state.slash = new SlashState(); - state.slash.rotation = 6; - } - state.slash.isSlashed = false; - boolean isTransient = transientEnabling || cb.isTransient; - state.secondaryLabel = getSecondaryLabel(isTransient, cb.statusLabel); - state.state = Tile.STATE_ACTIVE; - state.dualTarget = true; - state.value = transientEnabling || cb.enabled; - state.activityIn = cb.enabled && cb.activityIn; - state.activityOut = cb.enabled && cb.activityOut; - final StringBuffer minimalContentDescription = new StringBuffer(); - final StringBuffer minimalStateDescription = new StringBuffer(); - final Resources r = mContext.getResources(); - if (isTransient) { - state.icon = ResourceIcon.get( - com.android.internal.R.drawable.ic_signal_wifi_transient_animation); - state.label = r.getString(R.string.quick_settings_wifi_label); - } else if (!state.value) { - state.slash.isSlashed = true; - state.state = Tile.STATE_INACTIVE; - state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_DISABLED); - state.label = r.getString(R.string.quick_settings_wifi_label); - } else if (wifiConnected) { - state.icon = ResourceIcon.get(cb.wifiSignalIconId); - state.label = cb.ssid != null ? removeDoubleQuotes(cb.ssid) : getTileLabel(); - } else if (wifiNotConnected) { - state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK); - state.label = r.getString(R.string.quick_settings_wifi_label); - } else { - state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK); - state.label = r.getString(R.string.quick_settings_wifi_label); - } - minimalContentDescription.append( - mContext.getString(R.string.quick_settings_wifi_label)).append(","); - if (state.value) { - if (wifiConnected) { - minimalStateDescription.append(cb.wifiSignalContentDescription); - minimalContentDescription.append(removeDoubleQuotes(cb.ssid)); - if (!TextUtils.isEmpty(state.secondaryLabel)) { - minimalContentDescription.append(",").append(state.secondaryLabel); - } - } - } - state.stateDescription = minimalStateDescription.toString(); - state.contentDescription = minimalContentDescription.toString(); - state.dualLabelContentDescription = r.getString( - R.string.accessibility_quick_settings_open_settings, getTileLabel()); - state.expandedAccessibilityClassName = Switch.class.getName(); - } - - private CharSequence getSecondaryLabel(boolean isTransient, String statusLabel) { - return isTransient - ? mContext.getString(R.string.quick_settings_wifi_secondary_label_transient) - : statusLabel; - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.QS_WIFI; - } - - @Override - public boolean isAvailable() { - return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); - } - - @Nullable - private static String removeDoubleQuotes(String string) { - if (string == null) return null; - final int length = string.length(); - if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) { - return string.substring(1, length - 1); - } - return string; - } - - protected static final class CallbackInfo { - boolean enabled; - boolean connected; - int wifiSignalIconId; - @Nullable - String ssid; - boolean activityIn; - boolean activityOut; - @Nullable - String wifiSignalContentDescription; - boolean isTransient; - @Nullable - public String statusLabel; - - @Override - public String toString() { - return new StringBuilder("CallbackInfo[") - .append("enabled=").append(enabled) - .append(",connected=").append(connected) - .append(",wifiSignalIconId=").append(wifiSignalIconId) - .append(",ssid=").append(ssid) - .append(",activityIn=").append(activityIn) - .append(",activityOut=").append(activityOut) - .append(",wifiSignalContentDescription=").append(wifiSignalContentDescription) - .append(",isTransient=").append(isTransient) - .append(']').toString(); - } - } - - protected final class WifiSignalCallback implements SignalCallback { - final CallbackInfo mInfo = new CallbackInfo(); - - @Override - public void setWifiIndicators(@NonNull WifiIndicators indicators) { - if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + indicators.enabled); - if (indicators.qsIcon == null) { - return; - } - mInfo.enabled = indicators.enabled; - mInfo.connected = indicators.qsIcon.visible; - mInfo.wifiSignalIconId = indicators.qsIcon.icon; - mInfo.ssid = indicators.description; - mInfo.activityIn = indicators.activityIn; - mInfo.activityOut = indicators.activityOut; - mInfo.wifiSignalContentDescription = indicators.qsIcon.contentDescription; - mInfo.isTransient = indicators.isTransient; - mInfo.statusLabel = indicators.statusLabel; - refreshState(); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index a6c7781d891c..72c6bfe371ce 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -101,7 +101,6 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements @MainThread public void onManagedProfileRemoved() { mHost.removeTile(getTileSpec()); - mHost.unmarkTileAsAutoAdded(getTileSpec()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 91ebf79344b6..b21a4857c736 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -687,8 +687,8 @@ public class ScreenshotController { } }); if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { - mScreenshotView.badgeScreenshot( - mContext.getPackageManager().getUserBadgeForDensity(owner, 0)); + mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( + mContext.getDrawable(R.drawable.overlay_badge_background), owner)); } mScreenshotView.setScreenshot(mScreenBitmap, screenInsets); if (DEBUG_WINDOW) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 899cdb74274f..200a7dc08bb3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -1090,7 +1090,7 @@ public class ScreenshotView extends FrameLayout implements mScreenshotBadge.setVisibility(View.GONE); mScreenshotBadge.setImageDrawable(null); mPendingSharedTransition = false; - mActionsContainerBackground.setVisibility(View.GONE); + mActionsContainerBackground.setVisibility(View.INVISIBLE); mActionsContainer.setVisibility(View.GONE); mDismissButton.setVisibility(View.GONE); mScrollingScrim.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 28da38b701bc..61390c582fd6 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -112,7 +112,7 @@ class UserTrackerImpl internal constructor( // These get called when a managed profile goes in or out of quiet mode. addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) - + addAction(Intent.ACTION_MANAGED_PROFILE_ADDED) addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED) addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED) } @@ -129,6 +129,7 @@ class UserTrackerImpl internal constructor( Intent.ACTION_USER_INFO_CHANGED, Intent.ACTION_MANAGED_PROFILE_AVAILABLE, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE, + Intent.ACTION_MANAGED_PROFILE_ADDED, Intent.ACTION_MANAGED_PROFILE_REMOVED, Intent.ACTION_MANAGED_PROFILE_UNLOCKED -> { handleProfilesChanged() diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 80d7bbc04440..10130b0a0882 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -144,6 +144,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; @@ -692,6 +693,7 @@ public final class NotificationPanelViewController implements Dumpable { private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel; private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel; + private GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel; private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -700,6 +702,7 @@ public final class NotificationPanelViewController implements Dumpable { private int mDreamingToLockscreenTransitionTranslationY; private int mOccludedToLockscreenTransitionTranslationY; private int mLockscreenToDreamingTransitionTranslationY; + private int mGoneToDreamingTransitionTranslationY; private int mLockscreenToOccludedTransitionTranslationY; private boolean mUnocclusionTransitionFlagEnabled = false; @@ -735,6 +738,12 @@ public final class NotificationPanelViewController implements Dumpable { step.getTransitionState() == TransitionState.RUNNING; }; + private final Consumer<TransitionStep> mGoneToDreamingTransition = + (TransitionStep step) -> { + mIsOcclusionTransitionRunning = + step.getTransitionState() == TransitionState.RUNNING; + }; + private final Consumer<TransitionStep> mLockscreenToOccludedTransition = (TransitionStep step) -> { mIsOcclusionTransitionRunning = @@ -813,6 +822,7 @@ public final class NotificationPanelViewController implements Dumpable { DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel, OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel, LockscreenToDreamingTransitionViewModel lockscreenToDreamingTransitionViewModel, + GoneToDreamingTransitionViewModel goneToDreamingTransitionViewModel, LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel, @Main CoroutineDispatcher mainDispatcher, KeyguardTransitionInteractor keyguardTransitionInteractor, @@ -834,6 +844,7 @@ public final class NotificationPanelViewController implements Dumpable { mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel; mOccludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel; mLockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel; + mGoneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel; mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @@ -1172,6 +1183,17 @@ public final class NotificationPanelViewController implements Dumpable { setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); + // Gone->Dreaming + collectFlow(mView, mKeyguardTransitionInteractor.getGoneToDreamingTransition(), + mGoneToDreamingTransition, mMainDispatcher); + collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), + mMainDispatcher); + collectFlow(mView, mGoneToDreamingTransitionViewModel.lockscreenTranslationY( + mGoneToDreamingTransitionTranslationY), + setTransitionY(mNotificationStackScrollLayoutController), + mMainDispatcher); + // Lockscreen->Occluded collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(), mLockscreenToOccludedTransition, mMainDispatcher); @@ -1223,6 +1245,8 @@ public final class NotificationPanelViewController implements Dumpable { R.dimen.occluded_to_lockscreen_transition_lockscreen_translation_y); mLockscreenToDreamingTransitionTranslationY = mResources.getDimensionPixelSize( R.dimen.lockscreen_to_dreaming_transition_lockscreen_translation_y); + mGoneToDreamingTransitionTranslationY = mResources.getDimensionPixelSize( + R.dimen.gone_to_dreaming_transition_lockscreen_translation_y); mLockscreenToOccludedTransitionTranslationY = mResources.getDimensionPixelSize( R.dimen.lockscreen_to_occluded_transition_lockscreen_translation_y); } @@ -2340,7 +2364,7 @@ public final class NotificationPanelViewController implements Dumpable { // When false, down but not synthesized motion event. mLastEventSynthesizedDown = mExpectingSynthesizedDown; mLastDownEvents.insert( - mSystemClock.currentTimeMillis(), + event.getEventTime(), mDownX, mDownY, mQsTouchAboveFalsingThreshold, @@ -2473,7 +2497,7 @@ public final class NotificationPanelViewController implements Dumpable { mInitialTouchY = event.getY(); mInitialTouchX = event.getX(); } - if (!isFullyCollapsed()) { + if (!isFullyCollapsed() && !isShadeOrQsHeightAnimationRunning()) { handleQsDown(event); } // defer touches on QQS to shade while shade is collapsing. Added margin for error @@ -5263,6 +5287,11 @@ public final class NotificationPanelViewController implements Dumpable { } } + /** Returns whether a shade or QS expansion animation is running */ + private boolean isShadeOrQsHeightAnimationRunning() { + return mHeightAnimator != null && !mHintAnimationRunning && !mIsSpringBackAnimation; + } + /** * Phase 2: Bounce down. */ @@ -6280,8 +6309,7 @@ public final class NotificationPanelViewController implements Dumpable { mCollapsedAndHeadsUpOnDown = isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); addMovement(event); - boolean regularHeightAnimationRunning = mHeightAnimator != null - && !mHintAnimationRunning && !mIsSpringBackAnimation; + boolean regularHeightAnimationRunning = isShadeOrQsHeightAnimationRunning(); if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) { mTouchSlopExceeded = regularHeightAnimationRunning || mTouchSlopExceededBeforeDown; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 8314ec713ccb..26f8b6222dc1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -321,9 +321,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway; if (onKeyguard && mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) { + // both max and min display refresh rate must be set to take effect: mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardPreferredRefreshRate; + mLpChanged.preferredMinDisplayRefreshRate = mKeyguardPreferredRefreshRate; } else { mLpChanged.preferredMaxDisplayRefreshRate = 0; + mLpChanged.preferredMinDisplayRefreshRate = 0; } Trace.setCounter("display_set_preferred_refresh_rate", (long) mKeyguardPreferredRefreshRate); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index 5fedbeb556c2..11617be40f53 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -36,16 +36,9 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { buffer.log(TAG, LogLevel.DEBUG, msg) } - private inline fun log( - logLevel: LogLevel, - initializer: LogMessage.() -> Unit, - noinline printer: LogMessage.() -> String - ) { - buffer.log(TAG, logLevel, initializer, printer) - } - fun onQsInterceptMoveQsTrackingEnabled(h: Float) { - log( + buffer.log( + TAG, LogLevel.VERBOSE, { double1 = h.toDouble() }, { "onQsIntercept: move action, QS tracking enabled. h = $double1" } @@ -62,7 +55,8 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { keyguardShowing: Boolean, qsExpansionEnabled: Boolean ) { - log( + buffer.log( + TAG, LogLevel.VERBOSE, { int1 = initialTouchY.toInt() @@ -82,7 +76,8 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { } fun logMotionEvent(event: MotionEvent, message: String) { - log( + buffer.log( + TAG, LogLevel.VERBOSE, { str1 = message @@ -99,7 +94,8 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { } fun logMotionEventStatusBarState(event: MotionEvent, statusBarState: Int, message: String) { - log( + buffer.log( + TAG, LogLevel.VERBOSE, { str1 = message @@ -128,25 +124,33 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { tracking: Boolean, dragDownPxAmount: Float, ) { - log(LogLevel.VERBOSE, { - str1 = message - double1 = fraction.toDouble() - bool1 = expanded - bool2 = tracking - long1 = dragDownPxAmount.toLong() - }, { - "$str1 fraction=$double1,expanded=$bool1," + + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = message + double1 = fraction.toDouble() + bool1 = expanded + bool2 = tracking + long1 = dragDownPxAmount.toLong() + }, + { + "$str1 fraction=$double1,expanded=$bool1," + "tracking=$bool2," + "dragDownPxAmount=$dragDownPxAmount" - }) + } + ) } fun logHasVibrated(hasVibratedOnOpen: Boolean, fraction: Float) { - log(LogLevel.VERBOSE, { - bool1 = hasVibratedOnOpen - double1 = fraction.toDouble() - }, { - "hasVibratedOnOpen=$bool1, expansionFraction=$double1" - }) + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = hasVibratedOnOpen + double1 = fraction.toDouble() + }, + { "hasVibratedOnOpen=$bool1, expansionFraction=$double1" } + ) } fun logQsExpansionChanged( @@ -159,42 +163,56 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { qsAnimatorExpand: Boolean, animatingQs: Boolean ) { - log(LogLevel.VERBOSE, { - str1 = message - bool1 = qsExpanded - int1 = qsMinExpansionHeight - int2 = qsMaxExpansionHeight - bool2 = stackScrollerOverscrolling - bool3 = dozing - bool4 = qsAnimatorExpand - // 0 = false, 1 = true - long1 = animatingQs.compareTo(false).toLong() - }, { - "$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," + + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = message + bool1 = qsExpanded + int1 = qsMinExpansionHeight + int2 = qsMaxExpansionHeight + bool2 = stackScrollerOverscrolling + bool3 = dozing + bool4 = qsAnimatorExpand + // 0 = false, 1 = true + long1 = animatingQs.compareTo(false).toLong() + }, + { + "$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," + "stackScrollerOverscrolling=$bool2,dozing=$bool3,qsAnimatorExpand=$bool4," + "animatingQs=$long1" - }) + } + ) } fun logSingleTapUp(isDozing: Boolean, singleTapEnabled: Boolean, isNotDocked: Boolean) { - log(LogLevel.DEBUG, { - bool1 = isDozing - bool2 = singleTapEnabled - bool3 = isNotDocked - }, { - "PulsingGestureListener#onSingleTapUp all of this must true for single " + - "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3" + buffer.log( + TAG, + LogLevel.DEBUG, + { + bool1 = isDozing + bool2 = singleTapEnabled + bool3 = isNotDocked + }, + { + "PulsingGestureListener#onSingleTapUp all of this must true for single " + + "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3" }) } fun logSingleTapUpFalsingState(proximityIsNotNear: Boolean, isNotFalseTap: Boolean) { - log(LogLevel.DEBUG, { - bool1 = proximityIsNotNear - bool2 = isNotFalseTap - }, { - "PulsingGestureListener#onSingleTapUp all of this must true for single " + + buffer.log( + TAG, + LogLevel.DEBUG, + { + bool1 = proximityIsNotNear + bool2 = isNotFalseTap + }, + { + "PulsingGestureListener#onSingleTapUp all of this must true for single " + "tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2" - }) + } + ) } fun logNotInterceptingTouchInstantExpanding( @@ -202,13 +220,18 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { notificationsDragEnabled: Boolean, touchDisabled: Boolean ) { - log(LogLevel.VERBOSE, { - bool1 = instantExpanding - bool2 = notificationsDragEnabled - bool3 = touchDisabled - }, { - "NPVC not intercepting touch, instantExpanding: $bool1, " + + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = instantExpanding + bool2 = notificationsDragEnabled + bool3 = touchDisabled + }, + { + "NPVC not intercepting touch, instantExpanding: $bool1, " + "!notificationsDragEnabled: $bool2, touchDisabled: $bool3" - }) + } + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt index c6a6e875b82d..9851625b6152 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt @@ -32,11 +32,21 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { fun logApplyingWindowLayoutParams(lp: WindowManager.LayoutParams) { - log(DEBUG, { str1 = lp.toString() }, { "Applying new window layout params: $str1" }) + buffer.log( + TAG, + DEBUG, + { str1 = lp.toString() }, + { "Applying new window layout params: $str1" } + ) } fun logNewState(state: Any) { - log(DEBUG, { str1 = state.toString() }, { "Applying new state: $str1" }) + buffer.log( + TAG, + DEBUG, + { str1 = state.toString() }, + { "Applying new state: $str1" } + ) } private inline fun log( @@ -48,11 +58,16 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: } fun logApplyVisibility(visible: Boolean) { - log(DEBUG, { bool1 = visible }, { "Updating visibility, should be visible : $bool1" }) + buffer.log( + TAG, + DEBUG, + { bool1 = visible }, + { "Updating visibility, should be visible : $bool1" }) } fun logShadeVisibleAndFocusable(visible: Boolean) { - log( + buffer.log( + TAG, DEBUG, { bool1 = visible }, { "Updating shade, should be visible and focusable: $bool1" } @@ -60,6 +75,11 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: } fun logShadeFocusable(focusable: Boolean) { - log(DEBUG, { bool1 = focusable }, { "Updating shade, should be focusable : $bool1" }) + buffer.log( + TAG, + DEBUG, + { bool1 = focusable }, + { "Updating shade, should be focusable : $bool1" } + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index 9070eadd9944..149ec545dfa7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -154,9 +154,7 @@ public class AutoTileManager implements UserAwareController { if (!mAutoTracker.isAdded(SAVER)) { mDataSaverController.addCallback(mDataSaverListener); } - if (!mAutoTracker.isAdded(WORK)) { - mManagedProfileController.addCallback(mProfileCallback); - } + mManagedProfileController.addCallback(mProfileCallback); if (!mAutoTracker.isAdded(NIGHT) && ColorDisplayManager.isNightDisplayAvailable(mContext)) { mNightDisplayListener.setCallback(mNightDisplayCallback); @@ -275,18 +273,18 @@ public class AutoTileManager implements UserAwareController { return mCurrentUser.getIdentifier(); } - public void unmarkTileAsAutoAdded(String tabSpec) { - mAutoTracker.setTileRemoved(tabSpec); - } - private final ManagedProfileController.Callback mProfileCallback = new ManagedProfileController.Callback() { @Override public void onManagedProfileChanged() { - if (mAutoTracker.isAdded(WORK)) return; if (mManagedProfileController.hasActiveProfile()) { + if (mAutoTracker.isAdded(WORK)) return; mHost.addTile(WORK); mAutoTracker.setTileAdded(WORK); + } else { + if (!mAutoTracker.isAdded(WORK)) return; + mHost.removeTile(WORK); + mAutoTracker.setTileRemoved(WORK); } } @@ -429,7 +427,7 @@ public class AutoTileManager implements UserAwareController { initSafetyTile(); } else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) { mHost.removeTile(mSafetySpec); - mHost.unmarkTileAsAutoAdded(mSafetySpec); + mAutoTracker.setTileRemoved(mSafetySpec); } } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 895a2934ec1b..db2c0a08c1d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.SESSION_KEYGUARD; +import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME; + import android.annotation.IntDef; import android.content.res.Resources; import android.hardware.biometrics.BiometricFaceConstants; @@ -27,7 +29,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.os.Handler; import android.os.PowerManager; -import android.os.SystemClock; import android.os.Trace; import androidx.annotation.Nullable; @@ -62,6 +63,7 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -78,6 +80,7 @@ import javax.inject.Inject; */ @SysUISingleton public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable { + private static final long RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS = 400L; private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000; private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock"; private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); @@ -169,9 +172,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final MetricsLogger mMetricsLogger; private final AuthController mAuthController; private final StatusBarStateController mStatusBarStateController; + private final WakefulnessLifecycle mWakefulnessLifecycle; private final LatencyTracker mLatencyTracker; private final VibratorHelper mVibratorHelper; private final BiometricUnlockLogger mLogger; + private final SystemClock mSystemClock; private long mLastFpFailureUptimeMillis; private int mNumConsecutiveFpFailures; @@ -279,14 +284,17 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp SessionTracker sessionTracker, LatencyTracker latencyTracker, ScreenOffAnimationController screenOffAnimationController, - VibratorHelper vibrator) { + VibratorHelper vibrator, + SystemClock systemClock + ) { mPowerManager = powerManager; mShadeController = shadeController; mUpdateMonitor = keyguardUpdateMonitor; mUpdateMonitor.registerCallback(this); mMediaManager = notificationMediaManager; mLatencyTracker = latencyTracker; - wakefulnessLifecycle.addObserver(mWakefulnessObserver); + mWakefulnessLifecycle = wakefulnessLifecycle; + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); screenLifecycle.addObserver(mScreenObserver); mNotificationShadeWindowController = notificationShadeWindowController; @@ -306,6 +314,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mScreenOffAnimationController = screenOffAnimationController; mVibratorHelper = vibrator; mLogger = biometricUnlockLogger; + mSystemClock = systemClock; dumpManager.registerDumpable(getClass().getName(), this); } @@ -429,8 +438,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp Runnable wakeUp = ()-> { if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) { mLogger.i("bio wakelock: Authenticated, waking up..."); - mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC, - "android.policy:BIOMETRIC"); + mPowerManager.wakeUp( + mSystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_BIOMETRIC, + "android.policy:BIOMETRIC" + ); } Trace.beginSection("release wake-and-unlock"); releaseBiometricWakeLock(); @@ -670,7 +682,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp startWakeAndUnlock(MODE_ONLY_WAKE); } else if (biometricSourceType == BiometricSourceType.FINGERPRINT && mUpdateMonitor.isUdfpsSupported()) { - long currUptimeMillis = SystemClock.uptimeMillis(); + long currUptimeMillis = mSystemClock.uptimeMillis(); if (currUptimeMillis - mLastFpFailureUptimeMillis < mConsecutiveFpFailureThreshold) { mNumConsecutiveFpFailures += 1; } else { @@ -718,12 +730,26 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp cleanup(); } - //these haptics are for device-entry only + // these haptics are for device-entry only private void vibrateSuccess(BiometricSourceType type) { + if (mAuthController.isSfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()) + && lastWakeupFromPowerButtonWithinHapticThreshold()) { + mLogger.d("Skip auth success haptic. Power button was recently pressed."); + return; + } mVibratorHelper.vibrateAuthSuccess( getClass().getSimpleName() + ", type =" + type + "device-entry::success"); } + private boolean lastWakeupFromPowerButtonWithinHapticThreshold() { + final boolean lastWakeupFromPowerButton = mWakefulnessLifecycle.getLastWakeReason() + == PowerManager.WAKE_REASON_POWER_BUTTON; + return lastWakeupFromPowerButton + && mWakefulnessLifecycle.getLastWakeTime() != UNKNOWN_LAST_WAKE_TIME + && mSystemClock.uptimeMillis() - mWakefulnessLifecycle.getLastWakeTime() + < RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS; + } + private void vibrateError(BiometricSourceType type) { mVibratorHelper.vibrateAuthError( getClass().getSimpleName() + ", type =" + type + "device-entry::error"); @@ -816,7 +842,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (mUpdateMonitor.isUdfpsSupported()) { pw.print(" mNumConsecutiveFpFailures="); pw.println(mNumConsecutiveFpFailures); pw.print(" time since last failure="); - pw.println(SystemClock.uptimeMillis() - mLastFpFailureUptimeMillis); + pw.println(mSystemClock.uptimeMillis() - mLastFpFailureUptimeMillis); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt index 6cd8c78dd52f..9e6bb20f429d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt @@ -16,7 +16,9 @@ package com.android.systemui.statusbar.phone +import android.view.InsetsFlags import android.view.InsetsVisibilities +import android.view.ViewDebug import android.view.WindowInsetsController.Appearance import android.view.WindowInsetsController.Behavior import com.android.internal.statusbar.LetterboxDetails @@ -148,4 +150,20 @@ private data class SystemBarAttributesParams( ) { val letterboxesArray = letterboxes.toTypedArray() val appearanceRegionsArray = appearanceRegions.toTypedArray() + override fun toString(): String { + val appearanceToString = + ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance) + return """SystemBarAttributesParams( + displayId=$displayId, + appearance=$appearanceToString, + appearanceRegions=$appearanceRegions, + navbarColorManagedByIme=$navbarColorManagedByIme, + behavior=$behavior, + requestedVisibilities=$requestedVisibilities, + packageName='$packageName', + letterboxes=$letterboxes, + letterboxesArray=${letterboxesArray.contentToString()}, + appearanceRegionsArray=${appearanceRegionsArray.contentToString()} + )""".trimMargin() + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt index 59603874efde..5562e73f0478 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.pipeline.mobile.data.model import android.telephony.Annotation.NetworkType +import com.android.settingslib.SignalIcon +import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy /** @@ -38,4 +40,12 @@ sealed interface ResolvedNetworkType { data class OverrideNetworkType( override val lookupKey: String, ) : ResolvedNetworkType + + /** Represents the carrier merged network. See [CarrierMergedConnectionRepository]. */ + object CarrierMergedNetworkType : ResolvedNetworkType { + // Effectively unused since [iconGroupOverride] is used instead. + override val lookupKey: String = "cwf" + + val iconGroupOverride: SignalIcon.MobileIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index d04996b4d6ce..6187f64e011d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -22,7 +22,6 @@ import android.telephony.TelephonyManager import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow /** @@ -50,7 +49,7 @@ interface MobileConnectionRepository { * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single * listener + model. */ - val connectionInfo: Flow<MobileConnectionModel> + val connectionInfo: StateFlow<MobileConnectionModel> /** The total number of levels. Used with [SignalDrawable]. */ val numberOfLevels: StateFlow<Int> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt index 8ac12379e59e..22aca0a8b0d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt @@ -39,7 +39,11 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConn import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.CarrierMergedConnectionRepository.Companion.createCarrierMergedConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -60,15 +64,19 @@ import kotlinx.coroutines.launch class DemoMobileConnectionsRepository @Inject constructor( - private val dataSource: DemoModeMobileConnectionDataSource, + private val mobileDataSource: DemoModeMobileConnectionDataSource, + private val wifiDataSource: DemoModeWifiDataSource, @Application private val scope: CoroutineScope, context: Context, private val logFactory: TableLogBufferFactory, ) : MobileConnectionsRepository { - private var demoCommandJob: Job? = null + private var mobileDemoCommandJob: Job? = null + private var wifiDemoCommandJob: Job? = null - private var connectionRepoCache = mutableMapOf<Int, DemoMobileConnectionRepository>() + private var carrierMergedSubId: Int? = null + + private var connectionRepoCache = mutableMapOf<Int, CacheContainer>() private val subscriptionInfoCache = mutableMapOf<Int, SubscriptionModel>() val demoModeFinishedEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1) @@ -144,52 +152,83 @@ constructor( override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel()) override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository { - return connectionRepoCache[subId] - ?: createDemoMobileConnectionRepo(subId).also { connectionRepoCache[subId] = it } + val current = connectionRepoCache[subId]?.repo + if (current != null) { + return current + } + + val new = createDemoMobileConnectionRepo(subId) + connectionRepoCache[subId] = new + return new.repo } - private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository { - val tableLogBuffer = logFactory.getOrCreate("DemoMobileConnectionLog [$subId]", 100) + private fun createDemoMobileConnectionRepo(subId: Int): CacheContainer { + val tableLogBuffer = + logFactory.getOrCreate( + "DemoMobileConnectionLog [$subId]", + MOBILE_CONNECTION_BUFFER_SIZE, + ) - return DemoMobileConnectionRepository( - subId, - tableLogBuffer, - ) + val repo = + DemoMobileConnectionRepository( + subId, + tableLogBuffer, + ) + return CacheContainer(repo, lastMobileState = null) } override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit) fun startProcessingCommands() { - demoCommandJob = + mobileDemoCommandJob = + scope.launch { + mobileDataSource.mobileEvents.filterNotNull().collect { event -> + processMobileEvent(event) + } + } + wifiDemoCommandJob = scope.launch { - dataSource.mobileEvents.filterNotNull().collect { event -> processEvent(event) } + wifiDataSource.wifiEvents.filterNotNull().collect { event -> + processWifiEvent(event) + } } } fun stopProcessingCommands() { - demoCommandJob?.cancel() + mobileDemoCommandJob?.cancel() + wifiDemoCommandJob?.cancel() _subscriptions.value = listOf() connectionRepoCache.clear() subscriptionInfoCache.clear() } - private fun processEvent(event: FakeNetworkEventModel) { + private fun processMobileEvent(event: FakeNetworkEventModel) { when (event) { is Mobile -> { processEnabledMobileState(event) } is MobileDisabled -> { - processDisabledMobileState(event) + maybeRemoveSubscription(event.subId) } } } + private fun processWifiEvent(event: FakeWifiEventModel) { + when (event) { + is FakeWifiEventModel.WifiDisabled -> disableCarrierMerged() + is FakeWifiEventModel.Wifi -> disableCarrierMerged() + is FakeWifiEventModel.CarrierMerged -> processCarrierMergedWifiState(event) + } + } + private fun processEnabledMobileState(state: Mobile) { // get or create the connection repo, and set its values val subId = state.subId ?: DEFAULT_SUB_ID maybeCreateSubscription(subId) val connection = getRepoForSubId(subId) + connectionRepoCache[subId]?.lastMobileState = state + // This is always true here, because we split out disabled states at the data-source level connection.dataEnabled.value = true connection.networkName.value = NetworkNameModel.Derived(state.name) @@ -198,14 +237,36 @@ constructor( connection.connectionInfo.value = state.toMobileConnectionModel() } - private fun processDisabledMobileState(state: MobileDisabled) { + private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) { + // The new carrier merged connection is for a different sub ID, so disable carrier merged + // for the current (now old) sub + if (carrierMergedSubId != event.subscriptionId) { + disableCarrierMerged() + } + + // get or create the connection repo, and set its values + val subId = event.subscriptionId + maybeCreateSubscription(subId) + carrierMergedSubId = subId + + val connection = getRepoForSubId(subId) + // This is always true here, because we split out disabled states at the data-source level + connection.dataEnabled.value = true + connection.networkName.value = NetworkNameModel.Derived(CARRIER_MERGED_NAME) + connection.numberOfLevels.value = event.numberOfLevels + connection.cdmaRoaming.value = false + connection.connectionInfo.value = event.toMobileConnectionModel() + Log.e("CCS", "output connection info = ${connection.connectionInfo.value}") + } + + private fun maybeRemoveSubscription(subId: Int?) { if (_subscriptions.value.isEmpty()) { // Nothing to do here return } - val subId = - state.subId + val finalSubId = + subId ?: run { // For sake of usability, we can allow for no subId arg if there is only one // subscription @@ -223,7 +284,21 @@ constructor( _subscriptions.value[0].subscriptionId } - removeSubscription(subId) + removeSubscription(finalSubId) + } + + private fun disableCarrierMerged() { + val currentCarrierMergedSubId = carrierMergedSubId ?: return + + // If this sub ID was previously not carrier merged, we should reset it to its previous + // connection. + val lastMobileState = connectionRepoCache[carrierMergedSubId]?.lastMobileState + if (lastMobileState != null) { + processEnabledMobileState(lastMobileState) + } else { + // Otherwise, just remove the subscription entirely + removeSubscription(currentCarrierMergedSubId) + } } private fun removeSubscription(subId: Int) { @@ -251,6 +326,10 @@ constructor( ) } + private fun FakeWifiEventModel.CarrierMerged.toMobileConnectionModel(): MobileConnectionModel { + return createCarrierMergedConnectionModel(this.level) + } + private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType { val key = mobileMappingsReverseLookup.value[this] ?: "dis" return DefaultNetworkType(key) @@ -260,9 +339,17 @@ constructor( private const val TAG = "DemoMobileConnectionsRepo" private const val DEFAULT_SUB_ID = 1 + + private const val CARRIER_MERGED_NAME = "Carrier Merged Network" } } +class CacheContainer( + var repo: DemoMobileConnectionRepository, + /** The last received [Mobile] event. Used when switching from carrier merged back to mobile. */ + var lastMobileState: Mobile?, +) + class DemoMobileConnectionRepository( override val subId: Int, override val tableLogBuffer: TableLogBuffer, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt new file mode 100644 index 000000000000..c783b12e0c0b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod + +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * A repository implementation for a carrier merged (aka VCN) network. A carrier merged network is + * delivered to SysUI as a wifi network (see [WifiNetworkModel.CarrierMerged], but is visually + * displayed as a mobile network triangle. + * + * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information. + * + * See [MobileConnectionRepositoryImpl] for a repository implementation of a typical mobile + * connection. + */ +class CarrierMergedConnectionRepository( + override val subId: Int, + override val tableLogBuffer: TableLogBuffer, + defaultNetworkName: NetworkNameModel, + @Application private val scope: CoroutineScope, + val wifiRepository: WifiRepository, +) : MobileConnectionRepository { + + /** + * Outputs the carrier merged network to use, or null if we don't have a valid carrier merged + * network. + */ + private val network: Flow<WifiNetworkModel.CarrierMerged?> = + combine( + wifiRepository.isWifiEnabled, + wifiRepository.isWifiDefault, + wifiRepository.wifiNetwork, + ) { isEnabled, isDefault, network -> + when { + !isEnabled -> null + !isDefault -> null + network !is WifiNetworkModel.CarrierMerged -> null + network.subscriptionId != subId -> { + Log.w( + TAG, + "Connection repo subId=$subId " + + "does not equal wifi repo subId=${network.subscriptionId}; " + + "not showing carrier merged" + ) + null + } + else -> network + } + } + + override val connectionInfo: StateFlow<MobileConnectionModel> = + network + .map { it.toMobileConnectionModel() } + .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel()) + + // TODO(b/238425913): Add logging to this class. + // TODO(b/238425913): Make sure SignalStrength.getEmptyState is used when appropriate. + + // Carrier merged is never roaming. + override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() + + // TODO(b/238425913): Fetch the carrier merged network name. + override val networkName: StateFlow<NetworkNameModel> = + flowOf(defaultNetworkName) + .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName) + + override val numberOfLevels: StateFlow<Int> = + wifiRepository.wifiNetwork + .map { + if (it is WifiNetworkModel.CarrierMerged) { + it.numberOfLevels + } else { + DEFAULT_NUM_LEVELS + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS) + + override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled + + private fun WifiNetworkModel.CarrierMerged?.toMobileConnectionModel(): MobileConnectionModel { + if (this == null) { + return MobileConnectionModel() + } + + return createCarrierMergedConnectionModel(level) + } + + companion object { + /** + * Creates an instance of [MobileConnectionModel] that represents a carrier merged network + * with the given [level]. + */ + fun createCarrierMergedConnectionModel(level: Int): MobileConnectionModel { + return MobileConnectionModel( + primaryLevel = level, + cdmaLevel = level, + // A [WifiNetworkModel.CarrierMerged] instance is always connected. + // (A [WifiNetworkModel.Inactive] represents a disconnected network.) + dataConnectionState = DataConnectionState.Connected, + // TODO(b/238425913): This should come from [WifiRepository.wifiActivity]. + dataActivityDirection = + DataActivityModel( + hasActivityIn = false, + hasActivityOut = false, + ), + resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType, + // Carrier merged is never roaming + isRoaming = false, + + // TODO(b/238425913): Verify that these fields never change for carrier merged. + isEmergencyOnly = false, + operatorAlphaShort = null, + isInService = true, + isGsm = false, + carrierNetworkChangeActive = false, + ) + } + } + + @SysUISingleton + class Factory + @Inject + constructor( + @Application private val scope: CoroutineScope, + private val wifiRepository: WifiRepository, + ) { + fun build( + subId: Int, + mobileLogger: TableLogBuffer, + defaultNetworkName: NetworkNameModel, + ): MobileConnectionRepository { + return CarrierMergedConnectionRepository( + subId, + mobileLogger, + defaultNetworkName, + scope, + wifiRepository, + ) + } + } +} + +private const val TAG = "CarrierMergedConnectionRepository" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt new file mode 100644 index 000000000000..0f30ae249c31 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod + +import androidx.annotation.VisibleForTesting +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.stateIn + +/** + * A repository that fully implements a mobile connection. + * + * This connection could either be a typical mobile connection (see [MobileConnectionRepositoryImpl] + * or a carrier merged connection (see [CarrierMergedConnectionRepository]). This repository + * switches between the two types of connections based on whether the connection is currently + * carrier merged (see [setIsCarrierMerged]). + */ +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) +class FullMobileConnectionRepository( + override val subId: Int, + startingIsCarrierMerged: Boolean, + override val tableLogBuffer: TableLogBuffer, + private val defaultNetworkName: NetworkNameModel, + private val networkNameSeparator: String, + private val globalMobileDataSettingChangedEvent: Flow<Unit>, + @Application scope: CoroutineScope, + private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory, + private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory, +) : MobileConnectionRepository { + /** + * Sets whether this connection is a typical mobile connection or a carrier merged connection. + */ + fun setIsCarrierMerged(isCarrierMerged: Boolean) { + _isCarrierMerged.value = isCarrierMerged + } + + /** + * Returns true if this repo is currently for a carrier merged connection and false otherwise. + */ + @VisibleForTesting fun getIsCarrierMerged() = _isCarrierMerged.value + + private val _isCarrierMerged = MutableStateFlow(startingIsCarrierMerged) + private val isCarrierMerged: StateFlow<Boolean> = + _isCarrierMerged + .logDiffsForTable( + tableLogBuffer, + columnPrefix = "", + columnName = "isCarrierMerged", + initialValue = startingIsCarrierMerged, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), startingIsCarrierMerged) + + private val mobileRepo: MobileConnectionRepository by lazy { + mobileRepoFactory.build( + subId, + tableLogBuffer, + defaultNetworkName, + networkNameSeparator, + globalMobileDataSettingChangedEvent, + ) + } + + private val carrierMergedRepo: MobileConnectionRepository by lazy { + carrierMergedRepoFactory.build(subId, tableLogBuffer, defaultNetworkName) + } + + @VisibleForTesting + internal val activeRepo: StateFlow<MobileConnectionRepository> = run { + val initial = + if (startingIsCarrierMerged) { + carrierMergedRepo + } else { + mobileRepo + } + + this.isCarrierMerged + .mapLatest { isCarrierMerged -> + if (isCarrierMerged) { + carrierMergedRepo + } else { + mobileRepo + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), initial) + } + + override val cdmaRoaming = + activeRepo + .flatMapLatest { it.cdmaRoaming } + .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaRoaming.value) + + override val connectionInfo = + activeRepo + .flatMapLatest { it.connectionInfo } + .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value) + + override val dataEnabled = + activeRepo + .flatMapLatest { it.dataEnabled } + .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value) + + override val numberOfLevels = + activeRepo + .flatMapLatest { it.numberOfLevels } + .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.numberOfLevels.value) + + override val networkName = + activeRepo + .flatMapLatest { it.networkName } + .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value) + + class Factory + @Inject + constructor( + @Application private val scope: CoroutineScope, + private val logFactory: TableLogBufferFactory, + private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory, + private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory, + ) { + fun build( + subId: Int, + startingIsCarrierMerged: Boolean, + defaultNetworkName: NetworkNameModel, + networkNameSeparator: String, + globalMobileDataSettingChangedEvent: Flow<Unit>, + ): FullMobileConnectionRepository { + val mobileLogger = + logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE) + + return FullMobileConnectionRepository( + subId, + startingIsCarrierMerged, + mobileLogger, + defaultNetworkName, + networkNameSeparator, + globalMobileDataSettingChangedEvent, + scope, + mobileRepoFactory, + carrierMergedRepoFactory, + ) + } + + companion object { + /** The buffer size to use for logging. */ + const val MOBILE_CONNECTION_BUFFER_SIZE = 100 + + /** Returns a log buffer name for a mobile connection with the given [subId]. */ + fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]" + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index 4e42f9b31e5c..3f2ce4000ff1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -38,7 +38,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.table.TableLogBuffer -import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel @@ -70,6 +69,10 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn +/** + * A repository implementation for a typical mobile connection (as opposed to a carrier merged + * connection -- see [CarrierMergedConnectionRepository]). + */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) class MobileConnectionRepositoryImpl( @@ -298,18 +301,16 @@ class MobileConnectionRepositoryImpl( private val logger: ConnectivityPipelineLogger, private val globalSettings: GlobalSettings, private val mobileMappingsProxy: MobileMappingsProxy, - private val logFactory: TableLogBufferFactory, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, ) { fun build( subId: Int, + mobileLogger: TableLogBuffer, defaultNetworkName: NetworkNameModel, networkNameSeparator: String, globalMobileDataSettingChangedEvent: Flow<Unit>, ): MobileConnectionRepository { - val mobileLogger = logFactory.getOrCreate(tableBufferLogName(subId), 100) - return MobileConnectionRepositoryImpl( context, subId, @@ -327,8 +328,4 @@ class MobileConnectionRepositoryImpl( ) } } - - companion object { - fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]" - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index c88c70064238..4472e0972a0b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -46,11 +46,12 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel -import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -85,9 +86,14 @@ constructor( private val context: Context, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, - private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory + // Some "wifi networks" should be rendered as a mobile connection, which is why the wifi + // repository is an input to the mobile repository. + // See [CarrierMergedConnectionRepository] for details. + wifiRepository: WifiRepository, + private val fullMobileRepoFactory: FullMobileConnectionRepository.Factory, ) : MobileConnectionsRepository { - private var subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf() + private var subIdRepositoryCache: MutableMap<Int, FullMobileConnectionRepository> = + mutableMapOf() private val defaultNetworkName = NetworkNameModel.Default( @@ -97,30 +103,43 @@ constructor( private val networkNameSeparator: String = context.getString(R.string.status_bar_network_name_separator) + private val carrierMergedSubId: StateFlow<Int?> = + wifiRepository.wifiNetwork + .mapLatest { + if (it is WifiNetworkModel.CarrierMerged) { + it.subscriptionId + } else { + null + } + } + .distinctUntilChanged() + .stateIn(scope, started = SharingStarted.WhileSubscribed(), null) + + private val mobileSubscriptionsChangeEvent: Flow<Unit> = conflatedCallbackFlow { + val callback = + object : SubscriptionManager.OnSubscriptionsChangedListener() { + override fun onSubscriptionsChanged() { + trySend(Unit) + } + } + + subscriptionManager.addOnSubscriptionsChangedListener( + bgDispatcher.asExecutor(), + callback, + ) + + awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) } + } + /** * State flow that emits the set of mobile data subscriptions, each represented by its own - * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each - * info object, but for now we keep track of the infos themselves. + * [SubscriptionModel]. */ override val subscriptions: StateFlow<List<SubscriptionModel>> = - conflatedCallbackFlow { - val callback = - object : SubscriptionManager.OnSubscriptionsChangedListener() { - override fun onSubscriptionsChanged() { - trySend(Unit) - } - } - - subscriptionManager.addOnSubscriptionsChangedListener( - bgDispatcher.asExecutor(), - callback, - ) - - awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) } - } + merge(mobileSubscriptionsChangeEvent, carrierMergedSubId) .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } } .logInputChange(logger, "onSubscriptionsChanged") - .onEach { infos -> dropUnusedReposFromCache(infos) } + .onEach { infos -> updateRepos(infos) } .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf()) /** StateFlow that keeps track of the current active mobile data subscription */ @@ -173,7 +192,7 @@ constructor( .distinctUntilChanged() .logInputChange(logger, "defaultMobileIconGroup") - override fun getRepoForSubId(subId: Int): MobileConnectionRepository { + override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository { if (!isValidSubId(subId)) { throw IllegalArgumentException( "subscriptionId $subId is not in the list of valid subscriptions" @@ -251,15 +270,27 @@ constructor( @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache - private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository { - return mobileConnectionRepositoryFactory.build( + private fun createRepositoryForSubId(subId: Int): FullMobileConnectionRepository { + return fullMobileRepoFactory.build( subId, + isCarrierMerged(subId), defaultNetworkName, networkNameSeparator, globalMobileDataSettingChangedEvent, ) } + private fun updateRepos(newInfos: List<SubscriptionModel>) { + dropUnusedReposFromCache(newInfos) + subIdRepositoryCache.forEach { (subId, repo) -> + repo.setIsCarrierMerged(isCarrierMerged(subId)) + } + } + + private fun isCarrierMerged(subId: Int): Boolean { + return subId == carrierMergedSubId.value + } + private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) { // Remove any connection repository from the cache that isn't in the new set of IDs. They // will get garbage collected once their subscribers go away diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index 9427c6b9fece..003df2482c6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -22,8 +22,8 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository -import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -138,7 +138,11 @@ class MobileIconInteractorImpl( defaultMobileIconMapping, defaultMobileIconGroup, ) { info, mapping, defaultGroup -> - mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup + when (info.resolvedNetworkType) { + is ResolvedNetworkType.CarrierMergedNetworkType -> + info.resolvedNetworkType.iconGroupOverride + else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup + } } .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt index 4251d18357f7..da2daf2c55ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt @@ -16,13 +16,18 @@ package com.android.systemui.statusbar.pipeline.wifi.data.model +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.annotation.VisibleForTesting import com.android.systemui.log.table.TableRowLogger import com.android.systemui.log.table.Diffable +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS /** Provides information about the current wifi network. */ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { + // TODO(b/238425913): Have a better, more unified strategy for diff-logging instead of + // copy-pasting the column names for each sub-object. + /** * A model representing that we couldn't fetch any wifi information. * @@ -41,8 +46,43 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { override fun logFull(row: TableRowLogger) { row.logChange(COL_NETWORK_TYPE, TYPE_UNAVAILABLE) row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) + row.logChange(COL_SUB_ID, SUB_ID_DEFAULT) + row.logChange(COL_VALIDATED, false) + row.logChange(COL_LEVEL, LEVEL_DEFAULT) + row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) + row.logChange(COL_SSID, null) + row.logChange(COL_PASSPOINT_ACCESS_POINT, false) + row.logChange(COL_ONLINE_SIGN_UP, false) + row.logChange(COL_PASSPOINT_NAME, null) + } + } + + /** + * A model representing that the wifi information we received was invalid in some way. + */ + data class Invalid( + /** A description of why the wifi information was invalid. */ + val invalidReason: String, + ) : WifiNetworkModel() { + override fun toString() = "WifiNetwork.Invalid[$invalidReason]" + override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { + if (prevVal !is Invalid) { + logFull(row) + return + } + + if (invalidReason != prevVal.invalidReason) { + row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason") + } + } + + override fun logFull(row: TableRowLogger) { + row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason") + row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) + row.logChange(COL_SUB_ID, SUB_ID_DEFAULT) row.logChange(COL_VALIDATED, false) row.logChange(COL_LEVEL, LEVEL_DEFAULT) + row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) @@ -59,18 +99,21 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { return } - if (prevVal is CarrierMerged) { - // The only difference between CarrierMerged and Inactive is the type - row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE) - return - } - - // When changing from Active to Inactive, we need to log diffs to all the fields. - logFullNonActiveNetwork(TYPE_INACTIVE, row) + // When changing to Inactive, we need to log diffs to all the fields. + logFull(row) } override fun logFull(row: TableRowLogger) { - logFullNonActiveNetwork(TYPE_INACTIVE, row) + row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE) + row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) + row.logChange(COL_SUB_ID, SUB_ID_DEFAULT) + row.logChange(COL_VALIDATED, false) + row.logChange(COL_LEVEL, LEVEL_DEFAULT) + row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) + row.logChange(COL_SSID, null) + row.logChange(COL_PASSPOINT_ACCESS_POINT, false) + row.logChange(COL_ONLINE_SIGN_UP, false) + row.logChange(COL_PASSPOINT_NAME, null) } } @@ -80,22 +123,75 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { * * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information. */ - object CarrierMerged : WifiNetworkModel() { - override fun toString() = "WifiNetwork.CarrierMerged" + data class CarrierMerged( + /** + * The [android.net.Network.netId] we received from + * [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network. + * + * Importantly, **not** [android.net.wifi.WifiInfo.getNetworkId]. + */ + val networkId: Int, + + /** + * The subscription ID that this connection represents. + * + * Comes from [android.net.wifi.WifiInfo.getSubscriptionId]. + * + * Per that method, this value must not be [INVALID_SUBSCRIPTION_ID] (if it was invalid, + * then this is *not* a carrier merged network). + */ + val subscriptionId: Int, + + /** + * The signal level, guaranteed to be 0 <= level <= numberOfLevels. + */ + val level: Int, + + /** + * The maximum possible level. + */ + val numberOfLevels: Int = DEFAULT_NUM_LEVELS, + ) : WifiNetworkModel() { + init { + require(level in MIN_VALID_LEVEL..numberOfLevels) { + "0 <= wifi level <= $numberOfLevels required; level was $level" + } + require(subscriptionId != INVALID_SUBSCRIPTION_ID) { + "subscription ID cannot be invalid" + } + } override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { - if (prevVal is CarrierMerged) { + if (prevVal !is CarrierMerged) { + logFull(row) return } - if (prevVal is Inactive) { - // The only difference between CarrierMerged and Inactive is the type. - row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED) - return + if (prevVal.networkId != networkId) { + row.logChange(COL_NETWORK_ID, networkId) } + if (prevVal.subscriptionId != subscriptionId) { + row.logChange(COL_SUB_ID, subscriptionId) + } + if (prevVal.level != level) { + row.logChange(COL_LEVEL, level) + } + if (prevVal.numberOfLevels != numberOfLevels) { + row.logChange(COL_NUM_LEVELS, numberOfLevels) + } + } - // When changing from Active to CarrierMerged, we need to log diffs to all the fields. - logFullNonActiveNetwork(TYPE_CARRIER_MERGED, row) + override fun logFull(row: TableRowLogger) { + row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED) + row.logChange(COL_NETWORK_ID, networkId) + row.logChange(COL_SUB_ID, subscriptionId) + row.logChange(COL_VALIDATED, true) + row.logChange(COL_LEVEL, level) + row.logChange(COL_NUM_LEVELS, numberOfLevels) + row.logChange(COL_SSID, null) + row.logChange(COL_PASSPOINT_ACCESS_POINT, false) + row.logChange(COL_ONLINE_SIGN_UP, false) + row.logChange(COL_PASSPOINT_NAME, null) } } @@ -137,38 +233,50 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { if (prevVal !is Active) { - row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE) + logFull(row) + return } - if (prevVal !is Active || prevVal.networkId != networkId) { + if (prevVal.networkId != networkId) { row.logChange(COL_NETWORK_ID, networkId) } - if (prevVal !is Active || prevVal.isValidated != isValidated) { + if (prevVal.isValidated != isValidated) { row.logChange(COL_VALIDATED, isValidated) } - if (prevVal !is Active || prevVal.level != level) { + if (prevVal.level != level) { row.logChange(COL_LEVEL, level) } - if (prevVal !is Active || prevVal.ssid != ssid) { + if (prevVal.ssid != ssid) { row.logChange(COL_SSID, ssid) } // TODO(b/238425913): The passpoint-related values are frequently never used, so it // would be great to not log them when they're not used. - if (prevVal !is Active || prevVal.isPasspointAccessPoint != isPasspointAccessPoint) { + if (prevVal.isPasspointAccessPoint != isPasspointAccessPoint) { row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint) } - if (prevVal !is Active || - prevVal.isOnlineSignUpForPasspointAccessPoint != + if (prevVal.isOnlineSignUpForPasspointAccessPoint != isOnlineSignUpForPasspointAccessPoint) { row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint) } - if (prevVal !is Active || - prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) { + if (prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) { row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName) } } + override fun logFull(row: TableRowLogger) { + row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE) + row.logChange(COL_NETWORK_ID, networkId) + row.logChange(COL_SUB_ID, null) + row.logChange(COL_VALIDATED, isValidated) + row.logChange(COL_LEVEL, level) + row.logChange(COL_NUM_LEVELS, null) + row.logChange(COL_SSID, ssid) + row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint) + row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint) + row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName) + } + override fun toString(): String { // Only include the passpoint-related values in the string if we have them. (Most // networks won't have them so they'll be mostly clutter.) @@ -189,21 +297,13 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { companion object { @VisibleForTesting - internal const val MIN_VALID_LEVEL = 0 - @VisibleForTesting internal const val MAX_VALID_LEVEL = 4 } } - internal fun logFullNonActiveNetwork(type: String, row: TableRowLogger) { - row.logChange(COL_NETWORK_TYPE, type) - row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) - row.logChange(COL_VALIDATED, false) - row.logChange(COL_LEVEL, LEVEL_DEFAULT) - row.logChange(COL_SSID, null) - row.logChange(COL_PASSPOINT_ACCESS_POINT, false) - row.logChange(COL_ONLINE_SIGN_UP, false) - row.logChange(COL_PASSPOINT_NAME, null) + companion object { + @VisibleForTesting + internal const val MIN_VALID_LEVEL = 0 } } @@ -214,12 +314,16 @@ const val TYPE_ACTIVE = "Active" const val COL_NETWORK_TYPE = "type" const val COL_NETWORK_ID = "networkId" +const val COL_SUB_ID = "subscriptionId" const val COL_VALIDATED = "isValidated" const val COL_LEVEL = "level" +const val COL_NUM_LEVELS = "maxLevel" const val COL_SSID = "ssid" const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint" const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint" const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName" val LEVEL_DEFAULT: String? = null +val NUM_LEVELS_DEFAULT: String? = null val NETWORK_ID_DEFAULT: String? = null +val SUB_ID_DEFAULT: String? = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt index c588945fbd67..caac8fa2f2c3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt @@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK import com.android.systemui.demomode.DemoModeController +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -43,10 +44,10 @@ constructor( private fun Bundle.toWifiEvent(): FakeWifiEventModel? { val wifi = getString("wifi") ?: return null - return if (wifi == "show") { - activeWifiEvent() - } else { - FakeWifiEventModel.WifiDisabled + return when (wifi) { + "show" -> activeWifiEvent() + "carriermerged" -> carrierMergedWifiEvent() + else -> FakeWifiEventModel.WifiDisabled } } @@ -64,6 +65,14 @@ constructor( ) } + private fun Bundle.carrierMergedWifiEvent(): FakeWifiEventModel.CarrierMerged { + val subId = getString("slot")?.toInt() ?: DEFAULT_CARRIER_MERGED_SUB_ID + val level = getString("level")?.toInt() ?: 0 + val numberOfLevels = getString("numlevels")?.toInt() ?: DEFAULT_NUM_LEVELS + + return FakeWifiEventModel.CarrierMerged(subId, level, numberOfLevels) + } + private fun String.toActivity(): Int = when (this) { "inout" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT @@ -71,4 +80,8 @@ constructor( "out" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE } + + companion object { + const val DEFAULT_CARRIER_MERGED_SUB_ID = 10 + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index be3d7d4e65c4..e161b3e42d02 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -66,6 +66,7 @@ constructor( private fun processEvent(event: FakeWifiEventModel) = when (event) { is FakeWifiEventModel.Wifi -> processEnabledWifiState(event) + is FakeWifiEventModel.CarrierMerged -> processCarrierMergedWifiState(event) is FakeWifiEventModel.WifiDisabled -> processDisabledWifiState() } @@ -85,6 +86,14 @@ constructor( _wifiNetwork.value = event.toWifiNetworkModel() } + private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) { + _isWifiEnabled.value = true + _isWifiDefault.value = true + // TODO(b/238425913): Support activity in demo mode. + _wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false) + _wifiNetwork.value = event.toCarrierMergedModel() + } + private fun FakeWifiEventModel.Wifi.toWifiNetworkModel(): WifiNetworkModel = WifiNetworkModel.Active( networkId = DEMO_NET_ID, @@ -99,6 +108,14 @@ constructor( passpointProviderFriendlyName = null, ) + private fun FakeWifiEventModel.CarrierMerged.toCarrierMergedModel(): WifiNetworkModel = + WifiNetworkModel.CarrierMerged( + networkId = DEMO_NET_ID, + subscriptionId = subscriptionId, + level = level, + numberOfLevels = numberOfLevels, + ) + companion object { private const val DEMO_NET_ID = 1234 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt index 2353fb82f3b1..518f8ce66d2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt @@ -29,5 +29,11 @@ sealed interface FakeWifiEventModel { val validated: Boolean?, ) : FakeWifiEventModel + data class CarrierMerged( + val subscriptionId: Int, + val level: Int, + val numberOfLevels: Int, + ) : FakeWifiEventModel + object WifiDisabled : FakeWifiEventModel } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index c47c20d280c7..d26499c18661 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -29,6 +29,7 @@ import android.net.NetworkRequest import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.net.wifi.WifiManager.TrafficStateCallback +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import com.android.settingslib.Utils import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -269,7 +270,19 @@ constructor( wifiManager: WifiManager, ): WifiNetworkModel { return if (wifiInfo.isCarrierMerged) { - WifiNetworkModel.CarrierMerged + if (wifiInfo.subscriptionId == INVALID_SUBSCRIPTION_ID) { + WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON) + } else { + WifiNetworkModel.CarrierMerged( + networkId = network.getNetId(), + subscriptionId = wifiInfo.subscriptionId, + level = wifiManager.calculateSignalLevel(wifiInfo.rssi), + // The WiFi signal level returned by WifiManager#calculateSignalLevel start + // from 0, so WifiManager#getMaxSignalLevel + 1 represents the total level + // buckets count. + numberOfLevels = wifiManager.maxSignalLevel + 1, + ) + } } else { WifiNetworkModel.Active( network.getNetId(), @@ -302,6 +315,9 @@ constructor( .build() private const val WIFI_NETWORK_CALLBACK_NAME = "wifiNetworkModel" + + private const val CARRIER_MERGED_INVALID_SUB_ID_REASON = + "Wifi network was carrier merged but had invalid sub ID" } @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt index 980560ab5d58..86dcd18c643c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt @@ -66,6 +66,7 @@ class WifiInteractorImpl @Inject constructor( override val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info -> when (info) { is WifiNetworkModel.Unavailable -> null + is WifiNetworkModel.Invalid -> null is WifiNetworkModel.Inactive -> null is WifiNetworkModel.CarrierMerged -> null is WifiNetworkModel.Active -> when { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt index 824b5972ba4b..95431afb71bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt @@ -83,6 +83,7 @@ constructor( private fun WifiNetworkModel.icon(): WifiIcon { return when (this) { is WifiNetworkModel.Unavailable -> WifiIcon.Hidden + is WifiNetworkModel.Invalid -> WifiIcon.Hidden is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden is WifiNetworkModel.Inactive -> WifiIcon.Visible( res = WIFI_NO_NETWORK, diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt index 14a9161ac291..5a8850a9f89b 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt @@ -52,8 +52,8 @@ constructor( eventTimeMillis: Long, batteryState: BatteryState ) { - if (batteryState.isPresent) { - stylusUsiPowerUi.updateBatteryState(batteryState) + if (batteryState.isPresent && batteryState.capacity > 0f) { + stylusUsiPowerUi.updateBatteryState(deviceId, batteryState) } } @@ -61,6 +61,7 @@ constructor( if (!featureFlags.isEnabled(Flags.ENABLE_USI_BATTERY_NOTIFICATIONS)) return if (!hostDeviceSupportsStylusInput()) return + stylusUsiPowerUi.init() stylusManager.registerCallback(this) stylusManager.startListener() } diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt index e8216576811a..8d5e01c5b782 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt @@ -18,17 +18,21 @@ package com.android.systemui.stylus import android.Manifest import android.app.PendingIntent +import android.content.ActivityNotFoundException import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.hardware.BatteryState import android.hardware.input.InputManager +import android.os.Bundle import android.os.Handler import android.os.UserHandle +import android.util.Log import android.view.InputDevice import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -53,6 +57,7 @@ constructor( // These values must only be accessed on the handler. private var batteryCapacity = 1.0f private var suppressed = false + private var inputDeviceId: Int? = null fun init() { val filter = @@ -87,10 +92,12 @@ constructor( } } - fun updateBatteryState(batteryState: BatteryState) { + fun updateBatteryState(deviceId: Int, batteryState: BatteryState) { handler.post updateBattery@{ - if (batteryState.capacity == batteryCapacity) return@updateBattery + if (batteryState.capacity == batteryCapacity || batteryState.capacity <= 0f) + return@updateBattery + inputDeviceId = deviceId batteryCapacity = batteryState.capacity refresh() } @@ -150,23 +157,41 @@ constructor( } private fun getPendingBroadcast(action: String): PendingIntent? { - return PendingIntent.getBroadcastAsUser( + return PendingIntent.getBroadcast( context, 0, - Intent(action), + Intent(action).setPackage(context.packageName), PendingIntent.FLAG_IMMUTABLE, - UserHandle.CURRENT ) } - private val receiver: BroadcastReceiver = + @VisibleForTesting + internal val receiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { ACTION_DISMISSED_LOW_BATTERY -> updateSuppression(true) ACTION_CLICKED_LOW_BATTERY -> { updateSuppression(true) - // TODO(b/261584943): open USI device details page + if (inputDeviceId == null) return + + val args = Bundle() + args.putInt(KEY_DEVICE_INPUT_ID, inputDeviceId!!) + try { + context.startActivity( + Intent(ACTION_STYLUS_USI_DETAILS) + .putExtra(KEY_SETTINGS_FRAGMENT_ARGS, args) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } catch (e: ActivityNotFoundException) { + // In the rare scenario where the Settings app manifest doesn't contain + // the USI details activity, ignore the intent. + Log.e( + StylusUsiPowerUI::class.java.simpleName, + "Cannot open USI details page." + ) + } } } } @@ -179,7 +204,11 @@ constructor( private val USI_NOTIFICATION_ID = R.string.stylus_battery_low_percentage - private const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss" - private const val ACTION_CLICKED_LOW_BATTERY = "StylusUsiPowerUI.click" + @VisibleForTesting const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss" + @VisibleForTesting const val ACTION_CLICKED_LOW_BATTERY = "StylusUsiPowerUI.click" + @VisibleForTesting + const val ACTION_STYLUS_USI_DETAILS = "com.android.settings.STYLUS_USI_DETAILS_SETTINGS" + @VisibleForTesting const val KEY_DEVICE_INPUT_ID = "device_input_id" + @VisibleForTesting const val KEY_SETTINGS_FRAGMENT_ARGS = ":settings:show_fragment_args" } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index 59ad24a3e7bb..2709da38a7d8 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -17,6 +17,9 @@ package com.android.systemui.unfold import android.content.Context +import android.hardware.devicestate.DeviceStateManager +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.LifecycleScreenStatusProvider import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.system.SystemUnfoldSharedModule @@ -32,6 +35,7 @@ import dagger.Lazy import dagger.Module import dagger.Provides import java.util.Optional +import java.util.concurrent.Executor import javax.inject.Named import javax.inject.Singleton @@ -40,6 +44,20 @@ class UnfoldTransitionModule { @Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui" + /** A globally available FoldStateListener that allows one to query the fold state. */ + @Provides + @Singleton + fun providesFoldStateListener( + deviceStateManager: DeviceStateManager, + @Application context: Context, + @Main executor: Executor + ): DeviceStateManager.FoldStateListener { + val listener = DeviceStateManager.FoldStateListener(context) + deviceStateManager.registerCallback(executor, listener) + + return listener + } + @Provides @Singleton fun providesFoldStateLoggingProvider( diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 9a9387b96817..df6752a2b69d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -32,6 +32,9 @@ import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELL import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser; +import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED; +import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; +import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; import static com.google.common.truth.Truth.assertThat; @@ -92,6 +95,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.service.dreams.IDreamManager; import android.service.trust.TrustAgentService; import android.telephony.ServiceState; @@ -124,6 +128,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; @@ -193,6 +198,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private DevicePolicyManager mDevicePolicyManager; @Mock + private DevicePostureController mDevicePostureController; + @Mock private IDreamManager mDreamManager; @Mock private KeyguardBypassController mKeyguardBypassController; @@ -300,6 +307,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { .thenReturn(new ServiceState()); when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings); when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false); + when(mDevicePostureController.getDevicePosture()).thenReturn(DEVICE_POSTURE_UNKNOWN); mMockitoSession = ExtendedMockito.mockitoSession() .spyStatic(SubscriptionManager.class) @@ -311,6 +319,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mUserTracker.getUserId()).thenReturn(mCurrentUserId); ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService); + mContext.getOrCreateTestableResources().addOverride( + com.android.systemui.R.integer.config_face_auth_supported_posture, + DEVICE_POSTURE_UNKNOWN); mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig( mContext.getResources(), mGlobalSettings, @@ -1254,7 +1265,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled() + public void startsListeningForSfps_whenKeyguardIsVisible_ifRequireInteractiveToAuthEnabled() throws RemoteException { // SFPS supported and enrolled final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); @@ -1262,12 +1273,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mAuthController.getSfpsProps()).thenReturn(props); when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - // WHEN require screen on to auth is disabled, and keyguard is not awake + // WHEN require interactive to auth is disabled, and keyguard is not awake when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(false); - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true); - // Preconditions for sfps auth to run keyguardNotGoingAway(); currentUserIsPrimary(); @@ -1282,7 +1290,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // THEN we should listen for sfps when screen off, because require screen on is disabled assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); - // WHEN require screen on to auth is enabled, and keyguard is not awake + // WHEN require interactive to auth is enabled, and keyguard is not awake when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(true); // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled @@ -1297,6 +1305,61 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); } + @Test + public void notListeningForSfps_whenGoingToSleep_ifRequireInteractiveToAuthEnabled() + throws RemoteException { + // GIVEN SFPS supported and enrolled + final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); + props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON)); + when(mAuthController.getSfpsProps()).thenReturn(props); + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + + // GIVEN Preconditions for sfps auth to run + keyguardNotGoingAway(); + currentUserIsPrimary(); + currentUserDoesNotHaveTrust(); + biometricsNotDisabledThroughDevicePolicyManager(); + biometricsEnabledForCurrentUser(); + userNotCurrentlySwitching(); + statusBarShadeIsLocked(); + + // WHEN require interactive to auth is enabled & keyguard is going to sleep + when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(true); + deviceGoingToSleep(); + + mTestableLooper.processAllMessages(); + + // THEN we should NOT listen for sfps because device is going to sleep + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse(); + } + + @Test + public void listeningForSfps_whenGoingToSleep_ifRequireInteractiveToAuthDisabled() + throws RemoteException { + // GIVEN SFPS supported and enrolled + final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); + props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON)); + when(mAuthController.getSfpsProps()).thenReturn(props); + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + + // GIVEN Preconditions for sfps auth to run + keyguardNotGoingAway(); + currentUserIsPrimary(); + currentUserDoesNotHaveTrust(); + biometricsNotDisabledThroughDevicePolicyManager(); + biometricsEnabledForCurrentUser(); + userNotCurrentlySwitching(); + statusBarShadeIsLocked(); + + // WHEN require interactive to auth is disabled & keyguard is going to sleep + when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(false); + deviceGoingToSleep(); + + mTestableLooper.processAllMessages(); + + // THEN we should listen for sfps because screen on to auth is disabled + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); + } private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal( @FingerprintSensorProperties.SensorType int sensorType) { @@ -2189,6 +2252,54 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { eq(true)); } + @Test + public void testShouldListenForFace_withAuthSupportPostureConfig_returnsTrue() + throws RemoteException { + mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_CLOSED; + keyguardNotGoingAway(); + bouncerFullyVisibleAndNotGoingToSleep(); + currentUserIsPrimary(); + currentUserDoesNotHaveTrust(); + biometricsNotDisabledThroughDevicePolicyManager(); + biometricsEnabledForCurrentUser(); + userNotCurrentlySwitching(); + supportsFaceDetection(); + + deviceInPostureStateOpened(); + mTestableLooper.processAllMessages(); + // Should not listen for face when posture state in DEVICE_POSTURE_OPENED + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); + + deviceInPostureStateClosed(); + mTestableLooper.processAllMessages(); + // Should listen for face when posture state in DEVICE_POSTURE_CLOSED + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); + } + + @Test + public void testShouldListenForFace_withoutAuthSupportPostureConfig_returnsTrue() + throws RemoteException { + mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_UNKNOWN; + keyguardNotGoingAway(); + bouncerFullyVisibleAndNotGoingToSleep(); + currentUserIsPrimary(); + currentUserDoesNotHaveTrust(); + biometricsNotDisabledThroughDevicePolicyManager(); + biometricsEnabledForCurrentUser(); + userNotCurrentlySwitching(); + supportsFaceDetection(); + + deviceInPostureStateClosed(); + mTestableLooper.processAllMessages(); + // Whether device in any posture state, always listen for face + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); + + deviceInPostureStateOpened(); + mTestableLooper.processAllMessages(); + // Whether device in any posture state, always listen for face + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); + } + private void userDeviceLockDown() { when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId)) @@ -2268,6 +2379,14 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { .onAuthenticationAcquired(FINGERPRINT_ACQUIRED_START); } + private void deviceInPostureStateOpened() { + mKeyguardUpdateMonitor.mPostureCallback.onPostureChanged(DEVICE_POSTURE_OPENED); + } + + private void deviceInPostureStateClosed() { + mKeyguardUpdateMonitor.mPostureCallback.onPostureChanged(DEVICE_POSTURE_CLOSED); + } + private void successfulFingerprintAuth() { mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback .onAuthenticationSucceeded( @@ -2409,7 +2528,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mPowerManager, mTrustManager, mSubscriptionManager, mUserManager, mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager, mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager, - mFaceWakeUpTriggersConfig, Optional.of(mInteractiveToAuthProvider)); + mFaceWakeUpTriggersConfig, mDevicePostureController, + Optional.of(mInteractiveToAuthProvider)); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt new file mode 100644 index 000000000000..af46d9b97abf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.udfps + +import android.graphics.Point +import android.graphics.Rect +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters +import org.mockito.Mockito.spy +import org.mockito.Mockito.`when` as whenEver + +@SmallTest +@RunWith(Parameterized::class) +class EllipseOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() { + val underTest = spy(EllipseOverlapDetector(neededPoints = 1)) + + @Before + fun setUp() { + // Use one single center point for testing, required or total number of points may change + whenEver(underTest.calculateSensorPoints(SENSOR)) + .thenReturn(listOf(Point(SENSOR.centerX(), SENSOR.centerY()))) + } + + @Test + fun isGoodOverlap() { + val touchData = + TOUCH_DATA.copy( + x = testCase.x.toFloat(), + y = testCase.y.toFloat(), + minor = testCase.minor, + major = testCase.major + ) + val actual = underTest.isGoodOverlap(touchData, SENSOR) + + assertThat(actual).isEqualTo(testCase.expected) + } + + data class TestCase( + val x: Int, + val y: Int, + val minor: Float, + val major: Float, + val expected: Boolean + ) + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun data(): List<TestCase> = + listOf( + genTestCases( + innerXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()), + innerYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY()), + outerXs = listOf(SENSOR.left - 1, SENSOR.right + 1), + outerYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1), + minor = 300f, + major = 300f, + expected = true + ), + genTestCases( + innerXs = listOf(SENSOR.left, SENSOR.right), + innerYs = listOf(SENSOR.top, SENSOR.bottom), + outerXs = listOf(SENSOR.left - 1, SENSOR.right + 1), + outerYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1), + minor = 100f, + major = 100f, + expected = false + ) + ) + .flatten() + } +} + +/* Placeholder touch parameters. */ +private const val POINTER_ID = 42 +private const val NATIVE_MINOR = 2.71828f +private const val NATIVE_MAJOR = 3.14f +private const val ORIENTATION = 0f // used for perfect circles +private const val TIME = 12345699L +private const val GESTURE_START = 12345600L + +/* Template [NormalizedTouchData]. */ +private val TOUCH_DATA = + NormalizedTouchData( + POINTER_ID, + x = 0f, + y = 0f, + NATIVE_MINOR, + NATIVE_MAJOR, + ORIENTATION, + TIME, + GESTURE_START + ) + +private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 400 /* bottom */) + +private fun genTestCases( + innerXs: List<Int>, + innerYs: List<Int>, + outerXs: List<Int>, + outerYs: List<Int>, + minor: Float, + major: Float, + expected: Boolean +): List<EllipseOverlapDetectorTest.TestCase> { + return (innerXs + outerXs).flatMap { x -> + (innerYs + outerYs).map { y -> + EllipseOverlapDetectorTest.TestCase(x, y, minor, major, expected) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java index 0fadc138637a..e4df754ec96a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java @@ -106,6 +106,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { mClassifiers.add(mClassifierB); when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mFalsingDataProvider.isFolded()).thenReturn(true); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, @@ -121,6 +122,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue(); mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true); mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true); + mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index 4281ee0f139f..ae38eb67c431 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -89,25 +89,27 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mClassifiers.add(mClassifierA); when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mFalsingDataProvider.isFolded()).thenReturn(true); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, mAccessibilityManager, false, mFakeFeatureFlags); mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true); + mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true); } @Test public void testA11yDisablesGesture() { - assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue(); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); - assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse(); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); } @Test public void testA11yDisablesTap() { - assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); + assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue(); when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); - assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); + assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse(); } @@ -179,4 +181,11 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { when(mFalsingDataProvider.isA11yAction()).thenReturn(true); assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse(); } + + @Test + public void testSkipUnfolded() { + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); + when(mFalsingDataProvider.isFolded()).thenReturn(false); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java index 5fa7214f07ff..94cf384267ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java @@ -16,6 +16,7 @@ package com.android.systemui.classifier; +import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.util.DisplayMetrics; import android.view.MotionEvent; @@ -38,6 +39,7 @@ public class ClassifierTest extends SysuiTestCase { private float mOffsetY = 0; @Mock private BatteryController mBatteryController; + private FoldStateListener mFoldStateListener = new FoldStateListener(mContext); private final DockManagerFake mDockManager = new DockManagerFake(); public void setup() { @@ -47,7 +49,8 @@ public class ClassifierTest extends SysuiTestCase { displayMetrics.ydpi = 100; displayMetrics.widthPixels = 1000; displayMetrics.heightPixels = 1000; - mDataProvider = new FalsingDataProvider(displayMetrics, mBatteryController, mDockManager); + mDataProvider = new FalsingDataProvider( + displayMetrics, mBatteryController, mFoldStateListener, mDockManager); } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java index d315c2da0703..c451a1e754c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; import android.view.MotionEvent; @@ -50,6 +51,8 @@ public class FalsingDataProviderTest extends ClassifierTest { private FalsingDataProvider mDataProvider; @Mock private BatteryController mBatteryController; + @Mock + private FoldStateListener mFoldStateListener; private final DockManagerFake mDockManager = new DockManagerFake(); @Before @@ -61,7 +64,8 @@ public class FalsingDataProviderTest extends ClassifierTest { displayMetrics.ydpi = 100; displayMetrics.widthPixels = 1000; displayMetrics.heightPixels = 1000; - mDataProvider = new FalsingDataProvider(displayMetrics, mBatteryController, mDockManager); + mDataProvider = new FalsingDataProvider( + displayMetrics, mBatteryController, mFoldStateListener, mDockManager); } @After @@ -316,4 +320,16 @@ public class FalsingDataProviderTest extends ClassifierTest { mDataProvider.onA11yAction(); assertThat(mDataProvider.isA11yAction()).isTrue(); } + + @Test + public void test_FoldedState_Folded() { + when(mFoldStateListener.getFolded()).thenReturn(true); + assertThat(mDataProvider.isFolded()).isTrue(); + } + + @Test + public void test_FoldedState_Unfolded() { + when(mFoldStateListener.getFolded()).thenReturn(false); + assertThat(mDataProvider.isFolded()).isFalse(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt index 0a81c38e7448..ebbe096b0da3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt @@ -269,6 +269,14 @@ class ControlsBindingControllerImplTest : SysuiTestCase() { } @Test + fun testBindServiceForPanel() { + controller.bindServiceForPanel(TEST_COMPONENT_NAME_1) + executor.runAllReady() + + verify(providers[0]).bindServiceForPanel() + } + + @Test fun testSubscribe() { val controlInfo1 = ControlInfo("id_1", "", "", DeviceTypes.TYPE_UNKNOWN) val controlInfo2 = ControlInfo("id_2", "", "", DeviceTypes.TYPE_UNKNOWN) diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index 1b34706bd220..25f471b0d3e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -919,6 +919,12 @@ class ControlsControllerImplTest : SysuiTestCase() { .getFile(ControlsFavoritePersistenceWrapper.FILE_NAME, context.user.identifier) assertThat(userStructure.file).isNotNull() } + + @Test + fun testBindForPanel() { + controller.bindComponentForPanel(TEST_COMPONENT) + verify(bindingController).bindServiceForPanel(TEST_COMPONENT) + } } private class DidRunRunnable() : Runnable { diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt index af3f24a1c58a..da548f7ccef2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt @@ -105,6 +105,22 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { } @Test + fun testBindForPanel() { + manager.bindServiceForPanel() + executor.runAllReady() + assertTrue(context.isBound(componentName)) + } + + @Test + fun testUnbindPanelIsUnbound() { + manager.bindServiceForPanel() + executor.runAllReady() + manager.unbindService() + executor.runAllReady() + assertFalse(context.isBound(componentName)) + } + + @Test fun testNullBinding() { val mockContext = mock(Context::class.java) lateinit var serviceConnection: ServiceConnection diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt index d172c9a2e630..edc6882e71c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt @@ -229,6 +229,15 @@ class ControlsUiControllerImplTest : SysuiTestCase() { } @Test + fun testPanelBindsForPanel() { + val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls")) + setUpPanel(panel) + + underTest.show(parent, {}, context) + verify(controlsController).bindComponentForPanel(panel.componentName) + } + + @Test fun testPanelCallsTaskViewFactoryCreate() { mockLayoutInflater() val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls")) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 122d7fdbfa47..f55b86686152 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -485,6 +486,38 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { assertTrue(mViewMediator.isShowingAndNotOccluded()); } + @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) + public void testDoKeyguardWhileInteractive_resets() { + mViewMediator.setShowingLocked(true); + when(mKeyguardStateController.isShowing()).thenReturn(true); + TestableLooper.get(this).processAllMessages(); + + when(mPowerManager.isInteractive()).thenReturn(true); + + mViewMediator.onSystemReady(); + TestableLooper.get(this).processAllMessages(); + + assertTrue(mViewMediator.isShowingAndNotOccluded()); + verify(mStatusBarKeyguardViewManager).reset(anyBoolean()); + } + + @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) + public void testDoKeyguardWhileNotInteractive_showsInsteadOfResetting() { + mViewMediator.setShowingLocked(true); + when(mKeyguardStateController.isShowing()).thenReturn(true); + TestableLooper.get(this).processAllMessages(); + + when(mPowerManager.isInteractive()).thenReturn(false); + + mViewMediator.onSystemReady(); + TestableLooper.get(this).processAllMessages(); + + assertTrue(mViewMediator.isShowingAndNotOccluded()); + verify(mStatusBarKeyguardViewManager, never()).reset(anyBoolean()); + } + private void createAndStartViewMediator() { mViewMediator = new KeyguardViewMediator( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java index f32d76bb601e..39a453da7f92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java @@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -51,7 +52,12 @@ public class WakefulnessLifecycleTest extends SysuiTestCase { public void setUp() throws Exception { mWallpaperManager = mock(IWallpaperManager.class); mWakefulness = - new WakefulnessLifecycle(mContext, mWallpaperManager, mock(DumpManager.class)); + new WakefulnessLifecycle( + mContext, + mWallpaperManager, + new FakeSystemClock(), + mock(DumpManager.class) + ); mWakefulnessObserver = mock(WakefulnessLifecycle.Observer.class); mWakefulness.addObserver(mWakefulnessObserver); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index f8f2a56d4808..32cec09c3580 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -168,6 +168,25 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { assertThat(wtfHandler.failed).isTrue() } + @Test + fun `Attempt to manually update transition after CANCELED state throws exception`() { + val uuid = + underTest.startTransition( + TransitionInfo( + ownerName = OWNER_NAME, + from = AOD, + to = LOCKSCREEN, + animator = null, + ) + ) + + checkNotNull(uuid).let { + underTest.updateTransition(it, 0.2f, TransitionState.CANCELED) + underTest.updateTransition(it, 0.5f, TransitionState.RUNNING) + } + assertThat(wtfHandler.failed).isTrue() + } + private fun listWithStep( step: BigDecimal, start: BigDecimal = BigDecimal.ZERO, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index b3cee2273012..a1b6d478d799 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -177,7 +178,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setDreamingWithOverlay(false) // AND occluded has stopped keyguardRepository.setKeyguardOccluded(false) - runCurrent() + advanceUntilIdle() val info = withArgCaptor<TransitionInfo> { @@ -506,7 +507,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { withArgCaptor<TransitionInfo> { verify(mockTransitionRepository).startTransition(capture()) } - // THEN a transition to DOZING should occur + // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") assertThat(info.from).isEqualTo(KeyguardState.GONE) assertThat(info.to).isEqualTo(KeyguardState.AOD) @@ -515,6 +516,49 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { coroutineContext.cancelChildren() } + @Test + fun `GONE to DREAMING`() = + testScope.runTest { + // GIVEN a device that is not dreaming or dozing + keyguardRepository.setDreamingWithOverlay(false) + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + runCurrent() + + // GIVEN a prior transition has run to GONE + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + reset(mockTransitionRepository) + + // WHEN the device begins to dream + keyguardRepository.setDreamingWithOverlay(true) + advanceUntilIdle() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to DREAMING should occur + assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.GONE) + assertThat(info.to).isEqualTo(KeyguardState.DREAMING) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + private fun startingToWake() = WakefulnessModel( WakefulnessState.STARTING_TO_WAKE, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt new file mode 100644 index 000000000000..7fa204bb980b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt @@ -0,0 +1,141 @@ +/* + * 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 androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.AnimationParams +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA +import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class GoneToDreamingTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: GoneToDreamingTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = KeyguardTransitionInteractor(repository) + underTest = GoneToDreamingTransitionViewModel(interactor) + } + + @Test + fun lockscreenFadeOut() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) + + // Should start running here... + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + // ...up to here + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + // Only three values should be present, since the dream overlay runs for a small + // fraction + // of the overall animation time + assertThat(values.size).isEqualTo(3) + assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA)) + assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA)) + assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA)) + + job.cancel() + } + + @Test + fun lockscreenTranslationY() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val pixels = 100 + val job = + underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + + // Should start running here... + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.5f)) + // ...up to here + repository.sendTransitionStep(step(1f)) + // And a final reset event on CANCEL + repository.sendTransitionStep(step(0.8f, TransitionState.CANCELED)) + + assertThat(values.size).isEqualTo(4) + assertThat(values[0]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[1]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0.3f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[2]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0.5f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[3]).isEqualTo(0f) + job.cancel() + } + + private fun animValue(stepValue: Float, params: AnimationParams): Float { + val totalDuration = TO_DREAMING_DURATION + val startValue = (params.startTime / totalDuration).toFloat() + + val multiplier = (totalDuration / params.duration).toFloat() + return (stepValue - startValue) * multiplier + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.DREAMING, + value = value, + transitionState = state, + ownerName = "GoneToDreamingTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index 739059126b04..539fc2c1548e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -67,8 +67,7 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { repository.sendTransitionStep(step(1f)) // Only three values should be present, since the dream overlay runs for a small - // fraction - // of the overall animation time + // fraction of the overall animation time assertThat(values.size).isEqualTo(3) assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA)) assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA)) @@ -92,8 +91,10 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { repository.sendTransitionStep(step(0.5f)) // ...up to here repository.sendTransitionStep(step(1f)) + // And a final reset event on FINISHED + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) - assertThat(values.size).isEqualTo(3) + assertThat(values.size).isEqualTo(4) assertThat(values[0]) .isEqualTo( EMPHASIZED_ACCELERATE.getInterpolation( @@ -112,6 +113,8 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { animValue(0.5f, LOCKSCREEN_TRANSLATION_Y) ) * pixels ) + assertThat(values[3]).isEqualTo(0f) + job.cancel() } @@ -123,12 +126,15 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { return (stepValue - startValue) * multiplier } - private fun step(value: Float): TransitionStep { + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { return TransitionStep( from = KeyguardState.LOCKSCREEN, to = KeyguardState.DREAMING, value = value, - transitionState = TransitionState.RUNNING, + transitionState = state, ownerName = "LockscreenToDreamingTransitionViewModelTest" ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java index 4d2d0f05b76a..c0639f34484c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java @@ -79,7 +79,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { USER_ID, true, APP, null, ARTIST, TITLE, null, new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null, MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, - InstanceId.fakeInstanceId(-1), -1); + InstanceId.fakeInstanceId(-1), -1, false); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false); } 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 52b694fac07c..c24c8c7f7cf6 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 @@ -228,6 +228,7 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList) whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false) + whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true) whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId()) } @@ -300,6 +301,60 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testLoadMetadata_withExplicitIndicator() { + val metadata = + MediaMetadata.Builder().run { + putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) + putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) + putLong( + MediaConstants.METADATA_KEY_IS_EXPLICIT, + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + ) + build() + } + whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller) + whenever(controller.metadata).thenReturn(metadata) + + mediaDataManager.addListener(listener) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value!!.isExplicit).isTrue() + } + + @Test + fun testOnMetaDataLoaded_withoutExplicitIndicator() { + whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller) + whenever(controller.metadata).thenReturn(metadataBuilder.build()) + + mediaDataManager.addListener(listener) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value!!.isExplicit).isFalse() + } + + @Test fun testOnMetaDataLoaded_callsListener() { addNotificationAndLoad() verify(logger) @@ -603,6 +658,53 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testAddResumptionControls_withExplicitIndicator() { + val bundle = Bundle() + // WHEN resumption controls are added with explicit indicator + bundle.putLong( + MediaConstants.METADATA_KEY_IS_EXPLICIT, + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + ) + val desc = + MediaDescription.Builder().run { + setTitle(SESSION_TITLE) + setExtras(bundle) + build() + } + val currentTime = clock.elapsedRealtime() + mediaDataManager.addResumptionControls( + USER_ID, + desc, + Runnable {}, + session.sessionToken, + APP_NAME, + pendingIntent, + PACKAGE_NAME + ) + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + // THEN the media data indicates that it is for resumption + verify(listener) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + val data = mediaDataCaptor.value + assertThat(data.resumption).isTrue() + assertThat(data.song).isEqualTo(SESSION_TITLE) + assertThat(data.app).isEqualTo(APP_NAME) + assertThat(data.actions).hasSize(1) + assertThat(data.semanticActions!!.playOrPause).isNotNull() + assertThat(data.lastActive).isAtLeast(currentTime) + assertThat(data.isExplicit).isTrue() + verify(logger).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) + } + + @Test fun testResumptionDisabled_dismissesResumeControls() { // WHEN there are resume controls and resumption is switched off val desc = diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index 039dd4d92eb4..e4e95e580a7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt @@ -20,6 +20,7 @@ import android.app.PendingIntent import android.content.res.Configuration import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.util.MathUtils.abs import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase @@ -31,14 +32,11 @@ import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA import com.android.systemui.media.controls.pipeline.MediaDataManager -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.ANIMATION_BASE_DURATION -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.PAGINATION_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager +import com.android.systemui.qs.PageIndicator import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider import com.android.systemui.statusbar.policy.ConfigurationController @@ -56,6 +54,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.floatThat import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -86,6 +85,8 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Mock lateinit var debugLogger: MediaCarouselControllerLogger @Mock lateinit var mediaViewController: MediaViewController @Mock lateinit var smartspaceMediaData: SmartspaceMediaData + @Mock lateinit var mediaCarousel: MediaScrollView + @Mock lateinit var pageIndicator: PageIndicator @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener> @Captor lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener> @@ -647,25 +648,22 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() { val delta = 0.0001F - val paginationSquishMiddle = - TRANSFORM_BEZIER.getInterpolation( - (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION - ) - val paginationSquishEnd = - TRANSFORM_BEZIER.getInterpolation( - (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION - ) + mediaCarouselController.mediaCarousel = mediaCarousel + mediaCarouselController.pageIndicator = pageIndicator + whenever(mediaCarousel.measuredHeight).thenReturn(100) + whenever(pageIndicator.translationY).thenReturn(80F) + whenever(pageIndicator.height).thenReturn(10) whenever(mediaHostStatesManager.mediaHostStates) .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState)) whenever(mediaHostState.visible).thenReturn(true) mediaCarouselController.currentEndLocation = LOCATION_QS - whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle) + whenever(mediaHostState.squishFraction).thenReturn(0.938F) mediaCarouselController.updatePageIndicatorAlpha() - assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta) + verify(pageIndicator).alpha = floatThat { abs(it - 0.5F) < delta } - whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd) + whenever(mediaHostState.squishFraction).thenReturn(1.0F) mediaCarouselController.updatePageIndicatorAlpha() - assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta) + verify(pageIndicator).alpha = floatThat { abs(it - 1.0F) < delta } } @Ignore("b/253229241") diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt index b65f5cb51aaf..cfb19fc32bec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt @@ -54,6 +54,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.LiveData import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId +import com.android.internal.widget.CachingIconView import com.android.systemui.ActivityIntentHelper import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -154,6 +155,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var albumView: ImageView private lateinit var titleText: TextView private lateinit var artistText: TextView + private lateinit var explicitIndicator: CachingIconView private lateinit var seamless: ViewGroup private lateinit var seamlessButton: View @Mock private lateinit var seamlessBackground: RippleDrawable @@ -216,6 +218,7 @@ public class MediaControlPanelTest : SysuiTestCase() { this.set(Flags.UMO_SURFACE_RIPPLE, false) this.set(Flags.UMO_TURBULENCE_NOISE, false) this.set(Flags.MEDIA_FALSING_PENALTY, true) + this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true) } @JvmField @Rule val mockito = MockitoJUnit.rule() @@ -350,6 +353,7 @@ public class MediaControlPanelTest : SysuiTestCase() { appIcon = ImageView(context) titleText = TextView(context) artistText = TextView(context) + explicitIndicator = CachingIconView(context).also { it.id = R.id.media_explicit_indicator } seamless = FrameLayout(context) seamless.foreground = seamlessBackground seamlessButton = View(context) @@ -396,6 +400,7 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(albumView.foreground).thenReturn(mock(Drawable::class.java)) whenever(viewHolder.titleText).thenReturn(titleText) whenever(viewHolder.artistText).thenReturn(artistText) + whenever(viewHolder.explicitIndicator).thenReturn(explicitIndicator) whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java)) whenever(viewHolder.seamless).thenReturn(seamless) whenever(viewHolder.seamlessButton).thenReturn(seamlessButton) @@ -1019,6 +1024,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindText() { + useRealConstraintSets() player.attachPlayer(viewHolder) player.bindPlayer(mediaData, PACKAGE) @@ -1036,6 +1042,8 @@ public class MediaControlPanelTest : SysuiTestCase() { handler.onAnimationEnd(mockAnimator) assertThat(titleText.getText()).isEqualTo(TITLE) assertThat(artistText.getText()).isEqualTo(ARTIST) + assertThat(expandedSet.getVisibility(explicitIndicator.id)).isEqualTo(ConstraintSet.GONE) + assertThat(collapsedSet.getVisibility(explicitIndicator.id)).isEqualTo(ConstraintSet.GONE) // Rebinding should not trigger animation player.bindPlayer(mediaData, PACKAGE) @@ -1043,6 +1051,36 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test + fun bindTextWithExplicitIndicator() { + useRealConstraintSets() + val mediaDataWitExp = mediaData.copy(isExplicit = true) + player.attachPlayer(viewHolder) + player.bindPlayer(mediaDataWitExp, PACKAGE) + + // Capture animation handler + val captor = argumentCaptor<Animator.AnimatorListener>() + verify(mockAnimator, times(2)).addListener(captor.capture()) + val handler = captor.value + + // Validate text views unchanged but animation started + assertThat(titleText.getText()).isEqualTo("") + assertThat(artistText.getText()).isEqualTo("") + verify(mockAnimator, times(1)).start() + + // Binding only after animator runs + handler.onAnimationEnd(mockAnimator) + assertThat(titleText.getText()).isEqualTo(TITLE) + assertThat(artistText.getText()).isEqualTo(ARTIST) + assertThat(expandedSet.getVisibility(explicitIndicator.id)).isEqualTo(ConstraintSet.VISIBLE) + assertThat(collapsedSet.getVisibility(explicitIndicator.id)) + .isEqualTo(ConstraintSet.VISIBLE) + + // Rebinding should not trigger animation + player.bindPlayer(mediaData, PACKAGE) + verify(mockAnimator, times(3)).start() + } + + @Test fun bindTextInterrupted() { val data0 = mediaData.copy(artist = "ARTIST_0") val data1 = mediaData.copy(artist = "ARTIST_1") diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt index 35b0eb678441..4ed6d7cf6bd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt @@ -22,13 +22,6 @@ import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.ANIMATION_BASE_DURATION -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.CONTROLS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER import com.android.systemui.util.animation.MeasurementInput import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.animation.TransitionViewState @@ -60,9 +53,10 @@ class MediaViewControllerTest : SysuiTestCase() { @Mock private lateinit var controlWidgetState: WidgetState @Mock private lateinit var bgWidgetState: WidgetState @Mock private lateinit var mediaTitleWidgetState: WidgetState + @Mock private lateinit var mediaSubTitleWidgetState: WidgetState @Mock private lateinit var mediaContainerWidgetState: WidgetState - val delta = 0.0001F + val delta = 0.1F private lateinit var mediaViewController: MediaViewController @@ -76,10 +70,11 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() { mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) - player.measureState = TransitionViewState().apply { - this.height = 100 - this.measureHeight = 100 - } + player.measureState = + TransitionViewState().apply { + this.height = 100 + this.measureHeight = 100 + } mediaHostStateHolder.expansion = 1f val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) @@ -128,29 +123,21 @@ class MediaViewControllerTest : SysuiTestCase() { R.id.header_artist to detailWidgetState ) ) - - val detailSquishMiddle = - TRANSFORM_BEZIER.getInterpolation( - (DETAILS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, detailSquishMiddle) + whenever(mockCopiedState.measureHeight).thenReturn(200) + // detail widgets occupy [90, 100] + whenever(detailWidgetState.y).thenReturn(90F) + whenever(detailWidgetState.height).thenReturn(10) + // control widgets occupy [150, 170] + whenever(controlWidgetState.y).thenReturn(150F) + whenever(controlWidgetState.height).thenReturn(20) + // in current beizer, when the progress reach 0.38, the result will be 0.5 + mediaViewController.squishViewState(mockViewState, 119F / 200F) verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } - - val detailSquishEnd = - TRANSFORM_BEZIER.getInterpolation((DETAILS_DELAY + DURATION) / ANIMATION_BASE_DURATION) - mediaViewController.squishViewState(mockViewState, detailSquishEnd) + mediaViewController.squishViewState(mockViewState, 150F / 200F) verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } - - val controlSquishMiddle = - TRANSFORM_BEZIER.getInterpolation( - (CONTROLS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, controlSquishMiddle) + mediaViewController.squishViewState(mockViewState, 181.4F / 200F) verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } - - val controlSquishEnd = - TRANSFORM_BEZIER.getInterpolation((CONTROLS_DELAY + DURATION) / ANIMATION_BASE_DURATION) - mediaViewController.squishViewState(mockViewState, controlSquishEnd) + mediaViewController.squishViewState(mockViewState, 200F / 200F) verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } } @@ -161,36 +148,33 @@ class MediaViewControllerTest : SysuiTestCase() { .thenReturn( mutableMapOf( R.id.media_title1 to mediaTitleWidgetState, + R.id.media_subtitle1 to mediaSubTitleWidgetState, R.id.media_cover1_container to mediaContainerWidgetState ) ) - - val containerSquishMiddle = - TRANSFORM_BEZIER.getInterpolation( - (MEDIACONTAINERS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, containerSquishMiddle) + whenever(mockCopiedState.measureHeight).thenReturn(360) + // media container widgets occupy [20, 300] + whenever(mediaContainerWidgetState.y).thenReturn(20F) + whenever(mediaContainerWidgetState.height).thenReturn(280) + // media title widgets occupy [320, 330] + whenever(mediaTitleWidgetState.y).thenReturn(320F) + whenever(mediaTitleWidgetState.height).thenReturn(10) + // media subtitle widgets occupy [340, 350] + whenever(mediaSubTitleWidgetState.y).thenReturn(340F) + whenever(mediaSubTitleWidgetState.height).thenReturn(10) + + // in current beizer, when the progress reach 0.38, the result will be 0.5 + mediaViewController.squishViewState(mockViewState, 307.6F / 360F) verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } - - val containerSquishEnd = - TRANSFORM_BEZIER.getInterpolation( - (MEDIACONTAINERS_DELAY + DURATION) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, containerSquishEnd) + mediaViewController.squishViewState(mockViewState, 320F / 360F) verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } - - val titleSquishMiddle = - TRANSFORM_BEZIER.getInterpolation( - (MEDIATITLES_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, titleSquishMiddle) + // media title and media subtitle are in same widget group, should be calculate together and + // have same alpha + mediaViewController.squishViewState(mockViewState, 353.8F / 360F) verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } - - val titleSquishEnd = - TRANSFORM_BEZIER.getInterpolation( - (MEDIATITLES_DELAY + DURATION) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, titleSquishEnd) + verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } + mediaViewController.squishViewState(mockViewState, 360F / 360F) verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } + verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index 9bf27a26a682..8b0342eda633 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -51,6 +51,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.LightBarController; @@ -102,10 +103,10 @@ public class NavigationBarControllerTest extends SysuiTestCase { mock(NavBarHelper.class), mTaskbarDelegate, mNavigationBarFactory, - mock(StatusBarKeyguardViewManager.class), mock(DumpManager.class), mock(AutoHideController.class), mock(LightBarController.class), + TaskStackChangeListeners.getTestInstance(), Optional.of(mock(Pip.class)), Optional.of(mock(BackAnimation.class)), mock(FeatureFlags.class))); diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 80adbf025e0b..2ad865e6ef11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -28,6 +28,7 @@ import static android.view.WindowInsets.Type.ime; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS; import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -44,6 +45,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; import android.hardware.display.DisplayManagerGlobal; @@ -90,6 +92,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.ShadeController; import com.android.systemui.shared.rotation.RotationButtonController; +import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; @@ -203,6 +206,8 @@ public class NavigationBarTest extends SysuiTestCase { private ViewRootImpl mViewRootImpl; private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake(); + private TaskStackChangeListeners mTaskStackChangeListeners = + TaskStackChangeListeners.getTestInstance(); @Rule public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck(); @@ -437,6 +442,14 @@ public class NavigationBarTest extends SysuiTestCase { verify(mNavBarHelper, times(1)).getCurrentSysuiState(); } + @Test + public void testScreenPinningEnabled_updatesSysuiState() { + mNavigationBar.init(); + mTaskStackChangeListeners.getListenerImpl().onLockTaskModeChanged( + ActivityManager.LOCK_TASK_MODE_PINNED); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_SCREEN_PINNING), eq(true)); + } + private NavigationBar createNavBar(Context context) { DeviceProvisionedController deviceProvisionedController = mock(DeviceProvisionedController.class); @@ -481,7 +494,8 @@ public class NavigationBarTest extends SysuiTestCase { mEdgeBackGestureHandler, Optional.of(mock(BackAnimation.class)), mUserContextProvider, - mWakefulnessLifecycle)); + mWakefulnessLifecycle, + mTaskStackChangeListeners)); } private void processAllMessages() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt index 1742c6994246..537dfb821fef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt @@ -1,11 +1,14 @@ package com.android.systemui.navigationbar +import android.app.ActivityManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.model.SysUiState import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler import com.android.systemui.recents.OverviewProxyService +import com.android.systemui.shared.system.QuickStepContract +import com.android.systemui.shared.system.TaskStackChangeListeners import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.phone.AutoHideController import com.android.systemui.statusbar.phone.LightBarController @@ -14,6 +17,7 @@ import com.android.wm.shell.back.BackAnimation import com.android.wm.shell.pip.Pip import org.junit.Before import org.junit.Test +import org.mockito.ArgumentMatchers import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.any @@ -30,6 +34,7 @@ class TaskbarDelegateTest : SysuiTestCase() { val MODE_GESTURE = 0; val MODE_THREE_BUTTON = 1; + private lateinit var mTaskStackChangeListeners: TaskStackChangeListeners private lateinit var mTaskbarDelegate: TaskbarDelegate @Mock lateinit var mEdgeBackGestureHandlerFactory : EdgeBackGestureHandler.Factory @@ -69,11 +74,12 @@ class TaskbarDelegateTest : SysuiTestCase() { `when`(mLightBarControllerFactory.create(any())).thenReturn(mLightBarTransitionController) `when`(mNavBarHelper.currentSysuiState).thenReturn(mCurrentSysUiState) `when`(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState) + mTaskStackChangeListeners = TaskStackChangeListeners.getTestInstance() mTaskbarDelegate = TaskbarDelegate(context, mEdgeBackGestureHandlerFactory, mLightBarControllerFactory) mTaskbarDelegate.setDependencies(mCommandQueue, mOverviewProxyService, mNavBarHelper, mNavigationModeController, mSysUiState, mDumpManager, mAutoHideController, - mLightBarController, mOptionalPip, mBackAnimation) + mLightBarController, mOptionalPip, mBackAnimation, mTaskStackChangeListeners) } @Test @@ -90,4 +96,15 @@ class TaskbarDelegateTest : SysuiTestCase() { mTaskbarDelegate.init(DISPLAY_ID) verify(mEdgeBackGestureHandler, times(1)).onNavigationModeChanged(MODE_GESTURE) } + + @Test + fun screenPinningEnabled_updatesSysuiState() { + mTaskbarDelegate.init(DISPLAY_ID) + mTaskStackChangeListeners.listenerImpl.onLockTaskModeChanged( + ActivityManager.LOCK_TASK_MODE_PINNED) + verify(mSysUiState, times(1)).setFlag( + ArgumentMatchers.eq(QuickStepContract.SYSUI_STATE_SCREEN_PINNING), + ArgumentMatchers.eq(true) + ) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index fc90c1add5e9..8440455127bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -24,7 +24,7 @@ import android.os.UserManager import android.test.suitebuilder.annotation.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase -import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION +import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq @@ -50,7 +50,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) internal class NoteTaskControllerTest : SysuiTestCase() { - private val notesIntent = Intent(NOTES_ACTION) + private val notesIntent = Intent(ACTION_CREATE_NOTE) @Mock lateinit var context: Context @Mock lateinit var packageManager: PackageManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index 538131a4dd73..010ac5bbb2d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -106,7 +106,9 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { // region handleSystemKey @Test fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() { - createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) + createNoteTaskInitializer() + .callbacks + .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT) verify(noteTaskController).showNoteTask() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt index dd2cc2ffc9db..bbe60f4ba493 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt @@ -23,11 +23,10 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.ResolveInfoFlags import android.content.pm.ResolveInfo -import android.content.pm.ServiceInfo import android.test.suitebuilder.annotation.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase -import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION +import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -58,19 +57,13 @@ internal class NoteTaskIntentResolverTest : SysuiTestCase() { } private fun createResolveInfo( - packageName: String = "PackageName", - activityInfo: ActivityInfo? = null, + activityInfo: ActivityInfo? = createActivityInfo(), ): ResolveInfo { - return ResolveInfo().apply { - serviceInfo = - ServiceInfo().apply { - applicationInfo = ApplicationInfo().apply { this.packageName = packageName } - } - this.activityInfo = activityInfo - } + return ResolveInfo().apply { this.activityInfo = activityInfo } } private fun createActivityInfo( + packageName: String = "PackageName", name: String? = "ActivityName", exported: Boolean = true, enabled: Boolean = true, @@ -87,6 +80,7 @@ internal class NoteTaskIntentResolverTest : SysuiTestCase() { if (turnScreenOn) { flags = flags or ActivityInfo.FLAG_TURN_SCREEN_ON } + this.applicationInfo = ApplicationInfo().apply { this.packageName = packageName } } } @@ -107,7 +101,8 @@ internal class NoteTaskIntentResolverTest : SysuiTestCase() { val actual = resolver.resolveIntent() val expected = - Intent(NOTES_ACTION) + Intent(ACTION_CREATE_NOTE) + .setPackage("PackageName") .setComponent(ComponentName("PackageName", "ActivityName")) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) // Compares the string representation of both intents, as they are different instances. @@ -204,7 +199,9 @@ internal class NoteTaskIntentResolverTest : SysuiTestCase() { @Test fun resolveIntent_packageNameIsBlank_shouldReturnNull() { - givenQueryIntentActivities { listOf(createResolveInfo(packageName = "")) } + givenQueryIntentActivities { + listOf(createResolveInfo(createActivityInfo(packageName = ""))) + } val actual = resolver.resolveIntent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt index ca3182affcc1..3281fa9bd8a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt @@ -28,7 +28,6 @@ import com.android.systemui.qs.tiles.BatterySaverTile import com.android.systemui.qs.tiles.BluetoothTile import com.android.systemui.qs.tiles.CameraToggleTile import com.android.systemui.qs.tiles.CastTile -import com.android.systemui.qs.tiles.CellularTile import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile import com.android.systemui.qs.tiles.DataSaverTile @@ -49,7 +48,6 @@ import com.android.systemui.qs.tiles.ReduceBrightColorsTile import com.android.systemui.qs.tiles.RotationLockTile import com.android.systemui.qs.tiles.ScreenRecordTile import com.android.systemui.qs.tiles.UiModeNightTile -import com.android.systemui.qs.tiles.WifiTile import com.android.systemui.qs.tiles.WorkModeTile import com.android.systemui.util.leak.GarbageMonitor import com.google.common.truth.Truth.assertThat @@ -63,10 +61,8 @@ import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever private val specMap = mapOf( - "wifi" to WifiTile::class.java, "internet" to InternetTile::class.java, "bt" to BluetoothTile::class.java, - "cell" to CellularTile::class.java, "dnd" to DndTile::class.java, "inversion" to ColorInversionTile::class.java, "airplane" to AirplaneModeTile::class.java, @@ -102,10 +98,8 @@ class QSFactoryImplTest : SysuiTestCase() { @Mock(answer = Answers.RETURNS_SELF) private lateinit var customTileBuilder: CustomTile.Builder @Mock private lateinit var customTile: CustomTile - @Mock private lateinit var wifiTile: WifiTile @Mock private lateinit var internetTile: InternetTile @Mock private lateinit var bluetoothTile: BluetoothTile - @Mock private lateinit var cellularTile: CellularTile @Mock private lateinit var dndTile: DndTile @Mock private lateinit var colorInversionTile: ColorInversionTile @Mock private lateinit var airplaneTile: AirplaneModeTile @@ -146,10 +140,8 @@ class QSFactoryImplTest : SysuiTestCase() { factory = QSFactoryImpl( { qsHost }, { customTileBuilder }, - { wifiTile }, { internetTile }, { bluetoothTile }, - { cellularTile }, { dndTile }, { colorInversionTile }, { airplaneTile }, diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt new file mode 100644 index 000000000000..3710281499b3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt @@ -0,0 +1,100 @@ +package com.android.systemui.settings + +import android.content.Context +import android.content.Intent +import android.content.pm.UserInfo +import android.os.Handler +import android.os.UserHandle +import android.os.UserManager +import androidx.concurrent.futures.DirectExecutor +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.capture +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Executor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(Parameterized::class) +class UserTrackerImplReceiveTest : SysuiTestCase() { + + companion object { + + @JvmStatic + @Parameterized.Parameters + fun data(): Iterable<String> = + listOf( + Intent.ACTION_USER_INFO_CHANGED, + Intent.ACTION_MANAGED_PROFILE_AVAILABLE, + Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE, + Intent.ACTION_MANAGED_PROFILE_ADDED, + Intent.ACTION_MANAGED_PROFILE_REMOVED, + Intent.ACTION_MANAGED_PROFILE_UNLOCKED + ) + } + + private val executor: Executor = DirectExecutor.INSTANCE + + @Mock private lateinit var context: Context + @Mock private lateinit var userManager: UserManager + @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager + @Mock(stubOnly = true) private lateinit var handler: Handler + + @Parameterized.Parameter lateinit var intentAction: String + @Mock private lateinit var callback: UserTracker.Callback + @Captor private lateinit var captor: ArgumentCaptor<List<UserInfo>> + + private lateinit var tracker: UserTrackerImpl + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(context.user).thenReturn(UserHandle.SYSTEM) + `when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context) + + tracker = UserTrackerImpl(context, userManager, dumpManager, handler) + } + + @Test + fun `calls callback and updates profiles when an intent received`() { + tracker.initialize(0) + tracker.addCallback(callback, executor) + val profileID = tracker.userId + 10 + + `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation -> + val id = invocation.getArgument<Int>(0) + val info = UserInfo(id, "", UserInfo.FLAG_FULL) + val infoProfile = + UserInfo( + id + 10, + "", + "", + UserInfo.FLAG_MANAGED_PROFILE, + UserManager.USER_TYPE_PROFILE_MANAGED + ) + infoProfile.profileGroupId = id + listOf(info, infoProfile) + } + + tracker.onReceive(context, Intent(intentAction)) + + verify(callback, times(0)).onUserChanged(anyInt(), any()) + verify(callback, times(1)).onProfilesChanged(capture(captor)) + assertThat(captor.value.map { it.id }).containsExactly(0, profileID) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt index 52462c7186d4..e65bbb1bea08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt @@ -124,6 +124,16 @@ class UserTrackerImplTest : SysuiTestCase() { verify(context).registerReceiverForAllUsers( eq(tracker), capture(captor), isNull(), eq(handler)) + with(captor.value) { + assertThat(countActions()).isEqualTo(7) + assertThat(hasAction(Intent.ACTION_USER_SWITCHED)).isTrue() + assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue() + assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue() + assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue() + assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue() + assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue() + assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue() + } } @Test @@ -280,37 +290,6 @@ class UserTrackerImplTest : SysuiTestCase() { } @Test - fun testCallbackCalledOnProfileChanged() { - tracker.initialize(0) - val callback = TestCallback() - tracker.addCallback(callback, executor) - val profileID = tracker.userId + 10 - - `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation -> - val id = invocation.getArgument<Int>(0) - val info = UserInfo(id, "", UserInfo.FLAG_FULL) - val infoProfile = UserInfo( - id + 10, - "", - "", - UserInfo.FLAG_MANAGED_PROFILE, - UserManager.USER_TYPE_PROFILE_MANAGED - ) - infoProfile.profileGroupId = id - listOf(info, infoProfile) - } - - val intent = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) - .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID)) - - tracker.onReceive(context, intent) - - assertThat(callback.calledOnUserChanged).isEqualTo(0) - assertThat(callback.calledOnProfilesChanged).isEqualTo(1) - assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID) - } - - @Test fun testCallbackCalledOnUserInfoChanged() { tracker.initialize(0) val callback = TestCallback() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt index 88651c1292c3..f802a5e09228 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade import android.testing.AndroidTestingRunner +import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START @@ -92,12 +93,12 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f) assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID) - assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0f) + assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0.5f) assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd) .isEqualTo(PARENT_ID) assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias) - .isEqualTo(1f) + .isEqualTo(0.5f) assertThat(getConstraint(R.id.privacy_container).layout.endToEnd) .isEqualTo(R.id.end_guide) @@ -331,10 +332,8 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { val views = mapOf( R.id.clock to "clock", R.id.date to "date", - R.id.statusIcons to "icons", R.id.privacy_container to "privacy", R.id.carrier_group to "carriers", - R.id.batteryRemainingIcon to "battery", ) views.forEach { (id, name) -> assertWithMessage("$name has 0 height in qqs") @@ -352,11 +351,8 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() { val views = mapOf( R.id.clock to "clock", - R.id.date to "date", - R.id.statusIcons to "icons", R.id.privacy_container to "privacy", R.id.carrier_group to "carriers", - R.id.batteryRemainingIcon to "battery", ) views.forEach { (id, name) -> expect.withMessage("$name changes height") @@ -369,8 +365,8 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { } private fun Int.fromConstraint() = when (this) { - -1 -> "MATCH_PARENT" - -2 -> "WRAP_CONTENT" + ViewGroup.LayoutParams.MATCH_PARENT -> "MATCH_PARENT" + ViewGroup.LayoutParams.WRAP_CONTENT -> "WRAP_CONTENT" else -> toString() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index d0b42ae8ec56..0f3d4a8ca59a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -107,6 +107,7 @@ import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteracto import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; @@ -298,6 +299,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel; @Mock private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel; @Mock private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; + @Mock private GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock private CoroutineDispatcher mMainDispatcher; @@ -522,6 +524,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mDreamingToLockscreenTransitionViewModel, mOccludedToLockscreenTransitionViewModel, mLockscreenToDreamingTransitionViewModel, + mGoneToDreamingTransitionViewModel, mLockscreenToOccludedTransitionViewModel, mMainDispatcher, mKeyguardTransitionInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 08a9c9664ae0..526dc8d150fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -46,11 +46,14 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -68,6 +71,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import java.util.List; + @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest @@ -91,13 +96,21 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private ShadeExpansionStateManager mShadeExpansionStateManager; @Mock private ShadeWindowLogger mShadeWindowLogger; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; + @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener; private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; - + private float mPreferredRefreshRate = -1; @Before public void setUp() { MockitoAnnotations.initMocks(this); + // Preferred refresh rate is equal to the first displayMode's refresh rate + mPreferredRefreshRate = mContext.getDisplay().getSupportedModes()[0].getRefreshRate(); + overrideResource( + R.integer.config_keyguardRefreshRate, + (int) mPreferredRefreshRate + ); + when(mDozeParameters.getAlwaysOn()).thenReturn(true); when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); @@ -117,6 +130,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mNotificationShadeWindowController.attach(); verify(mWindowManager).addView(eq(mNotificationShadeWindowView), any()); + verify(mStatusBarStateController).addCallback(mStateListener.capture(), anyInt()); } @Test @@ -334,4 +348,59 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { assertThat(mLayoutParameters.getValue().screenOrientation) .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); } + + @Test + public void udfpsEnrolled_minAndMaxRefreshRateSetToPreferredRefreshRate() { + // GIVEN udfps is enrolled + when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true); + + // WHEN keyguard is showing + setKeyguardShowing(); + + // THEN min and max refresh rate is set to the preferredRefreshRate + verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture()); + final List<WindowManager.LayoutParams> lpList = mLayoutParameters.getAllValues(); + final WindowManager.LayoutParams lp = lpList.get(lpList.size() - 1); + assertThat(lp.preferredMaxDisplayRefreshRate).isEqualTo(mPreferredRefreshRate); + assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(mPreferredRefreshRate); + } + + @Test + public void udfpsNotEnrolled_refreshRateUnset() { + // GIVEN udfps is NOT enrolled + when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false); + + // WHEN keyguard is showing + setKeyguardShowing(); + + // THEN min and max refresh rate aren't set (set to 0) + verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture()); + final List<WindowManager.LayoutParams> lpList = mLayoutParameters.getAllValues(); + final WindowManager.LayoutParams lp = lpList.get(lpList.size() - 1); + assertThat(lp.preferredMaxDisplayRefreshRate).isEqualTo(0); + assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(0); + } + + @Test + public void keyguardNotShowing_refreshRateUnset() { + // GIVEN UDFPS is enrolled + when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true); + + // WHEN keyguard is NOT showing + mNotificationShadeWindowController.setKeyguardShowing(false); + + // THEN min and max refresh rate aren't set (set to 0) + verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture()); + final List<WindowManager.LayoutParams> lpList = mLayoutParameters.getAllValues(); + final WindowManager.LayoutParams lp = lpList.get(lpList.size() - 1); + assertThat(lp.preferredMaxDisplayRefreshRate).isEqualTo(0); + assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(0); + } + + private void setKeyguardShowing() { + mNotificationShadeWindowController.setKeyguardShowing(true); + mNotificationShadeWindowController.setKeyguardGoingAway(false); + mNotificationShadeWindowController.setKeyguardFadingAway(false); + mStateListener.getValue().onStateChanged(StatusBarState.KEYGUARD); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 4ccbc6d45e63..091bb5455d93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNotNull; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doReturn; @@ -74,6 +75,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import org.mockito.stubbing.Answer; import java.util.Collections; import java.util.List; @@ -115,8 +117,10 @@ public class AutoTileManagerTest extends SysuiTestCase { @Spy private PackageManager mPackageManager; private final boolean mIsReduceBrightColorsAvailable = true; - private AutoTileManager mAutoTileManager; + private AutoTileManager mAutoTileManager; // under test + private SecureSettings mSecureSettings; + private ManagedProfileController.Callback mManagedProfileCallback; @Before public void setUp() throws Exception { @@ -303,7 +307,7 @@ public class AutoTileManagerTest extends SysuiTestCase { InOrder inOrderManagedProfile = inOrder(mManagedProfileController); inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any()); - inOrderManagedProfile.verify(mManagedProfileController, never()).addCallback(any()); + inOrderManagedProfile.verify(mManagedProfileController).addCallback(any()); if (ColorDisplayManager.isNightDisplayAvailable(mContext)) { InOrder inOrderNightDisplay = inOrder(mNightDisplayListener); @@ -504,6 +508,40 @@ public class AutoTileManagerTest extends SysuiTestCase { } @Test + public void managedProfileAdded_tileAdded() { + when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false); + mAutoTileManager = createAutoTileManager(mContext); + Mockito.doAnswer((Answer<Object>) invocation -> { + mManagedProfileCallback = invocation.getArgument(0); + return null; + }).when(mManagedProfileController).addCallback(any()); + mAutoTileManager.init(); + when(mManagedProfileController.hasActiveProfile()).thenReturn(true); + + mManagedProfileCallback.onManagedProfileChanged(); + + verify(mQsTileHost, times(1)).addTile(eq("work")); + verify(mAutoAddTracker, times(1)).setTileAdded(eq("work")); + } + + @Test + public void managedProfileRemoved_tileRemoved() { + when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(true); + mAutoTileManager = createAutoTileManager(mContext); + Mockito.doAnswer((Answer<Object>) invocation -> { + mManagedProfileCallback = invocation.getArgument(0); + return null; + }).when(mManagedProfileController).addCallback(any()); + mAutoTileManager.init(); + when(mManagedProfileController.hasActiveProfile()).thenReturn(false); + + mManagedProfileCallback.onManagedProfileChanged(); + + verify(mQsTileHost, times(1)).removeTile(eq("work")); + verify(mAutoAddTracker, times(1)).setTileRemoved(eq("work")); + } + + @Test public void testEmptyArray_doesNotCrash() { mContext.getOrCreateTestableResources().addOverride( R.array.config_quickSettingsAutoAdd, new String[0]); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 74f8c61ad186..daf7dd06b0d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -57,6 +59,7 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -122,6 +125,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private VibratorHelper mVibratorHelper; @Mock private BiometricUnlockLogger mLogger; + private final FakeSystemClock mSystemClock = new FakeSystemClock(); private BiometricUnlockController mBiometricUnlockController; @Before @@ -144,7 +148,9 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mMetricsLogger, mDumpManager, mPowerManager, mLogger, mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle, mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController, - mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper); + mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper, + mSystemClock + ); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener); when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker); @@ -207,7 +213,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { verify(mKeyguardViewMediator).onWakeAndUnlocking(); assertThat(mBiometricUnlockController.getMode()) - .isEqualTo(BiometricUnlockController.MODE_WAKE_AND_UNLOCK); + .isEqualTo(MODE_WAKE_AND_UNLOCK); } @Test @@ -457,4 +463,83 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { // THEN wakeup the device verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString()); } + + @Test + public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() { + // GIVEN side fingerprint enrolled, last wake reason was power button + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + + // GIVEN last wake time just occurred + when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); + + // WHEN biometric fingerprint succeeds + givenFingerprintModeUnlockCollapsing(); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, + true); + + // THEN DO NOT vibrate the device + verify(mVibratorHelper, never()).vibrateAuthSuccess(anyString()); + } + + @Test + public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() { + // GIVEN side fingerprint enrolled, last wake reason was power button + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + + // GIVEN last wake time was 500ms ago + when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); + mSystemClock.advanceTime(500); + + // WHEN biometric fingerprint succeeds + givenFingerprintModeUnlockCollapsing(); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, + true); + + // THEN vibrate the device + verify(mVibratorHelper).vibrateAuthSuccess(anyString()); + } + + @Test + public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() { + // GIVEN side fingerprint enrolled, wakeup just happened + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); + + // GIVEN last wake reason was from a gesture + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_GESTURE); + + // WHEN biometric fingerprint succeeds + givenFingerprintModeUnlockCollapsing(); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, + true); + + // THEN vibrate the device + verify(mVibratorHelper).vibrateAuthSuccess(anyString()); + } + + @Test + public void onSideFingerprintFail_alwaysPlaysHaptic() { + // GIVEN side fingerprint enrolled, last wake reason was recent power button + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); + + // WHEN biometric fingerprint fails + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + + // THEN always vibrate the device + verify(mVibratorHelper).vibrateAuthError(anyString()); + } + + private void givenFingerprintModeUnlockCollapsing() { + when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true); + when(mKeyguardStateController.isShowing()).thenReturn(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 5a219451198a..c8157ccc8a9a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -380,7 +380,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any()); mWakefulnessLifecycle = - new WakefulnessLifecycle(mContext, mIWallpaperManager, mDumpManager); + new WakefulnessLifecycle(mContext, mIWallpaperManager, mFakeSystemClock, + mDumpManager); mWakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); mWakefulnessLifecycle.dispatchFinishedWakingUp(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index 5d377a8658a5..0859d140c3b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -34,6 +34,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.valid import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.kotlinArgumentCaptor import com.android.systemui.util.mockito.mock @@ -71,8 +73,10 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { private lateinit var underTest: MobileRepositorySwitcher private lateinit var realRepo: MobileConnectionsRepositoryImpl private lateinit var demoRepo: DemoMobileConnectionsRepository - private lateinit var mockDataSource: DemoModeMobileConnectionDataSource + private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource + private lateinit var wifiDataSource: DemoModeWifiDataSource private lateinit var logFactory: TableLogBufferFactory + private lateinit var wifiRepository: FakeWifiRepository @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var subscriptionManager: SubscriptionManager @@ -96,10 +100,15 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { // Never start in demo mode whenever(demoModeController.isInDemoMode).thenReturn(false) - mockDataSource = + mobileDataSource = mock<DemoModeMobileConnectionDataSource>().also { whenever(it.mobileEvents).thenReturn(fakeNetworkEventsFlow) } + wifiDataSource = + mock<DemoModeWifiDataSource>().also { + whenever(it.wifiEvents).thenReturn(MutableStateFlow(null)) + } + wifiRepository = FakeWifiRepository() realRepo = MobileConnectionsRepositoryImpl( @@ -113,12 +122,14 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { context, IMMEDIATE, scope, + wifiRepository, mock(), ) demoRepo = DemoMobileConnectionsRepository( - dataSource = mockDataSource, + mobileDataSource = mobileDataSource, + wifiDataSource = wifiDataSource, scope = scope, context = context, logFactory = logFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt index 210208532dd4..6989b514a703 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt @@ -29,6 +29,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectio import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock @@ -63,10 +65,12 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC private val testScope = TestScope(testDispatcher) private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null) + private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null) private lateinit var connectionsRepo: DemoMobileConnectionsRepository private lateinit var underTest: DemoMobileConnectionRepository private lateinit var mockDataSource: DemoModeMobileConnectionDataSource + private lateinit var mockWifiDataSource: DemoModeWifiDataSource @Before fun setUp() { @@ -75,10 +79,15 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC mock<DemoModeMobileConnectionDataSource>().also { whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow) } + mockWifiDataSource = + mock<DemoModeWifiDataSource>().also { + whenever(it.wifiEvents).thenReturn(fakeWifiEventFlow) + } connectionsRepo = DemoMobileConnectionsRepository( - dataSource = mockDataSource, + mobileDataSource = mockDataSource, + wifiDataSource = mockWifiDataSource, scope = testScope.backgroundScope, context = context, logFactory = logFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt index cdbe75e855bc..9d16b7fe5246 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt @@ -32,6 +32,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionMod import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock @@ -57,21 +59,28 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { private val testScope = TestScope(testDispatcher) private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null) + private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null) private lateinit var underTest: DemoMobileConnectionsRepository - private lateinit var mockDataSource: DemoModeMobileConnectionDataSource + private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource + private lateinit var wifiDataSource: DemoModeWifiDataSource @Before fun setUp() { // The data source only provides one API, so we can mock it with a flow here for convenience - mockDataSource = + mobileDataSource = mock<DemoModeMobileConnectionDataSource>().also { whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow) } + wifiDataSource = + mock<DemoModeWifiDataSource>().also { + whenever(it.wifiEvents).thenReturn(fakeWifiEventFlow) + } underTest = DemoMobileConnectionsRepository( - dataSource = mockDataSource, + mobileDataSource = mobileDataSource, + wifiDataSource = wifiDataSource, scope = testScope.backgroundScope, context = context, logFactory = logFactory, @@ -97,6 +106,22 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { } @Test + fun `wifi carrier merged event - create new subscription`() = + testScope.runTest { + var latest: List<SubscriptionModel>? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEmpty() + + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5) + + assertThat(latest).hasSize(1) + assertThat(latest!![0].subscriptionId).isEqualTo(5) + + job.cancel() + } + + @Test fun `network event - reuses subscription when same Id`() = testScope.runTest { var latest: List<SubscriptionModel>? = null @@ -119,6 +144,28 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { } @Test + fun `wifi carrier merged event - reuses subscription when same Id`() = + testScope.runTest { + var latest: List<SubscriptionModel>? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEmpty() + + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 1) + + assertThat(latest).hasSize(1) + assertThat(latest!![0].subscriptionId).isEqualTo(5) + + // Second network event comes in with the same subId, does not create a new subscription + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 2) + + assertThat(latest).hasSize(1) + assertThat(latest!![0].subscriptionId).isEqualTo(5) + + job.cancel() + } + + @Test fun `multiple subscriptions`() = testScope.runTest { var latest: List<SubscriptionModel>? = null @@ -133,6 +180,35 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { } @Test + fun `mobile subscription and carrier merged subscription`() = + testScope.runTest { + var latest: List<SubscriptionModel>? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + fakeNetworkEventFlow.value = validMobileEvent(subId = 1) + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5) + + assertThat(latest).hasSize(2) + + job.cancel() + } + + @Test + fun `multiple mobile subscriptions and carrier merged subscription`() = + testScope.runTest { + var latest: List<SubscriptionModel>? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + fakeNetworkEventFlow.value = validMobileEvent(subId = 1) + fakeNetworkEventFlow.value = validMobileEvent(subId = 2) + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 3) + + assertThat(latest).hasSize(3) + + job.cancel() + } + + @Test fun `mobile disabled event - disables connection - subId specified - single conn`() = testScope.runTest { var latest: List<SubscriptionModel>? = null @@ -194,6 +270,112 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun `wifi network updates to disabled - carrier merged connection removed`() = + testScope.runTest { + var latest: List<SubscriptionModel>? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1) + + assertThat(latest).hasSize(1) + + fakeWifiEventFlow.value = FakeWifiEventModel.WifiDisabled + + assertThat(latest).isEmpty() + + job.cancel() + } + + @Test + fun `wifi network updates to active - carrier merged connection removed`() = + testScope.runTest { + var latest: List<SubscriptionModel>? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1) + + assertThat(latest).hasSize(1) + + fakeWifiEventFlow.value = + FakeWifiEventModel.Wifi( + level = 1, + activity = 0, + ssid = null, + validated = true, + ) + + assertThat(latest).isEmpty() + + job.cancel() + } + + @Test + fun `mobile sub updates to carrier merged - only one connection`() = + testScope.runTest { + var latestSubsList: List<SubscriptionModel>? = null + var connections: List<DemoMobileConnectionRepository>? = null + val job = + underTest.subscriptions + .onEach { latestSubsList = it } + .onEach { infos -> + connections = + infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) } + } + .launchIn(this) + + fakeNetworkEventFlow.value = validMobileEvent(subId = 3, level = 2) + assertThat(latestSubsList).hasSize(1) + + val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1) + fakeWifiEventFlow.value = carrierMergedEvent + assertThat(latestSubsList).hasSize(1) + val connection = connections!!.find { it.subId == 3 }!! + assertCarrierMergedConnection(connection, carrierMergedEvent) + + job.cancel() + } + + @Test + fun `mobile sub updates to carrier merged then back - has old mobile data`() = + testScope.runTest { + var latestSubsList: List<SubscriptionModel>? = null + var connections: List<DemoMobileConnectionRepository>? = null + val job = + underTest.subscriptions + .onEach { latestSubsList = it } + .onEach { infos -> + connections = + infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) } + } + .launchIn(this) + + val mobileEvent = validMobileEvent(subId = 3, level = 2) + fakeNetworkEventFlow.value = mobileEvent + assertThat(latestSubsList).hasSize(1) + + val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1) + fakeWifiEventFlow.value = carrierMergedEvent + assertThat(latestSubsList).hasSize(1) + var connection = connections!!.find { it.subId == 3 }!! + assertCarrierMergedConnection(connection, carrierMergedEvent) + + // WHEN the carrier merged is removed + fakeWifiEventFlow.value = + FakeWifiEventModel.Wifi( + level = 4, + activity = 0, + ssid = null, + validated = true, + ) + + // THEN the subId=3 connection goes back to the mobile information + connection = connections!!.find { it.subId == 3 }!! + assertConnection(connection, mobileEvent) + + job.cancel() + } + /** Regression test for b/261706421 */ @Test fun `multiple connections - remove all - does not throw`() = @@ -289,6 +471,51 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun `demo connection - two connections - update carrier merged - no affect on first`() = + testScope.runTest { + var currentEvent1 = validMobileEvent(subId = 1) + var connection1: DemoMobileConnectionRepository? = null + var currentEvent2 = validCarrierMergedEvent(subId = 2) + var connection2: DemoMobileConnectionRepository? = null + var connections: List<DemoMobileConnectionRepository>? = null + val job = + underTest.subscriptions + .onEach { infos -> + connections = + infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) } + } + .launchIn(this) + + fakeNetworkEventFlow.value = currentEvent1 + fakeWifiEventFlow.value = currentEvent2 + assertThat(connections).hasSize(2) + connections!!.forEach { + when (it.subId) { + 1 -> connection1 = it + 2 -> connection2 = it + else -> Assert.fail("Unexpected subscription") + } + } + + assertConnection(connection1!!, currentEvent1) + assertCarrierMergedConnection(connection2!!, currentEvent2) + + // WHEN the event changes for connection 2, it updates, and connection 1 stays the same + currentEvent2 = validCarrierMergedEvent(subId = 2, level = 4) + fakeWifiEventFlow.value = currentEvent2 + assertConnection(connection1!!, currentEvent1) + assertCarrierMergedConnection(connection2!!, currentEvent2) + + // and vice versa + currentEvent1 = validMobileEvent(subId = 1, inflateStrength = true) + fakeNetworkEventFlow.value = currentEvent1 + assertConnection(connection1!!, currentEvent1) + assertCarrierMergedConnection(connection2!!, currentEvent2) + + job.cancel() + } + private fun assertConnection( conn: DemoMobileConnectionRepository, model: FakeNetworkEventModel @@ -315,6 +542,21 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { else -> {} } } + + private fun assertCarrierMergedConnection( + conn: DemoMobileConnectionRepository, + model: FakeWifiEventModel.CarrierMerged, + ) { + val connectionInfo: MobileConnectionModel = conn.connectionInfo.value + assertThat(conn.subId).isEqualTo(model.subscriptionId) + assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level) + assertThat(connectionInfo.primaryLevel).isEqualTo(model.level) + assertThat(connectionInfo.carrierNetworkChangeActive).isEqualTo(false) + assertThat(connectionInfo.isRoaming).isEqualTo(false) + assertThat(connectionInfo.isEmergencyOnly).isFalse() + assertThat(connectionInfo.isGsm).isFalse() + assertThat(connectionInfo.dataConnectionState).isEqualTo(DataConnectionState.Connected) + } } /** Convenience to create a valid fake network event with minimal params */ @@ -339,3 +581,14 @@ fun validMobileEvent( roaming = roaming, name = "demo name", ) + +fun validCarrierMergedEvent( + subId: Int = 1, + level: Int = 1, + numberOfLevels: Int = 4, +): FakeWifiEventModel.CarrierMerged = + FakeWifiEventModel.CarrierMerged( + subscriptionId = subId, + level = level, + numberOfLevels = numberOfLevels, + ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt new file mode 100644 index 000000000000..ea90150b432a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { + + private lateinit var underTest: CarrierMergedConnectionRepository + + private lateinit var wifiRepository: FakeWifiRepository + @Mock private lateinit var logger: TableLogBuffer + + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + wifiRepository = FakeWifiRepository() + + underTest = + CarrierMergedConnectionRepository( + SUB_ID, + logger, + NetworkNameModel.Default("name"), + testScope.backgroundScope, + wifiRepository, + ) + } + + @Test + fun connectionInfo_inactiveWifi_isDefault() = + testScope.runTest { + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) + + wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) + + assertThat(latest).isEqualTo(MobileConnectionModel()) + + job.cancel() + } + + @Test + fun connectionInfo_activeWifi_isDefault() = + testScope.runTest { + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) + + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = NET_ID, level = 1)) + + assertThat(latest).isEqualTo(MobileConnectionModel()) + + job.cancel() + } + + @Test + fun connectionInfo_carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() = + testScope.runTest { + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) + + wifiRepository.setIsWifiEnabled(true) + wifiRepository.setIsWifiDefault(true) + + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged( + networkId = NET_ID, + subscriptionId = SUB_ID, + level = 3, + ) + ) + + val expected = + MobileConnectionModel( + primaryLevel = 3, + cdmaLevel = 3, + dataConnectionState = DataConnectionState.Connected, + dataActivityDirection = + DataActivityModel( + hasActivityIn = false, + hasActivityOut = false, + ), + resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType, + isRoaming = false, + isEmergencyOnly = false, + operatorAlphaShort = null, + isInService = true, + isGsm = false, + carrierNetworkChangeActive = false, + ) + assertThat(latest).isEqualTo(expected) + + job.cancel() + } + + @Test + fun connectionInfo_carrierMergedWifi_wrongSubId_isDefault() = + testScope.runTest { + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) + + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged( + networkId = NET_ID, + subscriptionId = SUB_ID + 10, + level = 3, + ) + ) + + assertThat(latest).isEqualTo(MobileConnectionModel()) + assertThat(latest!!.primaryLevel).isNotEqualTo(3) + assertThat(latest!!.resolvedNetworkType) + .isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType) + + job.cancel() + } + + // This scenario likely isn't possible, but write a test for it anyway + @Test + fun connectionInfo_carrierMergedButNotEnabled_isDefault() = + testScope.runTest { + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) + + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged( + networkId = NET_ID, + subscriptionId = SUB_ID, + level = 3, + ) + ) + wifiRepository.setIsWifiEnabled(false) + + assertThat(latest).isEqualTo(MobileConnectionModel()) + + job.cancel() + } + + // This scenario likely isn't possible, but write a test for it anyway + @Test + fun connectionInfo_carrierMergedButWifiNotDefault_isDefault() = + testScope.runTest { + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) + + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged( + networkId = NET_ID, + subscriptionId = SUB_ID, + level = 3, + ) + ) + wifiRepository.setIsWifiDefault(false) + + assertThat(latest).isEqualTo(MobileConnectionModel()) + + job.cancel() + } + + @Test + fun numberOfLevels_comesFromCarrierMerged() = + testScope.runTest { + var latest: Int? = null + val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this) + + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged( + networkId = NET_ID, + subscriptionId = SUB_ID, + level = 1, + numberOfLevels = 6, + ) + ) + + assertThat(latest).isEqualTo(6) + + job.cancel() + } + + @Test + fun dataEnabled_matchesWifiEnabled() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this) + + wifiRepository.setIsWifiEnabled(true) + assertThat(latest).isTrue() + + wifiRepository.setIsWifiEnabled(false) + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun cdmaRoaming_alwaysFalse() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + job.cancel() + } + + private companion object { + const val SUB_ID = 123 + const val NET_ID = 456 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt new file mode 100644 index 000000000000..c02a4dfd074c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * This repo acts as a dispatcher to either the `typical` or `carrier merged` versions of the + * repository interface it's switching on. These tests just need to verify that the entire interface + * properly switches over when the value of `isCarrierMerged` changes. + */ +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class FullMobileConnectionRepositoryTest : SysuiTestCase() { + private lateinit var underTest: FullMobileConnectionRepository + + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + private val mobileMappings = FakeMobileMappingsProxy() + private val tableLogBuffer = mock<TableLogBuffer>() + private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>() + private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>() + + private lateinit var connectionsRepo: FakeMobileConnectionsRepository + private val globalMobileDataSettingChangedEvent: Flow<Unit> + get() = connectionsRepo.globalMobileDataSettingChangedEvent + + private lateinit var mobileRepo: FakeMobileConnectionRepository + private lateinit var carrierMergedRepo: FakeMobileConnectionRepository + + @Before + fun setUp() { + connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogBuffer) + + mobileRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer) + carrierMergedRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer) + + whenever( + mobileFactory.build( + eq(SUB_ID), + any(), + eq(DEFAULT_NAME), + eq(SEP), + eq(globalMobileDataSettingChangedEvent), + ) + ) + .thenReturn(mobileRepo) + whenever(carrierMergedFactory.build(eq(SUB_ID), any(), eq(DEFAULT_NAME))) + .thenReturn(carrierMergedRepo) + } + + @Test + fun startingIsCarrierMerged_usesCarrierMergedInitially() = + testScope.runTest { + val carrierMergedConnectionInfo = + MobileConnectionModel( + operatorAlphaShort = "Carrier Merged Operator", + ) + carrierMergedRepo.setConnectionInfo(carrierMergedConnectionInfo) + + initializeRepo(startingIsCarrierMerged = true) + + assertThat(underTest.activeRepo.value).isEqualTo(carrierMergedRepo) + assertThat(underTest.connectionInfo.value).isEqualTo(carrierMergedConnectionInfo) + verify(mobileFactory, never()) + .build( + SUB_ID, + tableLogBuffer, + DEFAULT_NAME, + SEP, + globalMobileDataSettingChangedEvent + ) + } + + @Test + fun startingNotCarrierMerged_usesTypicalInitially() = + testScope.runTest { + val mobileConnectionInfo = + MobileConnectionModel( + operatorAlphaShort = "Typical Operator", + ) + mobileRepo.setConnectionInfo(mobileConnectionInfo) + + initializeRepo(startingIsCarrierMerged = false) + + assertThat(underTest.activeRepo.value).isEqualTo(mobileRepo) + assertThat(underTest.connectionInfo.value).isEqualTo(mobileConnectionInfo) + verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer, DEFAULT_NAME) + } + + @Test + fun activeRepo_matchesIsCarrierMerged() = + testScope.runTest { + initializeRepo(startingIsCarrierMerged = false) + var latest: MobileConnectionRepository? = null + val job = underTest.activeRepo.onEach { latest = it }.launchIn(this) + + underTest.setIsCarrierMerged(true) + + assertThat(latest).isEqualTo(carrierMergedRepo) + + underTest.setIsCarrierMerged(false) + + assertThat(latest).isEqualTo(mobileRepo) + + underTest.setIsCarrierMerged(true) + + assertThat(latest).isEqualTo(carrierMergedRepo) + + job.cancel() + } + + @Test + fun connectionInfo_getsUpdatesFromRepo_carrierMerged() = + testScope.runTest { + initializeRepo(startingIsCarrierMerged = false) + + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) + + underTest.setIsCarrierMerged(true) + + val info1 = + MobileConnectionModel( + operatorAlphaShort = "Carrier Merged Operator", + primaryLevel = 1, + ) + carrierMergedRepo.setConnectionInfo(info1) + + assertThat(latest).isEqualTo(info1) + + val info2 = + MobileConnectionModel( + operatorAlphaShort = "Carrier Merged Operator #2", + primaryLevel = 2, + ) + carrierMergedRepo.setConnectionInfo(info2) + + assertThat(latest).isEqualTo(info2) + + val info3 = + MobileConnectionModel( + operatorAlphaShort = "Carrier Merged Operator #3", + primaryLevel = 3, + ) + carrierMergedRepo.setConnectionInfo(info3) + + assertThat(latest).isEqualTo(info3) + + job.cancel() + } + + @Test + fun connectionInfo_getsUpdatesFromRepo_mobile() = + testScope.runTest { + initializeRepo(startingIsCarrierMerged = false) + + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) + + underTest.setIsCarrierMerged(false) + + val info1 = + MobileConnectionModel( + operatorAlphaShort = "Typical Merged Operator", + primaryLevel = 1, + ) + mobileRepo.setConnectionInfo(info1) + + assertThat(latest).isEqualTo(info1) + + val info2 = + MobileConnectionModel( + operatorAlphaShort = "Typical Merged Operator #2", + primaryLevel = 2, + ) + mobileRepo.setConnectionInfo(info2) + + assertThat(latest).isEqualTo(info2) + + val info3 = + MobileConnectionModel( + operatorAlphaShort = "Typical Merged Operator #3", + primaryLevel = 3, + ) + mobileRepo.setConnectionInfo(info3) + + assertThat(latest).isEqualTo(info3) + + job.cancel() + } + + @Test + fun connectionInfo_updatesWhenCarrierMergedUpdates() = + testScope.runTest { + initializeRepo(startingIsCarrierMerged = false) + + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) + + val carrierMergedInfo = + MobileConnectionModel( + operatorAlphaShort = "Carrier Merged Operator", + primaryLevel = 4, + ) + carrierMergedRepo.setConnectionInfo(carrierMergedInfo) + + val mobileInfo = + MobileConnectionModel( + operatorAlphaShort = "Typical Operator", + primaryLevel = 2, + ) + mobileRepo.setConnectionInfo(mobileInfo) + + // Start with the mobile info + assertThat(latest).isEqualTo(mobileInfo) + + // WHEN isCarrierMerged is set to true + underTest.setIsCarrierMerged(true) + + // THEN the carrier merged info is used + assertThat(latest).isEqualTo(carrierMergedInfo) + + val newCarrierMergedInfo = + MobileConnectionModel( + operatorAlphaShort = "New CM Operator", + primaryLevel = 0, + ) + carrierMergedRepo.setConnectionInfo(newCarrierMergedInfo) + + assertThat(latest).isEqualTo(newCarrierMergedInfo) + + // WHEN isCarrierMerged is set to false + underTest.setIsCarrierMerged(false) + + // THEN the typical info is used + assertThat(latest).isEqualTo(mobileInfo) + + val newMobileInfo = + MobileConnectionModel( + operatorAlphaShort = "New Mobile Operator", + primaryLevel = 3, + ) + mobileRepo.setConnectionInfo(newMobileInfo) + + assertThat(latest).isEqualTo(newMobileInfo) + + job.cancel() + } + + @Test + fun `factory - reuses log buffers for same connection`() = + testScope.runTest { + val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock()) + + val factory = + FullMobileConnectionRepository.Factory( + scope = testScope.backgroundScope, + realLoggerFactory, + mobileFactory, + carrierMergedFactory, + ) + + // Create two connections for the same subId. Similar to if the connection appeared + // and disappeared from the connectionFactory's perspective + val connection1 = + factory.build( + SUB_ID, + startingIsCarrierMerged = false, + DEFAULT_NAME, + SEP, + globalMobileDataSettingChangedEvent, + ) + + val connection1Repeat = + factory.build( + SUB_ID, + startingIsCarrierMerged = false, + DEFAULT_NAME, + SEP, + globalMobileDataSettingChangedEvent, + ) + + assertThat(connection1.tableLogBuffer) + .isSameInstanceAs(connection1Repeat.tableLogBuffer) + } + + @Test + fun `factory - reuses log buffers for same sub ID even if carrier merged`() = + testScope.runTest { + val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock()) + + val factory = + FullMobileConnectionRepository.Factory( + scope = testScope.backgroundScope, + realLoggerFactory, + mobileFactory, + carrierMergedFactory, + ) + + val connection1 = + factory.build( + SUB_ID, + startingIsCarrierMerged = false, + DEFAULT_NAME, + SEP, + globalMobileDataSettingChangedEvent, + ) + + // WHEN a connection with the same sub ID but carrierMerged = true is created + val connection1Repeat = + factory.build( + SUB_ID, + startingIsCarrierMerged = true, + DEFAULT_NAME, + SEP, + globalMobileDataSettingChangedEvent, + ) + + // THEN the same table is re-used + assertThat(connection1.tableLogBuffer) + .isSameInstanceAs(connection1Repeat.tableLogBuffer) + } + + // TODO(b/238425913): Verify that the logging switches correctly (once the carrier merged repo + // implements logging). + + private fun initializeRepo(startingIsCarrierMerged: Boolean) { + underTest = + FullMobileConnectionRepository( + SUB_ID, + startingIsCarrierMerged, + tableLogBuffer, + DEFAULT_NAME, + SEP, + globalMobileDataSettingChangedEvent, + testScope.backgroundScope, + mobileFactory, + carrierMergedFactory, + ) + } + + private companion object { + const val SUB_ID = 42 + private val DEFAULT_NAME = NetworkNameModel.Default("default name") + private const val SEP = "-" + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 09589707b331..813b0ed041a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -37,17 +37,18 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel -import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings -import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -74,6 +75,9 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private lateinit var underTest: MobileConnectionsRepositoryImpl private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory + private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory + private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory + private lateinit var wifiRepository: FakeWifiRepository @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var subscriptionManager: SubscriptionManager @Mock private lateinit var telephonyManager: TelephonyManager @@ -100,6 +104,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mock<TableLogBuffer>() } + wifiRepository = FakeWifiRepository() + connectionFactory = MobileConnectionRepositoryImpl.Factory( fakeBroadcastDispatcher, @@ -110,7 +116,18 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { logger = logger, mobileMappingsProxy = mobileMappings, scope = scope, + ) + carrierMergedFactory = + CarrierMergedConnectionRepository.Factory( + scope, + wifiRepository, + ) + fullConnectionFactory = + FullMobileConnectionRepository.Factory( + scope = scope, logFactory = logBufferFactory, + mobileRepoFactory = connectionFactory, + carrierMergedRepoFactory = carrierMergedFactory, ) underTest = @@ -125,7 +142,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { context, IMMEDIATE, scope, - connectionFactory, + wifiRepository, + fullConnectionFactory, ) } @@ -180,6 +198,40 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test + fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() = + runBlocking(IMMEDIATE) { + var latest: List<SubscriptionModel>? = null + + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(SUB_CM)) + getSubscriptionCallback().onSubscriptionsChanged() + + assertThat(latest).isEqualTo(listOf(MODEL_CM)) + + job.cancel() + } + + @Test + fun testSubscriptions_carrierMergedAndOther_listHasBothWithCarrierMergedLast() = + runBlocking(IMMEDIATE) { + var latest: List<SubscriptionModel>? = null + + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(SUB_1, SUB_2, SUB_CM)) + getSubscriptionCallback().onSubscriptionsChanged() + + assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2, MODEL_CM)) + + job.cancel() + } + + @Test fun testActiveDataSubscriptionId_initialValueIsInvalidId() = runBlocking(IMMEDIATE) { assertThat(underTest.activeMobileDataSubscriptionId.value) @@ -219,6 +271,96 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test + fun testConnectionRepository_carrierMergedSubId_isCached() = + runBlocking(IMMEDIATE) { + val job = underTest.subscriptions.launchIn(this) + + wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(SUB_CM)) + getSubscriptionCallback().onSubscriptionsChanged() + + val repo1 = underTest.getRepoForSubId(SUB_CM_ID) + val repo2 = underTest.getRepoForSubId(SUB_CM_ID) + + assertThat(repo1).isSameInstanceAs(repo2) + + job.cancel() + } + + @Test + fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() = + runBlocking(IMMEDIATE) { + val job = underTest.subscriptions.launchIn(this) + + wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(SUB_1, SUB_CM)) + getSubscriptionCallback().onSubscriptionsChanged() + + val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID) + val mobileRepo = underTest.getRepoForSubId(SUB_1_ID) + assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue() + assertThat(mobileRepo.getIsCarrierMerged()).isFalse() + + job.cancel() + } + + @Test + fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() = + runBlocking(IMMEDIATE) { + val job = underTest.subscriptions.launchIn(this) + + wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(SUB_1, SUB_CM)) + getSubscriptionCallback().onSubscriptionsChanged() + + val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID) + var mobileRepo = underTest.getRepoForSubId(SUB_1_ID) + assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue() + assertThat(mobileRepo.getIsCarrierMerged()).isFalse() + + // WHEN the wifi network updates to be not carrier merged + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 4, level = 1)) + + // THEN the repos update + val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID) + mobileRepo = underTest.getRepoForSubId(SUB_1_ID) + assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse() + assertThat(mobileRepo.getIsCarrierMerged()).isFalse() + + job.cancel() + } + + @Test + fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() = + runBlocking(IMMEDIATE) { + val job = underTest.subscriptions.launchIn(this) + + wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(SUB_1, SUB_CM)) + getSubscriptionCallback().onSubscriptionsChanged() + + val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID) + var mobileRepo = underTest.getRepoForSubId(SUB_1_ID) + assertThat(notYetCarrierMergedRepo.getIsCarrierMerged()).isFalse() + assertThat(mobileRepo.getIsCarrierMerged()).isFalse() + + // WHEN the wifi network updates to be carrier merged + wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + + // THEN the repos update + val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID) + mobileRepo = underTest.getRepoForSubId(SUB_1_ID) + assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue() + assertThat(mobileRepo.getIsCarrierMerged()).isFalse() + + job.cancel() + } + + @Test fun testConnectionCache_clearsInvalidSubscriptions() = runBlocking(IMMEDIATE) { val job = underTest.subscriptions.launchIn(this) @@ -244,6 +386,34 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() = + runBlocking(IMMEDIATE) { + val job = underTest.subscriptions.launchIn(this) + + wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(SUB_1, SUB_2, SUB_CM)) + getSubscriptionCallback().onSubscriptionsChanged() + + // Get repos to trigger caching + val repo1 = underTest.getRepoForSubId(SUB_1_ID) + val repo2 = underTest.getRepoForSubId(SUB_2_ID) + val repoCarrierMerged = underTest.getRepoForSubId(SUB_CM_ID) + + assertThat(underTest.getSubIdRepoCache()) + .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2, SUB_CM_ID, repoCarrierMerged) + + // SUB_2 and SUB_CM disappear + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(SUB_1)) + getSubscriptionCallback().onSubscriptionsChanged() + + assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1) + + job.cancel() + } + /** Regression test for b/261706421 */ @Test fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() = @@ -295,13 +465,13 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { underTest.getRepoForSubId(SUB_1_ID) verify(logBufferFactory) .getOrCreate( - eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_1_ID)), + eq(tableBufferLogName(SUB_1_ID)), anyInt(), ) underTest.getRepoForSubId(SUB_2_ID) verify(logBufferFactory) .getOrCreate( - eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_2_ID)), + eq(tableBufferLogName(SUB_2_ID)), anyInt(), ) @@ -309,46 +479,6 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test - fun `connection repository factory - reuses log buffers for same connection`() = - runBlocking(IMMEDIATE) { - val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock()) - - connectionFactory = - MobileConnectionRepositoryImpl.Factory( - fakeBroadcastDispatcher, - context = context, - telephonyManager = telephonyManager, - bgDispatcher = IMMEDIATE, - globalSettings = globalSettings, - logger = logger, - mobileMappingsProxy = mobileMappings, - scope = scope, - logFactory = realLoggerFactory, - ) - - // Create two connections for the same subId. Similar to if the connection appeared - // and disappeared from the connectionFactory's perspective - val connection1 = - connectionFactory.build( - 1, - NetworkNameModel.Default("default_name"), - "-", - underTest.globalMobileDataSettingChangedEvent, - ) - - val connection1_repeat = - connectionFactory.build( - 1, - NetworkNameModel.Default("default_name"), - "-", - underTest.globalMobileDataSettingChangedEvent, - ) - - assertThat(connection1.tableLogBuffer) - .isSameInstanceAs(connection1_repeat.tableLogBuffer) - } - - @Test fun mobileConnectivity_default() { assertThat(underTest.defaultMobileNetworkConnectivity.value) .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false)) @@ -461,7 +591,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { context, IMMEDIATE, scope, - connectionFactory, + wifiRepository, + fullConnectionFactory, ) var latest: MobileMappings.Config? = null @@ -571,5 +702,16 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private const val NET_ID = 123 private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) } + + private const val SUB_CM_ID = 5 + private val SUB_CM = + mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) } + private val MODEL_CM = SubscriptionModel(subscriptionId = SUB_CM_ID) + private val WIFI_NETWORK_CM = + WifiNetworkModel.CarrierMerged( + networkId = 3, + subscriptionId = SUB_CM_ID, + level = 1, + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index 61e13b85db6c..e6be7f15235b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository @@ -271,6 +272,23 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test + fun iconGroup_carrierMerged_usesOverride() = + runBlocking(IMMEDIATE) { + connectionRepository.setConnectionInfo( + MobileConnectionModel( + resolvedNetworkType = CarrierMergedNetworkType, + ), + ) + + var latest: MobileIconGroup? = null + val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(CarrierMergedNetworkType.iconGroupOverride) + + job.cancel() + } + + @Test fun alwaysShowDataRatIcon_matchesParent() = runBlocking(IMMEDIATE) { var latest: Boolean? = null diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt index 30ac8d432e8a..824cebdc3c08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt @@ -16,11 +16,12 @@ package com.android.systemui.statusbar.pipeline.wifi.data.model +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableRowLogger import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MIN_VALID_LEVEL +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Companion.MIN_VALID_LEVEL import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -44,9 +45,53 @@ class WifiNetworkModelTest : SysuiTestCase() { WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1) } + @Test(expected = IllegalArgumentException::class) + fun carrierMerged_invalidSubId_exceptionThrown() { + WifiNetworkModel.CarrierMerged(NETWORK_ID, INVALID_SUBSCRIPTION_ID, 1) + } + // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken @Test + fun logDiffs_carrierMergedToInactive_resetsAllFields() { + val logger = TestLogger() + val prevVal = + WifiNetworkModel.CarrierMerged( + networkId = 5, + subscriptionId = 3, + level = 1, + ) + + WifiNetworkModel.Inactive.logDiffs(prevVal, logger) + + assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE)) + assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString())) + assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false")) + assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString())) + assertThat(logger.changes).contains(Pair(COL_SSID, "null")) + } + + @Test + fun logDiffs_inactiveToCarrierMerged_logsAllFields() { + val logger = TestLogger() + val carrierMerged = + WifiNetworkModel.CarrierMerged( + networkId = 6, + subscriptionId = 3, + level = 2, + ) + + carrierMerged.logDiffs(prevVal = WifiNetworkModel.Inactive, logger) + + assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)) + assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "6")) + assertThat(logger.changes).contains(Pair(COL_SUB_ID, "3")) + assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) + assertThat(logger.changes).contains(Pair(COL_LEVEL, "2")) + assertThat(logger.changes).contains(Pair(COL_SSID, "null")) + } + + @Test fun logDiffs_inactiveToActive_logsAllActiveFields() { val logger = TestLogger() val activeNetwork = @@ -95,8 +140,14 @@ class WifiNetworkModelTest : SysuiTestCase() { level = 3, ssid = "Test SSID" ) + val prevVal = + WifiNetworkModel.CarrierMerged( + networkId = 5, + subscriptionId = 3, + level = 1, + ) - activeNetwork.logDiffs(prevVal = WifiNetworkModel.CarrierMerged, logger) + activeNetwork.logDiffs(prevVal, logger) assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE)) assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5")) @@ -105,7 +156,7 @@ class WifiNetworkModelTest : SysuiTestCase() { assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID")) } @Test - fun logDiffs_activeToCarrierMerged_resetsAllActiveFields() { + fun logDiffs_activeToCarrierMerged_logsAllFields() { val logger = TestLogger() val activeNetwork = WifiNetworkModel.Active( @@ -114,13 +165,20 @@ class WifiNetworkModelTest : SysuiTestCase() { level = 3, ssid = "Test SSID" ) + val carrierMerged = + WifiNetworkModel.CarrierMerged( + networkId = 6, + subscriptionId = 3, + level = 2, + ) - WifiNetworkModel.CarrierMerged.logDiffs(prevVal = activeNetwork, logger) + carrierMerged.logDiffs(prevVal = activeNetwork, logger) assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)) - assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString())) - assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false")) - assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString())) + assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "6")) + assertThat(logger.changes).contains(Pair(COL_SUB_ID, "3")) + assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) + assertThat(logger.changes).contains(Pair(COL_LEVEL, "2")) assertThat(logger.changes).contains(Pair(COL_SSID, "null")) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index 8f07615b19b2..87ce8faff5a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -26,6 +26,7 @@ import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.net.wifi.WifiManager.TrafficStateCallback +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher @@ -340,7 +341,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { .launchIn(this) val wifiInfo = mock<WifiInfo>().apply { - whenever(this.ssid).thenReturn(SSID) whenever(this.isPrimary).thenReturn(true) whenever(this.isCarrierMerged).thenReturn(true) } @@ -353,6 +353,67 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + } + + getNetworkCallback().onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(wifiInfo), + ) + + assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java) + + job.cancel() + } + + @Test + fun wifiNetwork_isCarrierMerged_getsCorrectValues() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val rssi = -57 + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.rssi).thenReturn(rssi) + whenever(this.subscriptionId).thenReturn(567) + } + + whenever(wifiManager.calculateSignalLevel(rssi)).thenReturn(2) + whenever(wifiManager.maxSignalLevel).thenReturn(5) + + getNetworkCallback().onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(wifiInfo), + ) + + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + val latestCarrierMerged = latest as WifiNetworkModel.CarrierMerged + assertThat(latestCarrierMerged.networkId).isEqualTo(NETWORK_ID) + assertThat(latestCarrierMerged.subscriptionId).isEqualTo(567) + assertThat(latestCarrierMerged.level).isEqualTo(2) + // numberOfLevels = maxSignalLevel + 1 + assertThat(latestCarrierMerged.numberOfLevels).isEqualTo(6) + + job.cancel() + } + + @Test fun wifiNetwork_notValidated_networkNotValidated() = runBlocking(IMMEDIATE) { var latest: WifiNetworkModel? = null val job = underTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt index 01d59f96c221..089a170aa2be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt @@ -84,7 +84,9 @@ class WifiInteractorImplTest : SysuiTestCase() { @Test fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) { - wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged) + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged(networkId = 1, subscriptionId = 2, level = 1) + ) var latest: String? = "default" val job = underTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt index 726e813ec414..b9328377772a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt @@ -206,7 +206,8 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase // Enabled = false => no networks shown TestCase( enabled = false, - network = WifiNetworkModel.CarrierMerged, + network = + WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1), expected = null, ), TestCase( @@ -228,7 +229,8 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase // forceHidden = true => no networks shown TestCase( forceHidden = true, - network = WifiNetworkModel.CarrierMerged, + network = + WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1), expected = null, ), TestCase( @@ -369,7 +371,8 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase // network = CarrierMerged => not shown TestCase( - network = WifiNetworkModel.CarrierMerged, + network = + WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1), expected = null, ), diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt new file mode 100644 index 000000000000..7e010886c394 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 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.stylus + +import android.hardware.BatteryState + +class FixedCapacityBatteryState(private val capacity: Float) : BatteryState() { + override fun getCapacity() = capacity + override fun getStatus() = 0 + override fun isPresent() = true +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt index 117e00dd9b2a..1cccd65c8dbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt @@ -85,6 +85,13 @@ class StylusUsiPowerStartableTest : SysuiTestCase() { } @Test + fun start_initStylusUsiPowerUi() { + startable.start() + + verify(stylusUsiPowerUi, times(1)).init() + } + + @Test fun onStylusBluetoothConnected_refreshesNotification() { startable.onStylusBluetoothConnected(STYLUS_DEVICE_ID, "ANY") @@ -99,13 +106,21 @@ class StylusUsiPowerStartableTest : SysuiTestCase() { } @Test - fun onStylusUsiBatteryStateChanged_batteryPresent_refreshesNotification() { - val batteryState = mock(BatteryState::class.java) - whenever(batteryState.isPresent).thenReturn(true) + fun onStylusUsiBatteryStateChanged_batteryPresentValidCapacity_refreshesNotification() { + val batteryState = FixedCapacityBatteryState(0.1f) startable.onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState) - verify(stylusUsiPowerUi, times(1)).updateBatteryState(batteryState) + verify(stylusUsiPowerUi, times(1)).updateBatteryState(STYLUS_DEVICE_ID, batteryState) + } + + @Test + fun onStylusUsiBatteryStateChanged_batteryPresentInvalidCapacity_noop() { + val batteryState = FixedCapacityBatteryState(0f) + + startable.onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState) + + verifyNoMoreInteractions(stylusUsiPowerUi) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt index a7951f4fa068..1e81dc761b18 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt @@ -17,8 +17,11 @@ package com.android.systemui.stylus import android.app.Notification -import android.hardware.BatteryState +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent import android.hardware.input.InputManager +import android.os.Bundle import android.os.Handler import android.testing.AndroidTestingRunner import android.view.InputDevice @@ -27,8 +30,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.Ignore @@ -37,7 +42,10 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.doNothing import org.mockito.Mockito.inOrder +import org.mockito.Mockito.never +import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @@ -53,11 +61,16 @@ class StylusUsiPowerUiTest : SysuiTestCase() { @Captor lateinit var notificationCaptor: ArgumentCaptor<Notification> private lateinit var stylusUsiPowerUi: StylusUsiPowerUI + private lateinit var broadcastReceiver: BroadcastReceiver + private lateinit var contextSpy: Context @Before fun setUp() { MockitoAnnotations.initMocks(this) + contextSpy = spy(mContext) + doNothing().whenever(contextSpy).startActivity(any()) + whenever(handler.post(any())).thenAnswer { (it.arguments[0] as Runnable).run() true @@ -68,12 +81,20 @@ class StylusUsiPowerUiTest : SysuiTestCase() { whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true) // whenever(btStylusDevice.bluetoothAddress).thenReturn("SO:ME:AD:DR:ES") - stylusUsiPowerUi = StylusUsiPowerUI(mContext, notificationManager, inputManager, handler) + stylusUsiPowerUi = StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler) + broadcastReceiver = stylusUsiPowerUi.receiver + } + + @Test + fun updateBatteryState_capacityZero_noop() { + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0f)) + + verifyNoMoreInteractions(notificationManager) } @Test fun updateBatteryState_capacityBelowThreshold_notifies() { - stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f)) verify(notificationManager, times(1)) .notify(eq(R.string.stylus_battery_low_percentage), any()) @@ -82,7 +103,7 @@ class StylusUsiPowerUiTest : SysuiTestCase() { @Test fun updateBatteryState_capacityAboveThreshold_cancelsNotificattion() { - stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.8f)) + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.8f)) verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage) verifyNoMoreInteractions(notificationManager) @@ -90,8 +111,8 @@ class StylusUsiPowerUiTest : SysuiTestCase() { @Test fun updateBatteryState_existingNotification_capacityAboveThreshold_cancelsNotification() { - stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) - stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.8f)) + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f)) + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.8f)) inOrder(notificationManager).let { it.verify(notificationManager, times(1)) @@ -103,8 +124,8 @@ class StylusUsiPowerUiTest : SysuiTestCase() { @Test fun updateBatteryState_existingNotification_capacityBelowThreshold_updatesNotification() { - stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) - stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.15f)) + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f)) + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.15f)) verify(notificationManager, times(2)) .notify(eq(R.string.stylus_battery_low_percentage), notificationCaptor.capture()) @@ -121,9 +142,9 @@ class StylusUsiPowerUiTest : SysuiTestCase() { @Test fun updateBatteryState_capacityAboveThenBelowThreshold_hidesThenShowsNotification() { - stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) - stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.5f)) - stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f)) + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.5f)) + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f)) inOrder(notificationManager).let { it.verify(notificationManager, times(1)) @@ -145,7 +166,7 @@ class StylusUsiPowerUiTest : SysuiTestCase() { @Test fun updateSuppression_existingNotification_cancelsNotification() { - stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f)) stylusUsiPowerUi.updateSuppression(true) @@ -159,18 +180,18 @@ class StylusUsiPowerUiTest : SysuiTestCase() { @Test @Ignore("TODO(b/257936830): get bt address once input api available") - fun refresh_hasConnectedBluetoothStylus_doesNotNotify() { + fun refresh_hasConnectedBluetoothStylus_cancelsNotification() { whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0)) stylusUsiPowerUi.refresh() - verifyNoMoreInteractions(notificationManager) + verify(notificationManager).cancel(R.string.stylus_battery_low_percentage) } @Test @Ignore("TODO(b/257936830): get bt address once input api available") fun refresh_hasConnectedBluetoothStylus_existingNotification_cancelsNotification() { - stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) + stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f)) whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0)) stylusUsiPowerUi.refresh() @@ -178,9 +199,27 @@ class StylusUsiPowerUiTest : SysuiTestCase() { verify(notificationManager).cancel(R.string.stylus_battery_low_percentage) } - class FixedCapacityBatteryState(private val capacity: Float) : BatteryState() { - override fun getCapacity() = capacity - override fun getStatus() = 0 - override fun isPresent() = true + @Test + fun broadcastReceiver_clicked_hasInputDeviceId_startsUsiDetailsActivity() { + val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY) + val activityIntentCaptor = argumentCaptor<Intent>() + stylusUsiPowerUi.updateBatteryState(1, FixedCapacityBatteryState(0.15f)) + broadcastReceiver.onReceive(contextSpy, intent) + + verify(contextSpy, times(1)).startActivity(activityIntentCaptor.capture()) + assertThat(activityIntentCaptor.value.action) + .isEqualTo(StylusUsiPowerUI.ACTION_STYLUS_USI_DETAILS) + val args = + activityIntentCaptor.value.getExtra(StylusUsiPowerUI.KEY_SETTINGS_FRAGMENT_ARGS) + as Bundle + assertThat(args.getInt(StylusUsiPowerUI.KEY_DEVICE_INPUT_ID)).isEqualTo(1) + } + + @Test + fun broadcastReceiver_clicked_nullInputDeviceId_doesNotStartActivity() { + val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY) + broadcastReceiver.onReceive(contextSpy, intent) + + verify(contextSpy, never()).startActivity(any()) } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 554e2690b878..20c9a211e586 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -4215,7 +4215,6 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } boolean changed = false; - Set<Permission> needsUpdate = null; synchronized (mLock) { final Iterator<Permission> it = mRegistry.getPermissionTrees().iterator(); while (it.hasNext()) { @@ -4234,26 +4233,6 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt + " that used to be declared by " + bp.getPackageName()); it.remove(); } - if (needsUpdate == null) { - needsUpdate = new ArraySet<>(); - } - needsUpdate.add(bp); - } - } - if (needsUpdate != null) { - for (final Permission bp : needsUpdate) { - final AndroidPackage sourcePkg = - mPackageManagerInt.getPackage(bp.getPackageName()); - final PackageStateInternal sourcePs = - mPackageManagerInt.getPackageStateInternal(bp.getPackageName()); - synchronized (mLock) { - if (sourcePkg != null && sourcePs != null) { - continue; - } - Slog.w(TAG, "Removing dangling permission tree: " + bp.getName() - + " from package " + bp.getPackageName()); - mRegistry.removePermission(bp.getName()); - } } } return changed; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 5285f63dcc44..b55b6dd0eb55 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4129,9 +4129,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_DEMO_APP_2: case KeyEvent.KEYCODE_DEMO_APP_3: case KeyEvent.KEYCODE_DEMO_APP_4: { - // TODO(b/254604589): Dispatch KeyEvent to System UI. - sendSystemKeyToStatusBarAsync(keyCode); - // Just drop if keys are not intercepted for direct key. result &= ~ACTION_PASS_TO_USER; break; diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 8ad76a3eb327..79be9463aa05 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1074,10 +1074,14 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Use launch-adjacent-flag-root if launching with launch-adjacent flag. if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0 && mLaunchAdjacentFlagRootTask != null) { - // If the adjacent launch is coming from the same root, launch to adjacent root instead. - if (sourceTask != null && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null + if (sourceTask != null && sourceTask == candidateTask) { + // Do nothing when task that is getting opened is same as the source. + } else if (sourceTask != null + && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null && (sourceTask == mLaunchAdjacentFlagRootTask || sourceTask.isDescendantOf(mLaunchAdjacentFlagRootTask))) { + // If the adjacent launch is coming from the same root, launch to + // adjacent root instead. return mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment().asTask(); } else { return mLaunchAdjacentFlagRootTask; diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index ca9ff6f15f3f..962a07ac6553 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -43,23 +43,23 @@ import static com.android.server.backup.testing.Utils.transferStreamedData; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.intThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.intThat; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import static org.robolectric.shadow.api.Shadow.extract; @@ -2185,7 +2185,7 @@ public class KeyValueBackupTaskTest { task.waitCancel(); reset(transportMock.transport); taskFinished.block(); - verifyZeroInteractions(transportMock.transport); + verifyNoInteractions(transportMock.transport); } @Test |