diff options
176 files changed, 4964 insertions, 1114 deletions
diff --git a/Android.bp b/Android.bp index df6fdaa5fdf6..caec3a21e89f 100644 --- a/Android.bp +++ b/Android.bp @@ -151,6 +151,9 @@ java_library { visibility: [ // DO NOT ADD ANY MORE ENTRIES TO THIS LIST "//external/robolectric-shadows:__subpackages__", + //This will eventually replace the item above, and serves the + //same purpose. + "//external/robolectric:__subpackages__", "//frameworks/layoutlib:__subpackages__", ], } diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index 6091bf9088d3..bf72b1d7a035 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.hardware.vibrator.IVibrator; import android.util.ArrayMap; import android.util.Log; import android.util.Range; @@ -313,8 +314,14 @@ public class SystemVibrator extends Vibrator { private static final float EPSILON = 1e-5f; public MultiVibratorInfo(VibratorInfo[] vibrators) { + // Need to use an extra constructor to share the computation in super initialization. + this(vibrators, frequencyProfileIntersection(vibrators)); + } + + private MultiVibratorInfo(VibratorInfo[] vibrators, + VibratorInfo.FrequencyProfile mergedProfile) { super(/* id= */ -1, - capabilitiesIntersection(vibrators), + capabilitiesIntersection(vibrators, mergedProfile.isEmpty()), supportedEffectsIntersection(vibrators), supportedBrakingIntersection(vibrators), supportedPrimitivesAndDurationsIntersection(vibrators), @@ -323,14 +330,19 @@ public class SystemVibrator extends Vibrator { integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax), integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax), floatPropertyIntersection(vibrators, VibratorInfo::getQFactor), - frequencyProfileIntersection(vibrators)); + mergedProfile); } - private static int capabilitiesIntersection(VibratorInfo[] infos) { + private static int capabilitiesIntersection(VibratorInfo[] infos, + boolean frequencyProfileIsEmpty) { int intersection = ~0; for (VibratorInfo info : infos) { intersection &= info.getCapabilities(); } + if (frequencyProfileIsEmpty) { + // Revoke frequency control if the merged frequency profile ended up empty. + intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL; + } return intersection; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a955dbba97e7..5c8c117481b0 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -18441,6 +18441,9 @@ public final class Settings { /** * Activity Action: For system or preinstalled apps to show their {@link Activity} embedded * in Settings app on large screen devices. + * + * Developers should resolve the Intent action before using it. + * * <p> * Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI} must be included to * specify the intent for the activity which will be embedded in Settings app. diff --git a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java index 7b154a54fc85..80d14574955d 100644 --- a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java +++ b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java @@ -18,6 +18,7 @@ package com.android.internal.widget; import android.annotation.Nullable; import android.content.Context; +import android.os.Trace; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; @@ -54,6 +55,7 @@ public class RemeasuringLinearLayout extends LinearLayout { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Trace.beginSection("RemeasuringLinearLayout#onMeasure"); super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); int height = 0; @@ -86,5 +88,6 @@ public class RemeasuringLinearLayout extends LinearLayout { } mMatchParentViews.clear(); setMeasuredDimension(getMeasuredWidth(), height); + Trace.endSection(); } } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 07e05c6a830e..2a9e60a46972 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1914,6 +1914,10 @@ --> <string name="config_defaultCaptivePortalLoginPackageName" translatable="false">com.android.captiveportallogin</string> + <!-- The package name of the dock manager app. Must be granted the + POST_NOTIFICATIONS permission. --> + <string name="config_defaultDockManagerPackageName" translatable="false"></string> + <!-- Whether to enable geocoder overlay which allows geocoder to be replaced by an app at run-time. When disabled, only the config_geocoderProviderPackageName package will be searched for diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8e7da4a5eac3..79c7042adce1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3466,6 +3466,9 @@ <!-- Captive Portal Login --> <java-symbol type="string" name="config_defaultCaptivePortalLoginPackageName" /> + <!-- Dock Manager --> + <java-symbol type="string" name="config_defaultDockManagerPackageName" /> + <!-- Optional IPsec algorithms --> <java-symbol type="array" name="config_optionalIpSecAlgorithms" /> diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java index 7ebebc965d8d..c59a3f518da8 100644 --- a/core/tests/coretests/src/android/os/VibratorTest.java +++ b/core/tests/coretests/src/android/os/VibratorTest.java @@ -246,10 +246,12 @@ public class VibratorTest { @Test public void getQFactorAndResonantFrequency_differentValues_returnsNaN() { VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setQFactor(1f) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null)) .build(); VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setQFactor(2f) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null)) .build(); @@ -258,6 +260,7 @@ public class VibratorTest { assertTrue(Float.isNaN(info.getQFactor())); assertTrue(Float.isNaN(info.getResonantFrequencyHz())); + assertEmptyFrequencyProfileAndControl(info); // One vibrator with values undefined. VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build(); @@ -266,16 +269,19 @@ public class VibratorTest { assertTrue(Float.isNaN(info.getQFactor())); assertTrue(Float.isNaN(info.getResonantFrequencyHz())); + assertEmptyFrequencyProfileAndControl(info); } @Test public void getQFactorAndResonantFrequency_sameValues_returnsValue() { VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setQFactor(10f) .setFrequencyProfile(new VibratorInfo.FrequencyProfile( /* resonantFrequencyHz= */ 11, 10, 0.5f, null)) .build(); VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setQFactor(10f) .setFrequencyProfile(new VibratorInfo.FrequencyProfile( /* resonantFrequencyHz= */ 11, 5, 1, null)) @@ -285,113 +291,131 @@ public class VibratorTest { assertEquals(10f, info.getQFactor(), TEST_TOLERANCE); assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE); + + // No frequency range defined. + assertTrue(info.getFrequencyProfile().isEmpty()); + assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)); } @Test public void getFrequencyProfile_noVibrator_returnsEmpty() { VibratorInfo info = new SystemVibrator.NoVibratorInfo(); - assertTrue(info.getFrequencyProfile().isEmpty()); + assertEmptyFrequencyProfileAndControl(info); } @Test public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() { VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, new float[] { 0, 1 })) .build(); VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1, new float[] { 0, 1 })) .build(); VibratorInfo info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{firstVibrator, differentResonantFrequency}); - assertTrue(info.getFrequencyProfile().isEmpty()); + assertEmptyFrequencyProfileAndControl(info); VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2, new float[] { 0, 1 })) .build(); info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{firstVibrator, differentFrequencyResolution}); - assertTrue(info.getFrequencyProfile().isEmpty()); + assertEmptyFrequencyProfileAndControl(info); } @Test public void getFrequencyProfile_missingValues_returnsEmpty() { VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, new float[] { 0, 1 })) .build(); VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1, new float[] { 0, 1 })) .build(); VibratorInfo info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{firstVibrator, missingResonantFrequency}); - assertTrue(info.getFrequencyProfile().isEmpty()); + assertEmptyFrequencyProfileAndControl(info); VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1, new float[] { 0, 1 })) .build(); info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{firstVibrator, missingMinFrequency}); - assertTrue(info.getFrequencyProfile().isEmpty()); + assertEmptyFrequencyProfileAndControl(info); VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN, new float[] { 0, 1 })) .build(); info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{firstVibrator, missingFrequencyResolution}); - assertTrue(info.getFrequencyProfile().isEmpty()); + assertEmptyFrequencyProfileAndControl(info); VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null)) .build(); info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{firstVibrator, missingMaxAmplitudes}); - assertTrue(info.getFrequencyProfile().isEmpty()); + assertEmptyFrequencyProfileAndControl(info); } @Test public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() { VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f, new float[] { 0, 1, 1, 0 })) .build(); VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f, new float[] { 0, 1, 1, 0 })) .build(); VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0, 1, 1, 0 })) .build(); VibratorInfo info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{firstVibrator, unalignedMinFrequency, thirdVibrator}); - assertTrue(info.getFrequencyProfile().isEmpty()); + assertEmptyFrequencyProfileAndControl(info); } @Test public void getFrequencyProfile_alignedProfiles_returnsIntersection() { VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f, new float[] { 0.5f, 1, 1, 0.5f })) .build(); VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 1, 1, 1 })) .build(); VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3) + .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL) .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.8f, 0.5f })) .build(); @@ -401,6 +425,20 @@ public class VibratorTest { assertEquals( new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }), info.getFrequencyProfile()); + assertEquals(true, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)); + + // Third vibrator without frequency control capability. + thirdVibrator = new VibratorInfo.Builder(/* id= */ 3) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, + new float[] { 0.8f, 1, 0.8f, 0.5f })) + .build(); + info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator}); + + assertEquals( + new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }), + info.getFrequencyProfile()); + assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)); } @Test @@ -547,4 +585,12 @@ public class VibratorTest { VibrationAttributes vibrationAttributes = captor.getValue(); assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes); } + + /** + * Asserts that the frequency profile is empty, and therefore frequency control isn't supported. + */ + void assertEmptyFrequencyProfileAndControl(VibratorInfo info) { + assertTrue(info.getFrequencyProfile().isEmpty()); + assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)); + } } diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index df5f921f3a62..c6197c8a730b 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -111,4 +111,8 @@ <!-- Whether to dim a split-screen task when the other is the IME target --> <bool name="config_dimNonImeAttachedSide">true</bool> + + <!-- Components support to launch multiple instances into split-screen --> + <string-array name="config_componentsSupportMultiInstancesSplit"> + </string-array> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 839edc8c174a..3de1045bfbda 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -601,7 +601,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange animator.start(); } - /** Swich both surface position with animation. */ + /** Switch both surface position with animation. */ public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1, SurfaceControl leash2, Consumer<Rect> finishCallback) { final boolean isLandscape = isLandscape(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl index eb08d0ecbd06..5533ad56d17c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -86,8 +86,8 @@ interface ISplitScreen { /** * Starts a pair of intent and task in one transition. */ - oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent, - in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio, + oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId, + in Bundle options2, int sidePosition, float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 16; /** @@ -108,9 +108,8 @@ interface ISplitScreen { * Starts a pair of intent and task using legacy transition system. */ oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent, - in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2, - int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter, - in InstanceId instanceId) = 12; + in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio, + in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12; /** * Starts a pair of shortcut and task using legacy transition system. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index c6a2b8312ebd..cdc8cdd2c28d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.view.Display.DEFAULT_DISPLAY; @@ -32,6 +33,8 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; @@ -60,13 +63,12 @@ import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -166,8 +168,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private final IconProvider mIconProvider; private final Optional<RecentTasksController> mRecentTasksOptional; private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler; + private final String[] mMultiInstancesComponents; + + @VisibleForTesting + StageCoordinator mStageCoordinator; - private StageCoordinator mStageCoordinator; // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated // outside the bounds of the roots by being reparented into a higher level fullscreen container private SurfaceControl mGoingToRecentsTasksLayer; @@ -210,6 +215,51 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) { shellInit.addInitCallback(this::onInit, this); } + + // TODO(255224696): Remove the config once having a way for client apps to opt-in + // multi-instances split. + mMultiInstancesComponents = mContext.getResources() + .getStringArray(R.array.config_componentsSupportMultiInstancesSplit); + } + + @VisibleForTesting + SplitScreenController(Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, + ShellController shellController, + ShellTaskOrganizer shellTaskOrganizer, + SyncTransactionQueue syncQueue, + RootTaskDisplayAreaOrganizer rootTDAOrganizer, + DisplayController displayController, + DisplayImeController displayImeController, + DisplayInsetsController displayInsetsController, + DragAndDropController dragAndDropController, + Transitions transitions, + TransactionPool transactionPool, + IconProvider iconProvider, + RecentTasksController recentTasks, + ShellExecutor mainExecutor, + StageCoordinator stageCoordinator) { + mShellCommandHandler = shellCommandHandler; + mShellController = shellController; + mTaskOrganizer = shellTaskOrganizer; + mSyncQueue = syncQueue; + mContext = context; + mRootTDAOrganizer = rootTDAOrganizer; + mMainExecutor = mainExecutor; + mDisplayController = displayController; + mDisplayImeController = displayImeController; + mDisplayInsetsController = displayInsetsController; + mDragAndDropController = dragAndDropController; + mTransitions = transitions; + mTransactionPool = transactionPool; + mIconProvider = iconProvider; + mRecentTasksOptional = Optional.of(recentTasks); + mStageCoordinator = stageCoordinator; + mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this); + shellInit.addInitCallback(this::onInit, this); + mMultiInstancesComponents = mContext.getResources() + .getStringArray(R.array.config_componentsSupportMultiInstancesSplit); } public SplitScreen asSplitScreen() { @@ -471,72 +521,116 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, startIntent(intent, fillInIntent, position, options); } + private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, + @Nullable Bundle options1, int taskId, @Nullable Bundle options2, + @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, + InstanceId instanceId) { + Intent fillInIntent = null; + if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId) + && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) { + fillInIntent = new Intent(); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + } + mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent, + options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId); + } + + private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1, + int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, + float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + Intent fillInIntent = null; + if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId) + && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) { + fillInIntent = new Intent(); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + } + mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId, + options2, splitPosition, splitRatio, remoteTransition, instanceId); + } + @Override public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { - if (fillInIntent == null) { - fillInIntent = new Intent(); - } - // Flag this as a no-user-action launch to prevent sending user leaving event to the - // current top activity since it's going to be put into another side of the split. This - // prevents the current top activity from going into pip mode due to user leaving event. + // Flag this as a no-user-action launch to prevent sending user leaving event to the current + // top activity since it's going to be put into another side of the split. This prevents the + // current top activity from going into pip mode due to user leaving event. + if (fillInIntent == null) fillInIntent = new Intent(); fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); - // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the - // split and there is no reusable background task. - if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) { - final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent() - ? mRecentTasksOptional.get().findTaskInBackground( - intent.getIntent().getComponent()) - : null; - if (taskInfo != null) { - startTask(taskInfo.taskId, position, options); + if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) { + final ComponentName launching = intent.getIntent().getComponent(); + if (supportMultiInstancesSplit(launching)) { + // To prevent accumulating large number of instances in the background, reuse task + // in the background with priority. + final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional + .map(recentTasks -> recentTasks.findTaskInBackground(launching)) + .orElse(null); + if (taskInfo != null) { + startTask(taskInfo.taskId, position, options); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Start task in background"); + return; + } + + // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of + // the split and there is no reusable background task. + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); + } else if (isSplitScreenVisible()) { + mStageCoordinator.switchSplitPosition("startIntent"); return; } - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } - if (!ENABLE_SHELL_TRANSITIONS) { - mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options); - return; - } mStageCoordinator.startIntent(intent, fillInIntent, position, options); } /** Returns {@code true} if it's launching the same component on both sides of the split. */ - @VisibleForTesting - boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) { - if (startIntent == null) { - return false; - } + private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent, + @SplitPosition int position, int taskId) { + if (pendingIntent == null || pendingIntent.getIntent() == null) return false; + + final ComponentName launchingActivity = pendingIntent.getIntent().getComponent(); + if (launchingActivity == null) return false; - final ComponentName launchingActivity = startIntent.getComponent(); - if (launchingActivity == null) { + if (taskId != INVALID_TASK_ID) { + final ActivityManager.RunningTaskInfo taskInfo = + mTaskOrganizer.getRunningTaskInfo(taskId); + if (taskInfo != null) { + return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); + } return false; } - if (isSplitScreenVisible()) { - // To prevent users from constantly dropping the same app to the same side resulting in - // a large number of instances in the background. - final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position); - final ComponentName targetActivity = targetTaskInfo != null - ? targetTaskInfo.baseIntent.getComponent() : null; - if (Objects.equals(launchingActivity, targetActivity)) { - return false; + if (!isSplitScreenVisible()) { + // Split screen is not yet activated, check if the current top running task is valid to + // split together. + final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo(); + if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) { + return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); } - - // Allow users to start a new instance the same to adjacent side. - final ActivityManager.RunningTaskInfo pairedTaskInfo = - getTaskInfo(SplitLayout.reversePosition(position)); - final ComponentName pairedActivity = pairedTaskInfo != null - ? pairedTaskInfo.baseIntent.getComponent() : null; - return Objects.equals(launchingActivity, pairedActivity); + return false; } - final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo(); - if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) { - return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); + // Compare to the adjacent side of the split to determine if this is launching the same + // component adjacently. + final ActivityManager.RunningTaskInfo pairedTaskInfo = + getTaskInfo(SplitLayout.reversePosition(position)); + final ComponentName pairedActivity = pairedTaskInfo != null + ? pairedTaskInfo.baseIntent.getComponent() : null; + return Objects.equals(launchingActivity, pairedActivity); + } + + @VisibleForTesting + /** Returns {@code true} if the component supports multi-instances split. */ + boolean supportMultiInstancesSplit(@Nullable ComponentName launching) { + if (launching == null) return false; + + final String componentName = launching.flattenToString(); + for (int i = 0; i < mMultiInstancesComponents.length; i++) { + if (mMultiInstancesComponents[i].equals(componentName)) { + return true; + } } return false; @@ -839,14 +933,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, - Intent fillInIntent, Bundle options1, int taskId, Bundle options2, - int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, - InstanceId instanceId) { + Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio, + RemoteAnimationAdapter adapter, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentAndTaskWithLegacyTransition", (controller) -> - controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition( - pendingIntent, fillInIntent, options1, taskId, options2, - splitPosition, splitRatio, adapter, instanceId)); + controller.startIntentAndTaskWithLegacyTransition(pendingIntent, + options1, taskId, options2, splitPosition, splitRatio, adapter, + instanceId)); } @Override @@ -872,14 +965,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override - public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent, - @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, float splitRatio, - @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1, + int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, + float splitRatio, @Nullable RemoteTransition remoteTransition, + InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentAndTask", - (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent, - fillInIntent, options1, taskId, options2, splitPosition, splitRatio, - remoteTransition, instanceId)); + (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId, + options2, splitPosition, splitRatio, remoteTransition, instanceId)); } @Override 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 e888c6f8b0f9..c2ab7ef7e7bf 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 @@ -24,7 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; @@ -428,6 +427,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Launches an activity into split. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { + if (!ENABLE_SHELL_TRANSITIONS) { + startIntentLegacy(intent, fillInIntent, position, options); + return; + } + final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictChildTasks(position, evictWct); @@ -441,13 +445,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(wct, null /* taskInfo */, position); mSplitTransitions.startEnterTransition(transitType, wct, null, this, - aborted -> { - // Switch the split position if launching as MULTIPLE_TASK failed. - if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { - setSideStagePositionAnimated( - SplitLayout.reversePosition(mSideStagePosition)); - } - } /* consumedCallback */, + null /* consumedCallback */, (finishWct, finishT) -> { if (!evictWct.isEmpty()) { finishWct.merge(evictWct, true); @@ -457,7 +455,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Launches an activity into split by legacy transition. */ void startIntentLegacy(PendingIntent intent, Intent fillInIntent, - @SplitPosition int position, @androidx.annotation.Nullable Bundle options) { + @SplitPosition int position, @Nullable Bundle options) { final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictChildTasks(position, evictWct); @@ -473,12 +471,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, exitSplitScreen(mMainStage.getChildCount() == 0 ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); mSplitUnsupportedToast.show(); - } else { - // Switch the split position if launching as MULTIPLE_TASK failed. - if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { - setSideStagePosition(SplitLayout.reversePosition( - getSideStagePosition()), null); - } } // Do nothing when the animation was cancelled. @@ -771,9 +763,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.evictInvisibleChildren(wct); } - Bundle resolveStartStage(@StageType int stage, - @SplitPosition int position, @androidx.annotation.Nullable Bundle options, - @androidx.annotation.Nullable WindowContainerTransaction wct) { + Bundle resolveStartStage(@StageType int stage, @SplitPosition int position, + @Nullable Bundle options, @Nullable WindowContainerTransaction wct) { switch (stage) { case STAGE_TYPE_UNDEFINED: { if (position != SPLIT_POSITION_UNDEFINED) { @@ -844,9 +835,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, : mMainStage.getTopVisibleChildTaskId(); } - void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) { - if (mSideStagePosition == sideStagePosition) return; - SurfaceControl.Transaction t = mTransactionPool.acquire(); + void switchSplitPosition(String reason) { + final SurfaceControl.Transaction t = mTransactionPool.acquire(); mTempRect1.setEmpty(); final StageTaskListener topLeftStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; @@ -886,6 +876,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, va.start(); }); }); + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason); + mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), + getSideStagePosition(), mSideStage.getTopChildTaskUid(), + mSplitLayout.isLandscape()); } void setSideStagePosition(@SplitPosition int sideStagePosition, @@ -1617,10 +1612,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onDoubleTappedDivider() { - setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition)); - mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), - getSideStagePosition(), mSideStage.getTopChildTaskUid(), - mSplitLayout.isLandscape()); + switchSplitPosition("double tap"); } @Override diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index d01f3d310fc3..38b75f81171f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -16,18 +16,24 @@ package com.android.wm.shell.splitscreen; +import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -35,6 +41,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -65,11 +73,11 @@ import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Optional; - /** * Tests for {@link SplitScreenController} */ @@ -91,18 +99,21 @@ public class SplitScreenControllerTests extends ShellTestCase { @Mock Transitions mTransitions; @Mock TransactionPool mTransactionPool; @Mock IconProvider mIconProvider; - @Mock Optional<RecentTasksController> mRecentTasks; + @Mock StageCoordinator mStageCoordinator; + @Mock RecentTasksController mRecentTasks; + @Captor ArgumentCaptor<Intent> mIntentCaptor; private SplitScreenController mSplitScreenController; @Before public void setup() { + assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)); MockitoAnnotations.initMocks(this); mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit, mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool, - mIconProvider, mRecentTasks, mMainExecutor)); + mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator)); } @Test @@ -148,58 +159,100 @@ public class SplitScreenControllerTests extends ShellTestCase { } @Test - public void testShouldAddMultipleTaskFlag_notInSplitScreen() { - doReturn(false).when(mSplitScreenController).isSplitScreenVisible(); - doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any()); + public void testStartIntent_appendsNoUserActionFlag() { + Intent startIntent = createStartIntent("startActivity"); + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); - // Verify launching the same activity returns true. + mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null); + + verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(), + eq(SPLIT_POSITION_TOP_OR_LEFT), isNull()); + assertEquals(FLAG_ACTIVITY_NO_USER_ACTION, + mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION); + } + + @Test + public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() { + doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any()); Intent startIntent = createStartIntent("startActivity"); + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); + // Put the same component into focus task ActivityManager.RunningTaskInfo focusTaskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo(); - assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag( - startIntent, SPLIT_POSITION_TOP_OR_LEFT)); - - // Verify launching different activity returns false. - Intent diffIntent = createStartIntent("diffActivity"); - focusTaskInfo = - createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent); - doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo(); - assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag( - startIntent, SPLIT_POSITION_TOP_OR_LEFT)); + doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo(); + doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any()); + + mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null); + + verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(), + eq(SPLIT_POSITION_TOP_OR_LEFT), isNull()); + assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK, + mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK); } @Test - public void testShouldAddMultipleTaskFlag_inSplitScreen() { + public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() { + doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any()); + doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any()); + Intent startIntent = createStartIntent("startActivity"); + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); + // Put the same component into focus task + ActivityManager.RunningTaskInfo focusTaskInfo = + createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); + doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo(); + doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any()); + // Put the same component into a task in the background + ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo(); + doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any()); + + mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null); + + verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), + isNull()); + } + + @Test + public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() { + doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any()); + doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any()); + Intent startIntent = createStartIntent("startActivity"); + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); + // Put the same component into another side of the split doReturn(true).when(mSplitScreenController).isSplitScreenVisible(); + ActivityManager.RunningTaskInfo sameTaskInfo = + createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent); + doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo( + SPLIT_POSITION_BOTTOM_OR_RIGHT); + // Put the same component into a task in the background + doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks) + .findTaskInBackground(any()); + + mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null); + + verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), + isNull()); + } + + @Test + public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() { + doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any()); Intent startIntent = createStartIntent("startActivity"); + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); + // Put the same component into another side of the split + doReturn(true).when(mSplitScreenController).isSplitScreenVisible(); ActivityManager.RunningTaskInfo sameTaskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent); - Intent diffIntent = createStartIntent("diffActivity"); - ActivityManager.RunningTaskInfo differentTaskInfo = - createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent); - - // Verify launching the same activity return false. - doReturn(sameTaskInfo).when(mSplitScreenController) - .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); - assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag( - startIntent, SPLIT_POSITION_TOP_OR_LEFT)); - - // Verify launching the same activity as adjacent returns true. - doReturn(differentTaskInfo).when(mSplitScreenController) - .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); - doReturn(sameTaskInfo).when(mSplitScreenController) - .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); - assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag( - startIntent, SPLIT_POSITION_TOP_OR_LEFT)); - - // Verify launching different activity from adjacent returns false. - doReturn(differentTaskInfo).when(mSplitScreenController) - .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); - doReturn(differentTaskInfo).when(mSplitScreenController) - .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); - assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag( - startIntent, SPLIT_POSITION_TOP_OR_LEFT)); + doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo( + SPLIT_POSITION_BOTTOM_OR_RIGHT); + + mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null); + + verify(mStageCoordinator).switchSplitPosition(anyString()); } private Intent createStartIntent(String activityName) { diff --git a/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml new file mode 100644 index 000000000000..46abff81d38f --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml @@ -0,0 +1,33 @@ +<!-- + 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:viewportWidth="22" + android:viewportHeight="17" + android:width="22dp" + android:height="17dp"> + <group> + <group> + <path android:fillColor="#FF000000" + android:pathData="M1.03 8.47l0.43-4.96h4.33v1.17H2.48L2.25 7.39C2.66 7.1 3.1 6.96 3.57 6.96c0.77 0 1.38 0.3 1.83 0.9 s0.66 1.41 0.66 2.43c0 1.03-0.24 1.84-0.72 2.43S4.2 13.6 3.36 13.6c-0.75 0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07 0.57 0.23 1 0.49 1.29s0.59 0.43 1.01 0.43c0.47 0 0.84-0.2 1.1-0.61c0.26-0.41 0.4-0.96 0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.76 8.09 3.28 8.09c-0.4 0-0.72 0.1-0.96 0.31L1.99 8.73L1.03 8.47z"/> + </group> + <group> + <path android:fillColor="#FF000000" + android:pathData="M 18.93,5.74 L 18.93,3.39 L 17.63,3.39 L 17.63,5.74 L 15.28,5.74 L 15.28,7.04 L 17.63,7.04 L 17.63,9.39 L 18.93,9.39 L 18.93,7.04 L 21.28,7.04 L 21.28,5.74 z"/> + </group> + <path android:fillColor="#FF000000" + android:pathData="M13.78 12.24l-0.22 0.27c-0.63 0.73-1.55 1.1-2.76 1.1c-1.08 0-1.92-0.36-2.53-1.07s-0.93-1.72-0.94-3.02V7.56 c0-1.39 0.28-2.44 0.84-3.13s1.39-1.04 2.51-1.04c0.95 0 1.69 0.26 2.23 0.79s0.83 1.28 0.89 2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45s-0.74-0.52-1.34-0.52c-0.72 0-1.24 0.23-1.57 0.7S8.6 6.37 8.59 7.4v2.03c0 1 0.19 1.77 0.57 2.31 c0.38 0.54 0.93 0.8 1.65 0.8c0.67 0 1.19-0.16 1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z"/> + </group> +</vector> diff --git a/packages/SettingsLib/res/values/carrierid_icon_overrides.xml b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml new file mode 100644 index 000000000000..d2ae52d8347a --- /dev/null +++ b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml @@ -0,0 +1,32 @@ +<?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. + --> +<!-- + ~ This resource file exists to enumerate all network type icon overrides on a + ~ per-carrierId basis +--> +<resources> + <!-- + Network type (RAT) icon overrides can be configured here on a per-carrierId basis. + 1. Add a new TypedArray here, using the naming scheme below + 2. The entries are (NetworkType, drawable ID) pairs + 3. Add this array's ID to the MAPPING field of MobileIconCarrierIdOverrides.kt + --> + <array name="carrierId_2032_iconOverrides"> + <item>5G_PLUS</item> + <item>@drawable/ic_5g_plus_mobiledata_default</item> + </array> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt index 5fa04f93e993..faea5b2c3e71 100644 --- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt +++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt @@ -412,14 +412,13 @@ open class ThemedBatteryDrawable(private val context: Context, frameColor: Int) } companion object { - private const val TAG = "ThemedBatteryDrawable" - private const val WIDTH = 12f - private const val HEIGHT = 20f + const val WIDTH = 12f + const val HEIGHT = 20f private const val CRITICAL_LEVEL = 15 // On a 12x20 grid, how wide to make the fill protection stroke. // Scales when our size changes private const val PROTECTION_STROKE_WIDTH = 3f // Arbitrarily chosen for visibility at small sizes - private const val PROTECTION_MIN_STROKE_WIDTH = 6f + const val PROTECTION_MIN_STROKE_WIDTH = 6f } } diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt new file mode 100644 index 000000000000..a0395b559291 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt @@ -0,0 +1,142 @@ +/* + * 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.settingslib.mobile + +import android.annotation.DrawableRes +import android.content.res.Resources +import android.content.res.TypedArray +import android.util.Log +import androidx.annotation.VisibleForTesting +import com.android.settingslib.R +import com.android.settingslib.SignalIcon.MobileIconGroup + +/** + * This class defines a network type (3G, 4G, etc.) override mechanism on a per-carrierId basis. + * + * Traditionally, carrier-customized network type iconography was achieved using the `MCC/MNC` + * resource qualifiers, and swapping out the drawable resource by name. It would look like this: + * + * res/ + * drawable/ + * 3g_mobiledata_icon.xml + * drawable-MCC-MNC/ + * 3g_mobiledata_icon.xml + * + * This would mean that, provided a context created with this MCC/MNC configuration set, loading + * the network type icon through [MobileIconGroup] would provide a carrier-defined network type + * icon rather than the AOSP-defined default. + * + * The MCC/MNC mechanism no longer can fully define carrier-specific network type icons, because + * there is no longer a 1:1 mapping between MCC/MNC and carrier. With the advent of MVNOs, multiple + * carriers can have the same MCC/MNC value, but wish to differentiate based on their carrier ID. + * CarrierId is a newer concept than MCC/MNC, and provides more granularity when it comes to + * determining the carrier (e.g. MVNOs can share MCC/MNC values with the network owner), therefore + * it can fit all of the same use cases currently handled by `MCC/MNC`, without the need to apply a + * configuration context in order to get the proper UI for a given SIM icon. + * + * NOTE: CarrierId icon overrides will always take precedence over those defined using `MCC/MNC` + * resource qualifiers. + * + * [MAPPING] encodes the relationship between CarrierId and the corresponding override array + * that exists in the config.xml. An alternative approach could be to generate the resource name + * by string concatenation at run-time: + * + * val resName = "carrierId_$carrierId_iconOverrides" + * val override = resources.getResourceIdentifier(resName) + * + * However, that's going to be far less efficient until MAPPING grows to a sufficient size. For now, + * given a relatively small number of entries, we should just maintain the mapping here. + */ +interface MobileIconCarrierIdOverrides { + @DrawableRes + fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int + fun carrierIdEntryExists(carrierId: Int): Boolean +} + +class MobileIconCarrierIdOverridesImpl : MobileIconCarrierIdOverrides { + @DrawableRes + override fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int { + val resId = MAPPING[carrierId] ?: return 0 + val ta = resources.obtainTypedArray(resId) + val map = parseNetworkIconOverrideTypedArray(ta) + ta.recycle() + return map[networkType] ?: 0 + } + + override fun carrierIdEntryExists(carrierId: Int) = + overrideExists(carrierId, MAPPING) + + companion object { + private const val TAG = "MobileIconOverrides" + /** + * This map maintains the lookup from the canonical carrier ID (see below link) to the + * corresponding overlay resource. New overrides should add an entry below in order to + * change the network type icon resources based on carrier ID + * + * Refer to the link below for the canonical mapping maintained in AOSP: + * https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb + */ + private val MAPPING = mapOf( + // 2032 == Xfinity Mobile + 2032 to R.array.carrierId_2032_iconOverrides, + ) + + /** + * Parse `carrierId_XXXX_iconOverrides` for a particular network type. The resource file + * "carrierid_icon_overrides.xml" defines a TypedArray format for overriding specific + * network type icons (a.k.a. RAT icons) for a particular carrier ID. The format is defined + * as an array of (network type name, drawable) pairs: + * <array name="carrierId_XXXX_iconOverrides> + * <item>NET_TYPE_1</item> + * <item>@drawable/net_type_1_override</item> + * <item>NET_TYPE_2</item> + * <item>@drawable/net_type_2_override</item> + * </array> + * + * @param ta the [TypedArray] defined in carrierid_icon_overrides.xml + * @return the overridden drawable resource ID if it exists, or 0 if it does not + */ + @VisibleForTesting + @JvmStatic + fun parseNetworkIconOverrideTypedArray(ta: TypedArray): Map<String, Int> { + if (ta.length() % 2 != 0) { + Log.w(TAG, + "override must contain an even number of (key, value) entries. skipping") + + return mapOf() + } + + val result = mutableMapOf<String, Int>() + // The array is defined as Pair(String, resourceId), so walk by 2 + for (i in 0 until ta.length() step 2) { + val key = ta.getString(i) + val override = ta.getResourceId(i + 1, 0) + if (key == null || override == 0) { + Log.w(TAG, "Invalid override found. Skipping") + continue + } + result[key] = override + } + + return result + } + + @JvmStatic + private fun overrideExists(carrierId: Int, mapping: Map<Int, Int>): Boolean = + mapping.containsKey(carrierId) + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java new file mode 100644 index 000000000000..740261d3bac2 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java @@ -0,0 +1,140 @@ +/* + * 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.settingslib.mobile; + +import static com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl.parseNetworkIconOverrideTypedArray; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.res.TypedArray; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.util.Map; + +@RunWith(RobolectricTestRunner.class) +public final class MobileIconCarrierIdOverridesTest { + private static final String OVERRIDE_ICON_1_NAME = "name_1"; + private static final int OVERRIDE_ICON_1_RES = 1; + + private static final String OVERRIDE_ICON_2_NAME = "name_2"; + private static final int OVERRIDE_ICON_2_RES = 2; + + NetworkOverrideTypedArrayMock mResourceMock; + + @Before + public void setUp() { + mResourceMock = new NetworkOverrideTypedArrayMock( + new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME }, + new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES } + ); + } + + @Test + public void testParse_singleOverride() { + mResourceMock.setOverrides( + new String[] { OVERRIDE_ICON_1_NAME }, + new int[] { OVERRIDE_ICON_1_RES } + ); + + Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock()); + + assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES); + } + + @Test + public void testParse_multipleOverrides() { + mResourceMock.setOverrides( + new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME }, + new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES } + ); + + Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock()); + + assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isEqualTo(OVERRIDE_ICON_2_RES); + assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES); + } + + @Test + public void testParse_nonexistentKey_isNull() { + mResourceMock.setOverrides( + new String[] { OVERRIDE_ICON_1_NAME }, + new int[] { OVERRIDE_ICON_1_RES } + ); + + Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock()); + + assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isNull(); + } + + static class NetworkOverrideTypedArrayMock { + private Object[] mInterleaved; + + private final TypedArray mMockTypedArray = mock(TypedArray.class); + + NetworkOverrideTypedArrayMock( + String[] networkTypes, + int[] iconOverrides) { + + mInterleaved = interleaveTypes(networkTypes, iconOverrides); + + doAnswer(invocation -> { + return mInterleaved[(int) invocation.getArgument(0)]; + }).when(mMockTypedArray).getString(/* index */ anyInt()); + + doAnswer(invocation -> { + return mInterleaved[(int) invocation.getArgument(0)]; + }).when(mMockTypedArray).getResourceId(/* index */ anyInt(), /* default */ anyInt()); + + when(mMockTypedArray.length()).thenAnswer(invocation -> { + return mInterleaved.length; + }); + } + + TypedArray getMock() { + return mMockTypedArray; + } + + void setOverrides(String[] types, int[] resIds) { + mInterleaved = interleaveTypes(types, resIds); + } + + private Object[] interleaveTypes(String[] strs, int[] ints) { + assertThat(strs.length).isEqualTo(ints.length); + + Object[] ret = new Object[strs.length * 2]; + + // Keep track of where we are in the interleaved array, but iterate the overrides + int interleavedIndex = 0; + for (int i = 0; i < strs.length; i++) { + ret[interleavedIndex] = strs[i]; + interleavedIndex += 1; + ret[interleavedIndex] = ints[i]; + interleavedIndex += 1; + } + return ret; + } + } +} diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml index a4e7a5f12db4..f1aa54412b3b 100644 --- a/packages/SystemUI/res-keyguard/values-land/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml @@ -27,4 +27,6 @@ <integer name="scaled_password_text_size">26</integer> <dimen name="bouncer_user_switcher_y_trans">@dimen/status_bar_height</dimen> + <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen> + <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen> </resources> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 0a55cf779683..3861d983b309 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -124,6 +124,8 @@ <dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen> <dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen> <dimen name="bouncer_user_switcher_y_trans">0dp</dimen> + <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen> + <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen> <!-- 2 * the margin + size should equal the plus_margin --> <dimen name="user_switcher_icon_large_margin">16dp</dimen> diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index 5b961595fbd3..ae2537fe29f6 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -190,6 +190,11 @@ </LinearLayout> + <ViewStub android:id="@+id/secondary_mobile_network_stub" + android:inflatedId="@+id/secondary_mobile_network_layout" + android:layout="@layout/qs_dialog_secondary_mobile_network" + style="@style/InternetDialog.Network"/> + <LinearLayout android:id="@+id/turn_on_wifi_layout" style="@style/InternetDialog.Network" diff --git a/packages/SystemUI/res/layout/qs_dialog_secondary_mobile_network.xml b/packages/SystemUI/res/layout/qs_dialog_secondary_mobile_network.xml new file mode 100644 index 000000000000..4592c5e6332c --- /dev/null +++ b/packages/SystemUI/res/layout/qs_dialog_secondary_mobile_network.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + style="@style/InternetDialog.Network"> + + <FrameLayout + android:layout_width="24dp" + android:layout_height="24dp" + android:clickable="false" + android:layout_gravity="center_vertical|start"> + <ImageView + android:id="@+id/secondary_signal_icon" + android:autoMirrored="true" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"/> + </FrameLayout> + + <LinearLayout + android:layout_weight="1" + android:orientation="vertical" + android:clickable="false" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="start|center_vertical"> + <TextView + android:id="@+id/secondary_mobile_title" + android:maxLines="1" + style="@style/InternetDialog.NetworkTitle"/> + <TextView + android:id="@+id/secondary_mobile_summary" + style="@style/InternetDialog.NetworkSummary"/> + </LinearLayout> + + <FrameLayout + android:layout_width="24dp" + android:layout_height="match_parent" + android:clickable="false" + android:layout_gravity="end|center_vertical" + android:gravity="center"> + <ImageView + android:id="@+id/secondary_settings_icon" + android:src="@drawable/ic_settings_24dp" + android:layout_width="24dp" + android:layout_gravity="end|center_vertical" + android:layout_height="wrap_content"/> + </FrameLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml index d9df3373bef1..707bc9e535ff 100644 --- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml @@ -17,7 +17,6 @@ <resources> <dimen name="notification_panel_margin_horizontal">48dp</dimen> <dimen name="status_view_margin_horizontal">62dp</dimen> - <dimen name="bouncer_user_switcher_y_trans">20dp</dimen> <!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two pages is margin * 2, and that makes tiles page not appear immediately after user swipes to diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml index 97ead01669a9..b98165fb08f0 100644 --- a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml @@ -21,4 +21,6 @@ <!-- Space between status view and notification shelf --> <dimen name="keyguard_status_view_bottom_margin">70dp</dimen> <dimen name="keyguard_clock_top_margin">80dp</dimen> + <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">186dp</dimen> + <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">110dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml index 17f82b50d7be..8b41a44b9ba3 100644 --- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml @@ -21,7 +21,6 @@ for different hardware and product builds. --> <resources> <dimen name="status_view_margin_horizontal">124dp</dimen> - <dimen name="bouncer_user_switcher_y_trans">200dp</dimen> <dimen name="large_screen_shade_header_left_padding">24dp</dimen> <dimen name="qqs_layout_padding_bottom">40dp</dimen> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index ce9829b318cd..55d637916d0c 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -485,6 +485,12 @@ <!-- Whether to show a severe low battery dialog. --> <bool name="config_severe_battery_dialog">false</bool> + <!-- A path representing a shield. Will sometimes be displayed with the battery icon when + needed. This path is a 10px wide and 13px tall. --> + <string name="config_batterymeterShieldPath" translatable="false"> + M5 0L0 1.88V6.19C0 9.35 2.13 12.29 5 13.01C7.87 12.29 10 9.35 10 6.19V1.88L5 0Z + </string> + <!-- A path similar to frameworks/base/core/res/res/values/config.xml config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index e8ae929a6782..c78d36dd3685 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -105,6 +105,12 @@ so the width of the icon should be 13.0dp * (12.0 / 20.0) --> <dimen name="status_bar_battery_icon_width">7.8dp</dimen> + <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see + @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in + the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its + bottom still aligns with the bottom of all the other system icons. See b/258672854. --> + <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen> + <!-- The font size for the clock in the status bar. --> <dimen name="status_bar_clock_size">14sp</dimen> @@ -1478,10 +1484,12 @@ <!-- Dream overlay complications related dimensions --> <dimen name="dream_overlay_complication_clock_time_text_size">86sp</dimen> + <dimen name="dream_overlay_complication_clock_time_padding">20dp</dimen> <dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen> <dimen name="dream_overlay_complication_preview_text_size">36sp</dimen> <dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen> <dimen name="dream_overlay_complication_shadow_padding">2dp</dimen> + <dimen name="dream_overlay_complication_smartspace_padding">24dp</dimen> <!-- The position of the end guide, which dream overlay complications can align their start with if their end is aligned with the parent end. Represented as the percentage over from the diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index dc84435f4081..4146e20cb345 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -316,7 +316,7 @@ <!-- Content description of the QR Code scanner for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_qr_code_scanner_button">QR Code Scanner</string> <!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_unlock_button">Unlock</string> + <string name="accessibility_unlock_button">Unlocked</string> <!-- Content description of the lock icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_lock_icon">Device locked</string> <!-- Content description hint of the unlock button when fingerprint is on (not shown on the screen). [CHAR LIMIT=NONE] --> @@ -439,11 +439,17 @@ <string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string> <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$s</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string> + <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string> <!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string> + <!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent. Charging paused for battery protection.</string> + + <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage. Charging paused for battery protection.</string> + <!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] --> <string name="accessibility_overflow_action">See all notifications</string> @@ -2041,6 +2047,15 @@ <!-- Text used to refer to the user's current carrier in mobile_data_disable_message if the users's mobile network carrier name is not available [CHAR LIMIT=NONE] --> <string name="mobile_data_disable_message_default_carrier">your carrier</string> + <!-- Title of the dialog to turn off data usage [CHAR LIMIT=NONE] --> + <string name="auto_data_switch_disable_title">Switch back to <xliff:g id="carrier" example="T-Mobile">%s</xliff:g>?</string> + <!-- Message body of the dialog to turn off data usage [CHAR LIMIT=NONE] --> + <string name="auto_data_switch_disable_message">Mobile data won\’t automatically switch based on availability</string> + <!-- Negative button title of the quick settings switch back to DDS dialog [CHAR LIMIT=NONE] --> + <string name="auto_data_switch_dialog_negative_button">No thanks</string> + <!-- Positive button title of the quick settings switch back to DDS dialog [CHAR LIMIT=NONE] --> + <string name="auto_data_switch_dialog_positive_button">Yes, switch</string> + <!-- Warning shown when user input has been blocked due to another app overlaying screen content. Since we don't know what the app is showing on top of the input target, we can't verify user consent. [CHAR LIMIT=NONE] --> @@ -2511,6 +2526,12 @@ Summary indicating that a SIM has an active mobile data connection [CHAR LIMIT=50] --> <string name="mobile_data_connection_active">Connected</string> <!-- Provider Model: + Summary indicating that a SIM is temporarily connected to mobile data [CHAR LIMIT=50] --> + <string name="mobile_data_temp_connection_active">Temporarily connected</string> + <!-- Provider Model: + Summary indicating that a SIM is temporarily connected to mobile data [CHAR LIMIT=50] --> + <string name="mobile_data_poor_connection">Poor connection</string> + <!-- Provider Model: Summary indicating that a SIM has no mobile data connection [CHAR LIMIT=50] --> <string name="mobile_data_off_summary">Mobile data won\u0027t auto\u2011connect</string> <!-- Provider Model: diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml index af4be1ade656..e07a6c1dbc46 100644 --- a/packages/SystemUI/res/xml/qqs_header.xml +++ b/packages/SystemUI/res/xml/qqs_header.xml @@ -25,7 +25,7 @@ android:id="@+id/clock"> <Layout android:layout_width="wrap_content" - android:layout_height="0dp" + android:layout_height="@dimen/large_screen_shade_header_min_height" app:layout_constraintStart_toStartOf="@id/begin_guide" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" @@ -42,7 +42,7 @@ <Constraint android:id="@+id/date"> <Layout - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" android:layout_marginStart="8dp" app:layout_constrainedWidth="true" @@ -57,7 +57,7 @@ <Constraint android:id="@+id/statusIcons"> <Layout - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height" app:layout_constraintStart_toEndOf="@id/date" @@ -65,6 +65,7 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="1" + app:layout_constraintHorizontal_chainStyle="packed" /> </Constraint> @@ -80,12 +81,16 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="1" + app:layout_constraintHorizontal_chainStyle="packed" /> </Constraint> <Constraint android:id="@+id/carrier_group"> <Layout + app:layout_constraintWidth_min="48dp" + android:layout_width="wrap_content" + android:layout_height="@dimen/large_screen_shade_header_min_height" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml index d8a4e7752960..982c422f1fda 100644 --- a/packages/SystemUI/res/xml/qs_header_new.xml +++ b/packages/SystemUI/res/xml/qs_header_new.xml @@ -43,6 +43,7 @@ app:layout_constraintBottom_toBottomOf="@id/carrier_group" app:layout_constraintEnd_toStartOf="@id/carrier_group" app:layout_constraintHorizontal_bias="0" + app:layout_constraintHorizontal_chainStyle="spread_inside" /> <Transform android:scaleX="2.57" @@ -53,7 +54,7 @@ <Constraint android:id="@+id/date"> <Layout - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/space" @@ -67,16 +68,15 @@ <Constraint android:id="@+id/carrier_group"> <Layout - app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height" - android:minHeight="@dimen/large_screen_shade_header_min_height" app:layout_constraintWidth_min="48dp" - android:layout_width="0dp" - android:layout_height="0dp" + android:layout_width="wrap_content" + android:layout_height="@dimen/large_screen_shade_header_min_height" app:layout_constraintStart_toEndOf="@id/clock" app:layout_constraintTop_toBottomOf="@id/privacy_container" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1" app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon" + app:layout_constraintHorizontal_chainStyle="spread_inside" /> <PropertySet android:alpha="1" @@ -86,7 +86,7 @@ <Constraint android:id="@+id/statusIcons"> <Layout - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" app:layout_constrainedWidth="true" app:layout_constraintStart_toEndOf="@id/space" @@ -108,6 +108,7 @@ app:layout_constraintTop_toTopOf="@id/date" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="1" + app:layout_constraintHorizontal_chainStyle="spread_inside" /> </Constraint> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 485a0d320bb9..9d3a293dd662 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -52,11 +52,6 @@ android_library { "SystemUIUnfoldLib", "androidx.dynamicanimation_dynamicanimation", "androidx.concurrent_concurrent-futures", - "androidx.lifecycle_lifecycle-runtime-ktx", - "androidx.lifecycle_lifecycle-viewmodel-ktx", - "androidx.recyclerview_recyclerview", - "kotlinx_coroutines_android", - "kotlinx_coroutines", "dagger2", "jsr330", ], @@ -68,7 +63,6 @@ android_library { }, min_sdk_version: "current", plugins: ["dagger2-compiler"], - kotlincflags: ["-Xjvm-default=enable"], } java_library { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index db64f05ccbea..8fa7b11e2664 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -68,7 +68,7 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> private final KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @Override - public void onTrustGrantedWithFlags(int flags, int userId) { + public void onTrustGrantedWithFlags(int flags, int userId, String message) { if (userId != KeyguardUpdateMonitor.getCurrentUser()) return; boolean bouncerVisible = mView.isVisibleToUser(); boolean temporaryAndRenewable = diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 73229c321079..faaba63938bf 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.Resources; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import android.view.inputmethod.InputMethodManager; @@ -152,7 +153,9 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> } public void startAppearAnimation() { - mMessageAreaController.setMessage(getInitialMessageResId()); + if (TextUtils.isEmpty(mMessageAreaController.getMessage())) { + mMessageAreaController.setMessage(getInitialMessageResId()); + } mView.startAppearAnimation(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index 2bd3ca59b740..db986e0a631a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -103,6 +103,11 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> mView.setNextMessageColor(colorState); } + /** Returns the message of the underlying TextView. */ + public CharSequence getMessage() { + return mView.getText(); + } + /** * Reload colors from resources. **/ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 2bb3a5f437f5..5c4126eeb93a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -727,6 +727,11 @@ public class KeyguardSecurityContainer extends ConstraintLayout { mViewMode.reloadColors(); } + /** Handles density or font scale changes. */ + void onDensityOrFontScaleChanged() { + mViewMode.onDensityOrFontScaleChanged(); + } + /** * Enscapsulates the differences between bouncer modes for the container. */ @@ -752,6 +757,9 @@ public class KeyguardSecurityContainer extends ConstraintLayout { /** Refresh colors */ default void reloadColors() {}; + /** Handles density or font scale changes. */ + default void onDensityOrFontScaleChanged() {} + /** On a successful auth, optionally handle how the view disappears */ default void startDisappearAnimation(SecurityMode securityMode) {}; @@ -899,14 +907,9 @@ public class KeyguardSecurityContainer extends ConstraintLayout { mFalsingA11yDelegate = falsingA11yDelegate; if (mUserSwitcherViewGroup == null) { - LayoutInflater.from(v.getContext()).inflate( - R.layout.keyguard_bouncer_user_switcher, - mView, - true); - mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher); + inflateUserSwitcher(); } updateSecurityViewLocation(); - mUserSwitcher = mView.findViewById(R.id.user_switcher_header); setupUserSwitcher(); mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback); } @@ -937,6 +940,12 @@ public class KeyguardSecurityContainer extends ConstraintLayout { } @Override + public void onDensityOrFontScaleChanged() { + mView.removeView(mUserSwitcherViewGroup); + inflateUserSwitcher(); + } + + @Override public void onDestroy() { mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback); } @@ -1097,11 +1106,19 @@ public class KeyguardSecurityContainer extends ConstraintLayout { new KeyguardSecurityViewTransition()); } int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans); + int viewFlipperBottomMargin = mResources.getDimensionPixelSize( + R.dimen.bouncer_user_switcher_view_mode_view_flipper_bottom_margin); + int userSwitcherBottomMargin = mResources.getDimensionPixelSize( + R.dimen.bouncer_user_switcher_view_mode_user_switcher_bottom_margin); if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { ConstraintSet constraintSet = new ConstraintSet(); constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP, yTrans); - constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP); - constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM); + constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, mViewFlipper.getId(), + TOP, userSwitcherBottomMargin); + constraintSet.connect(mViewFlipper.getId(), TOP, mUserSwitcherViewGroup.getId(), + BOTTOM); + constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM, + viewFlipperBottomMargin); constraintSet.centerHorizontally(mViewFlipper.getId(), PARENT_ID); constraintSet.centerHorizontally(mUserSwitcherViewGroup.getId(), PARENT_ID); constraintSet.setVerticalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD); @@ -1137,6 +1154,15 @@ public class KeyguardSecurityContainer extends ConstraintLayout { } } + private void inflateUserSwitcher() { + LayoutInflater.from(mView.getContext()).inflate( + R.layout.keyguard_bouncer_user_switcher, + mView, + true); + mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher); + mUserSwitcher = mView.findViewById(R.id.user_switcher_header); + } + interface UserSwitcherCallback { void showUnlockToContinueMessage(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 7a49926f8ef1..01be33e1e156 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -251,6 +251,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void onUiModeChanged() { reloadColors(); } + + @Override + public void onDensityOrFontScaleChanged() { + KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged(); + } }; private boolean mBouncerVisible = false; private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = @@ -727,6 +732,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mView.reloadColors(); } + /** Handles density or font scale changes. */ + private void onDensityOrFontScaleChanged() { + mSecurityViewFlipperController.onDensityOrFontScaleChanged(); + mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode, + mKeyguardSecurityCallback); + mView.onDensityOrFontScaleChanged(); + } + static class Factory { private final KeyguardSecurityContainer mView; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java index bddf4b09ebb3..25afe11ac536 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java @@ -83,6 +83,13 @@ public class KeyguardSecurityViewFlipperController } } + /** Handles density or font scale changes. */ + public void onDensityOrFontScaleChanged() { + mView.removeAllViews(); + mChildren.clear(); + } + + @VisibleForTesting KeyguardInputViewController<KeyguardInputView> getSecurityView(SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 83e23bd52f19..8b9823be65fd 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -17,6 +17,7 @@ package com.android.keyguard; import android.content.Context; +import android.os.Trace; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -112,4 +113,11 @@ public class KeyguardStatusView extends GridLayout { mKeyguardSlice.dump(pw, args); } } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Trace.beginSection("KeyguardStatusView#onMeasure"); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + Trace.endSection(); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 47ea8784aae8..c8bcbbdb6883 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -486,19 +486,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab FACE_AUTH_TRIGGERED_TRUST_DISABLED); } - mLogger.logTrustChanged(wasTrusted, enabled, userId); - for (int i = 0; i < mCallbacks.size(); i++) { - KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); - if (cb != null) { - cb.onTrustChanged(userId); - if (enabled && flags != 0) { - cb.onTrustGrantedWithFlags(flags, userId); - } - } - } - + String message = null; if (KeyguardUpdateMonitor.getCurrentUser() == userId) { - CharSequence message = null; final boolean userHasTrust = getUserHasTrust(userId); if (userHasTrust && trustGrantedMessages != null) { for (String msg : trustGrantedMessages) { @@ -508,14 +497,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } } - - if (message != null) { - mLogger.logShowTrustGrantedMessage(message.toString()); - } - for (int i = 0; i < mCallbacks.size(); i++) { - KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); - if (cb != null) { - cb.showTrustGrantedMessage(message); + } + mLogger.logTrustChanged(wasTrusted, enabled, userId); + if (message != null) { + mLogger.logShowTrustGrantedMessage(message.toString()); + } + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onTrustChanged(userId); + if (enabled) { + cb.onTrustGrantedWithFlags(flags, userId, message); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index c06e1dcf08c2..c5142f309a46 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -174,14 +174,12 @@ public class KeyguardUpdateMonitorCallback { public void onTrustManagedChanged(int userId) { } /** - * Called after trust was granted with non-zero flags. + * Called after trust was granted. + * @param userId of the user that has been granted trust + * @param message optional message the trust agent has provided to show that should indicate + * why trust was granted. */ - public void onTrustGrantedWithFlags(int flags, int userId) { } - - /** - * Called when setting the trust granted message. - */ - public void showTrustGrantedMessage(@Nullable CharSequence message) { } + public void onTrustGrantedWithFlags(int flags, int userId, @Nullable String message) { } /** * Called when a biometric has been acquired. diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt index 5d52056d8b17..90ecb466b5d0 100644 --- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt @@ -169,7 +169,7 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { return } cutoutPath.reset() - display.getDisplayInfo(displayInfo) + context.display?.getDisplayInfo(displayInfo) displayInfo.displayCutout?.cutoutPath?.let { path -> cutoutPath.set(path) } invalidate() } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 11d579d481c1..45f9385a2620 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -170,6 +170,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { private Display.Mode mDisplayMode; @VisibleForTesting protected DisplayInfo mDisplayInfo = new DisplayInfo(); + private DisplayCutout mDisplayCutout; @VisibleForTesting protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) { @@ -384,6 +385,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mRotation = mDisplayInfo.rotation; mDisplayMode = mDisplayInfo.getMode(); mDisplayUniqueId = mDisplayInfo.uniqueId; + mDisplayCutout = mDisplayInfo.displayCutout; mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(), mDisplayUniqueId); mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( @@ -1022,7 +1024,8 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mRoundedCornerResDelegate.dump(pw, args); } - private void updateConfiguration() { + @VisibleForTesting + void updateConfiguration() { Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(), "must call on " + mHandler.getLooper().getThread() + ", but was " + Thread.currentThread()); @@ -1033,11 +1036,14 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mDotViewController.setNewRotation(newRotation); } final Display.Mode newMod = mDisplayInfo.getMode(); + final DisplayCutout newCutout = mDisplayInfo.displayCutout; if (!mPendingConfigChange - && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) { + && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod) + || !Objects.equals(newCutout, mDisplayCutout))) { mRotation = newRotation; mDisplayMode = newMod; + mDisplayCutout = newCutout; mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( getPhysicalPixelDisplaySizeRatio()); if (mScreenDecorHwcLayer != null) { diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt new file mode 100644 index 000000000000..b52ddc1dbc42 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt @@ -0,0 +1,207 @@ +/* + * 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.battery + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorFilter +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.Path +import android.graphics.PixelFormat +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode +import android.graphics.Rect +import android.graphics.drawable.DrawableWrapper +import android.util.PathParser +import com.android.settingslib.graph.ThemedBatteryDrawable +import com.android.systemui.R +import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT +import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD +import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH +import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD +import com.android.systemui.battery.BatterySpecs.SHIELD_LEFT_OFFSET +import com.android.systemui.battery.BatterySpecs.SHIELD_STROKE +import com.android.systemui.battery.BatterySpecs.SHIELD_TOP_OFFSET + +/** + * A battery drawable that accessorizes [ThemedBatteryDrawable] with additional information if + * necessary. + * + * For now, it adds a shield in the bottom-right corner when [displayShield] is true. + */ +class AccessorizedBatteryDrawable( + private val context: Context, + frameColor: Int, +) : DrawableWrapper(ThemedBatteryDrawable(context, frameColor)) { + private val mainBatteryDrawable: ThemedBatteryDrawable + get() = drawable as ThemedBatteryDrawable + + private val shieldPath = Path() + private val scaledShield = Path() + private val scaleMatrix = Matrix() + + private var shieldLeftOffsetScaled = SHIELD_LEFT_OFFSET + private var shieldTopOffsetScaled = SHIELD_TOP_OFFSET + + private var density = context.resources.displayMetrics.density + + private val dualTone = + context.resources.getBoolean(com.android.internal.R.bool.config_batterymeterDualTone) + + private val shieldTransparentOutlinePaint = + Paint(Paint.ANTI_ALIAS_FLAG).also { p -> + p.color = Color.TRANSPARENT + p.strokeWidth = ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH + p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) + p.style = Paint.Style.FILL_AND_STROKE + } + + private val shieldPaint = + Paint(Paint.ANTI_ALIAS_FLAG).also { p -> + p.color = Color.MAGENTA + p.style = Paint.Style.FILL + p.isDither = true + } + + init { + loadPaths() + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + updateSizes() + } + + var displayShield: Boolean = false + + private fun updateSizes() { + val b = bounds + if (b.isEmpty) { + return + } + + val mainWidth = BatterySpecs.getMainBatteryWidth(b.width().toFloat(), displayShield) + val mainHeight = BatterySpecs.getMainBatteryHeight(b.height().toFloat(), displayShield) + + drawable?.setBounds( + b.left, + b.top, + /* right= */ b.left + mainWidth.toInt(), + /* bottom= */ b.top + mainHeight.toInt() + ) + + if (displayShield) { + val sx = b.right / BATTERY_WIDTH_WITH_SHIELD + val sy = b.bottom / BATTERY_HEIGHT_WITH_SHIELD + scaleMatrix.setScale(sx, sy) + shieldPath.transform(scaleMatrix, scaledShield) + + shieldLeftOffsetScaled = sx * SHIELD_LEFT_OFFSET + shieldTopOffsetScaled = sy * SHIELD_TOP_OFFSET + + val scaledStrokeWidth = + (sx * SHIELD_STROKE).coerceAtLeast( + ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH + ) + shieldTransparentOutlinePaint.strokeWidth = scaledStrokeWidth + } + } + + override fun getIntrinsicHeight(): Int { + val height = + if (displayShield) { + BATTERY_HEIGHT_WITH_SHIELD + } else { + BATTERY_HEIGHT + } + return (height * density).toInt() + } + + override fun getIntrinsicWidth(): Int { + val width = + if (displayShield) { + BATTERY_WIDTH_WITH_SHIELD + } else { + BATTERY_WIDTH + } + return (width * density).toInt() + } + + override fun draw(c: Canvas) { + c.saveLayer(null, null) + // Draw the main battery icon + super.draw(c) + + if (displayShield) { + c.translate(shieldLeftOffsetScaled, shieldTopOffsetScaled) + // We need a transparent outline around the shield, so first draw the transparent-ness + // then draw the shield + c.drawPath(scaledShield, shieldTransparentOutlinePaint) + c.drawPath(scaledShield, shieldPaint) + } + c.restore() + } + + override fun getOpacity(): Int { + return PixelFormat.OPAQUE + } + + override fun setAlpha(p0: Int) { + // Unused internally -- see [ThemedBatteryDrawable.setAlpha]. + } + + override fun setColorFilter(colorfilter: ColorFilter?) { + super.setColorFilter(colorFilter) + shieldPaint.colorFilter = colorFilter + } + + /** Sets whether the battery is currently charging. */ + fun setCharging(charging: Boolean) { + mainBatteryDrawable.charging = charging + } + + /** Sets the current level (out of 100) of the battery. */ + fun setBatteryLevel(level: Int) { + mainBatteryDrawable.setBatteryLevel(level) + } + + /** Sets whether power save is enabled. */ + fun setPowerSaveEnabled(powerSaveEnabled: Boolean) { + mainBatteryDrawable.powerSaveEnabled = powerSaveEnabled + } + + /** Returns whether power save is currently enabled. */ + fun getPowerSaveEnabled(): Boolean { + return mainBatteryDrawable.powerSaveEnabled + } + + /** Sets the colors to use for the icon. */ + fun setColors(fgColor: Int, bgColor: Int, singleToneColor: Int) { + shieldPaint.color = if (dualTone) fgColor else singleToneColor + mainBatteryDrawable.setColors(fgColor, bgColor, singleToneColor) + } + + /** Notifies this drawable that the density might have changed. */ + fun notifyDensityChanged() { + density = context.resources.displayMetrics.density + } + + private fun loadPaths() { + val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath) + shieldPath.set(PathParser.createPathFromPathData(shieldPathString)) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java index 6a10d4ab1e8b..03d999f697d0 100644 --- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java @@ -45,7 +45,6 @@ import android.widget.TextView; import androidx.annotation.StyleRes; import androidx.annotation.VisibleForTesting; -import com.android.settingslib.graph.ThemedBatteryDrawable; import com.android.systemui.DualToneHandler; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; @@ -68,7 +67,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { public static final int MODE_OFF = 2; public static final int MODE_ESTIMATE = 3; - private final ThemedBatteryDrawable mDrawable; + private final AccessorizedBatteryDrawable mDrawable; private final ImageView mBatteryIconView; private TextView mBatteryPercentView; @@ -77,7 +76,10 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { private int mLevel; private int mShowPercentMode = MODE_DEFAULT; private boolean mShowPercentAvailable; + private String mEstimateText = null; private boolean mCharging; + private boolean mIsOverheated; + private boolean mDisplayShieldEnabled; // Error state where we know nothing about the current battery state private boolean mBatteryStateUnknown; // Lazily-loaded since this is expected to be a rare-if-ever state @@ -106,7 +108,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor, context.getColor(R.color.meter_background_color)); mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0); - mDrawable = new ThemedBatteryDrawable(context, frameColor); + mDrawable = new AccessorizedBatteryDrawable(context, frameColor); atts.recycle(); mShowPercentAvailable = context.getResources().getBoolean( @@ -170,12 +172,14 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { if (mode == mShowPercentMode) return; mShowPercentMode = mode; updateShowPercent(); + updatePercentText(); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); updatePercentView(); + mDrawable.notifyDensityChanged(); } public void setColorsFromContext(Context context) { @@ -203,6 +207,17 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { mDrawable.setPowerSaveEnabled(isPowerSave); } + void onIsOverheatedChanged(boolean isOverheated) { + boolean valueChanged = mIsOverheated != isOverheated; + mIsOverheated = isOverheated; + if (valueChanged) { + updateContentDescription(); + // The battery drawable is a different size depending on whether it's currently + // overheated or not, so we need to re-scale the view when overheated changes. + scaleBatteryMeterViews(); + } + } + private TextView loadPercentView() { return (TextView) LayoutInflater.from(getContext()) .inflate(R.layout.battery_percentage_view, null); @@ -227,13 +242,17 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { mBatteryEstimateFetcher = fetcher; } + void setDisplayShieldEnabled(boolean displayShieldEnabled) { + mDisplayShieldEnabled = displayShieldEnabled; + } + void updatePercentText() { if (mBatteryStateUnknown) { - setContentDescription(getContext().getString(R.string.accessibility_battery_unknown)); return; } if (mBatteryEstimateFetcher == null) { + setPercentTextAtCurrentLevel(); return; } @@ -245,10 +264,9 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { return; } if (estimate != null && mShowPercentMode == MODE_ESTIMATE) { + mEstimateText = estimate; mBatteryPercentView.setText(estimate); - setContentDescription(getContext().getString( - R.string.accessibility_battery_level_with_estimate, - mLevel, estimate)); + updateContentDescription(); } else { setPercentTextAtCurrentLevel(); } @@ -257,28 +275,49 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { setPercentTextAtCurrentLevel(); } } else { - setContentDescription( - getContext().getString(mCharging ? R.string.accessibility_battery_level_charging - : R.string.accessibility_battery_level, mLevel)); + updateContentDescription(); } } private void setPercentTextAtCurrentLevel() { - if (mBatteryPercentView == null) { - return; + if (mBatteryPercentView != null) { + mEstimateText = null; + String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f); + // Setting text actually triggers a layout pass (because the text view is set to + // wrap_content width and TextView always relayouts for this). Avoid needless + // relayout if the text didn't actually change. + if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) { + mBatteryPercentView.setText(percentText); + } } - String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f); - // Setting text actually triggers a layout pass (because the text view is set to - // wrap_content width and TextView always relayouts for this). Avoid needless - // relayout if the text didn't actually change. - if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) { - mBatteryPercentView.setText(percentText); + updateContentDescription(); + } + + private void updateContentDescription() { + Context context = getContext(); + + String contentDescription; + if (mBatteryStateUnknown) { + contentDescription = context.getString(R.string.accessibility_battery_unknown); + } else if (mShowPercentMode == MODE_ESTIMATE && !TextUtils.isEmpty(mEstimateText)) { + contentDescription = context.getString( + mIsOverheated + ? R.string.accessibility_battery_level_charging_paused_with_estimate + : R.string.accessibility_battery_level_with_estimate, + mLevel, + mEstimateText); + } else if (mIsOverheated) { + contentDescription = + context.getString(R.string.accessibility_battery_level_charging_paused, mLevel); + } else if (mCharging) { + contentDescription = + context.getString(R.string.accessibility_battery_level_charging, mLevel); + } else { + contentDescription = context.getString(R.string.accessibility_battery_level, mLevel); } - setContentDescription( - getContext().getString(mCharging ? R.string.accessibility_battery_level_charging - : R.string.accessibility_battery_level, mLevel)); + setContentDescription(contentDescription); } void updateShowPercent() { @@ -329,6 +368,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } mBatteryStateUnknown = isUnknown; + updateContentDescription(); if (mBatteryStateUnknown) { mBatteryIconView.setImageDrawable(getUnknownStateDrawable()); @@ -349,15 +389,43 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); float iconScaleFactor = typedValue.getFloat(); - int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height); - int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width); + float mainBatteryHeight = + res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height) * iconScaleFactor; + float mainBatteryWidth = + res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor; + + // If the battery is marked as overheated, we should display a shield indicating that the + // battery is being "defended". + boolean displayShield = mDisplayShieldEnabled && mIsOverheated; + float fullBatteryIconHeight = + BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield); + float fullBatteryIconWidth = + BatterySpecs.getFullBatteryWidth(mainBatteryWidth, displayShield); + + int marginTop; + if (displayShield) { + // If the shield is displayed, we need some extra marginTop so that the bottom of the + // main icon is still aligned with the bottom of all the other system icons. + int shieldHeightAddition = Math.round(fullBatteryIconHeight - mainBatteryHeight); + // However, the other system icons have some embedded bottom padding that the battery + // doesn't have, so we shouldn't move the battery icon down by the full amount. + // See b/258672854. + marginTop = shieldHeightAddition + - res.getDimensionPixelSize(R.dimen.status_bar_battery_extra_vertical_spacing); + } else { + marginTop = 0; + } + int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom); LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams( - (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor)); - scaledLayoutParams.setMargins(0, 0, 0, marginBottom); + Math.round(fullBatteryIconWidth), + Math.round(fullBatteryIconHeight)); + scaledLayoutParams.setMargins(0, marginTop, 0, marginBottom); + mDrawable.setDisplayShield(displayShield); mBatteryIconView.setLayoutParams(scaledLayoutParams); + mBatteryIconView.invalidateDrawable(mDrawable); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java index ae9a32309d45..77cb9d1bf594 100644 --- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java @@ -29,6 +29,8 @@ import android.view.View; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.BatteryController; @@ -84,6 +86,11 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> public void onBatteryUnknownStateChanged(boolean isUnknown) { mView.onBatteryUnknownStateChanged(isUnknown); } + + @Override + public void onIsOverheatedChanged(boolean isOverheated) { + mView.onIsOverheatedChanged(isOverheated); + } }; // Some places may need to show the battery conditionally, and not obey the tuner @@ -98,6 +105,7 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> BroadcastDispatcher broadcastDispatcher, @Main Handler mainHandler, ContentResolver contentResolver, + FeatureFlags featureFlags, BatteryController batteryController) { super(view); mConfigurationController = configurationController; @@ -106,6 +114,7 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> mBatteryController = batteryController; mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString); + mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON)); mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery); mSettingObserver = new SettingObserver(mainHandler); diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt new file mode 100644 index 000000000000..6455a9656fde --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt @@ -0,0 +1,109 @@ +/* + * 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.battery + +import com.android.settingslib.graph.ThemedBatteryDrawable + +/** An object storing specs related to the battery icon in the status bar. */ +object BatterySpecs { + + /** Width of the main battery icon, not including the shield. */ + const val BATTERY_WIDTH = ThemedBatteryDrawable.WIDTH + /** Height of the main battery icon, not including the shield. */ + const val BATTERY_HEIGHT = ThemedBatteryDrawable.HEIGHT + + private const val SHIELD_WIDTH = 10f + private const val SHIELD_HEIGHT = 13f + + /** + * Amount that the left side of the shield should be offset from the left side of the battery. + */ + const val SHIELD_LEFT_OFFSET = 8f + /** Amount that the top of the shield should be offset from the top of the battery. */ + const val SHIELD_TOP_OFFSET = 10f + + const val SHIELD_STROKE = 4f + + /** The full width of the battery icon, including the main battery icon *and* the shield. */ + const val BATTERY_WIDTH_WITH_SHIELD = SHIELD_LEFT_OFFSET + SHIELD_WIDTH + /** The full height of the battery icon, including the main battery icon *and* the shield. */ + const val BATTERY_HEIGHT_WITH_SHIELD = SHIELD_TOP_OFFSET + SHIELD_HEIGHT + + /** + * Given the desired height of the main battery icon in pixels, returns the height that the full + * battery icon will take up in pixels. + * + * If there's no shield, this will just return [mainBatteryHeight]. Otherwise, the shield + * extends slightly below the bottom of the main battery icon so we need some extra height. + */ + @JvmStatic + fun getFullBatteryHeight(mainBatteryHeight: Float, displayShield: Boolean): Float { + return if (!displayShield) { + mainBatteryHeight + } else { + val verticalScaleFactor = mainBatteryHeight / BATTERY_HEIGHT + verticalScaleFactor * BATTERY_HEIGHT_WITH_SHIELD + } + } + + /** + * Given the desired width of the main battery icon in pixels, returns the width that the full + * battery icon will take up in pixels. + * + * If there's no shield, this will just return [mainBatteryWidth]. Otherwise, the shield extends + * past the right side of the main battery icon so we need some extra width. + */ + @JvmStatic + fun getFullBatteryWidth(mainBatteryWidth: Float, displayShield: Boolean): Float { + return if (!displayShield) { + mainBatteryWidth + } else { + val horizontalScaleFactor = mainBatteryWidth / BATTERY_WIDTH + horizontalScaleFactor * BATTERY_WIDTH_WITH_SHIELD + } + } + + /** + * Given the height of the full battery icon, return how tall the main battery icon should be. + * + * If there's no shield, this will just return [fullBatteryHeight]. Otherwise, the shield takes + * up some of the view's height so the main battery width will be just a portion of + * [fullBatteryHeight]. + */ + @JvmStatic + fun getMainBatteryHeight(fullBatteryHeight: Float, displayShield: Boolean): Float { + return if (!displayShield) { + fullBatteryHeight + } else { + return (BATTERY_HEIGHT / BATTERY_HEIGHT_WITH_SHIELD) * fullBatteryHeight + } + } + + /** + * Given the width of the full battery icon, return how wide the main battery icon should be. + * + * If there's no shield, this will just return [fullBatteryWidth]. Otherwise, the shield takes + * up some of the view's width so the main battery width will be just a portion of + * [fullBatteryWidth]. + */ + @JvmStatic + fun getMainBatteryWidth(fullBatteryWidth: Float, displayShield: Boolean): Float { + return if (!displayShield) { + fullBatteryWidth + } else { + return (BATTERY_WIDTH / BATTERY_WIDTH_WITH_SHIELD) * fullBatteryWidth + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java index fe89c9a1e3b9..9e8c0ec7423e 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java @@ -21,24 +21,21 @@ import android.content.Context; import com.android.systemui.dagger.qualifiers.InstrumentationTest; import com.android.systemui.util.InitializationChecker; -import javax.inject.Singleton; - import dagger.BindsInstance; -import dagger.Component; /** * Base root component for Dagger injection. * + * This class is not actually annotated as a Dagger component, since it is not used directly as one. + * Doing so generates unnecessary code bloat. + * * See {@link ReferenceGlobalRootComponent} for the one actually used by AOSP. */ -@Singleton -@Component(modules = {GlobalModule.class}) public interface GlobalRootComponent { /** * Builder for a GlobalRootComponent. */ - @Component.Builder interface Builder { @BindsInstance Builder context(Context context); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java index 5694f6da0ea5..440dcbc18a12 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java @@ -194,7 +194,9 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll break; } - if (!isRoot) { + // Add margin if specified by the complication. Otherwise add default margin + // between complications. + if (mLayoutParams.isMarginSpecified() || !isRoot) { final int margin = mLayoutParams.getMargin(mDefaultMargin); switch(direction) { case ComplicationLayoutParams.DIRECTION_DOWN: diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java index a21eb19bd548..2b32d349dd67 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java @@ -261,6 +261,13 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { } /** + * Returns whether margin has been specified by the complication. + */ + public boolean isMarginSpecified() { + return mMargin != MARGIN_UNSPECIFIED; + } + + /** * Returns the margin to apply between complications, or the given default if no margin is * specified. */ diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java index cedd850ac2ef..c01cf43eae74 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java @@ -33,6 +33,7 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.systemui.CoreStartable; import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.controls.ControlsServiceInfo; import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.controls.management.ControlsListingController; import com.android.systemui.controls.ui.ControlsActivity; @@ -42,6 +43,8 @@ import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplica import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.util.ViewController; +import java.util.List; + import javax.inject.Inject; import javax.inject.Named; @@ -76,16 +79,25 @@ public class DreamHomeControlsComplication implements Complication { private final DreamOverlayStateController mDreamOverlayStateController; private final ControlsComponent mControlsComponent; - private boolean mControlServicesAvailable = false; + private boolean mOverlayActive = false; // Callback for when the home controls service availability changes. private final ControlsListingController.ControlsListingCallback mControlsCallback = - serviceInfos -> { - boolean available = !serviceInfos.isEmpty(); + services -> updateHomeControlsComplication(); + + private final DreamOverlayStateController.Callback mOverlayStateCallback = + new DreamOverlayStateController.Callback() { + @Override + public void onStateChanged() { + if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) { + return; + } - if (available != mControlServicesAvailable) { - mControlServicesAvailable = available; - updateComplicationAvailability(); + mOverlayActive = !mOverlayActive; + + if (mOverlayActive) { + updateHomeControlsComplication(); + } } }; @@ -102,18 +114,29 @@ public class DreamHomeControlsComplication implements Complication { public void start() { mControlsComponent.getControlsListingController().ifPresent( c -> c.addCallback(mControlsCallback)); + mDreamOverlayStateController.addCallback(mOverlayStateCallback); + } + + private void updateHomeControlsComplication() { + mControlsComponent.getControlsListingController().ifPresent(c -> { + if (isHomeControlsAvailable(c.getCurrentServices())) { + mDreamOverlayStateController.addComplication(mComplication); + } else { + mDreamOverlayStateController.removeComplication(mComplication); + } + }); } - private void updateComplicationAvailability() { + private boolean isHomeControlsAvailable(List<ControlsServiceInfo> controlsServices) { + if (controlsServices.isEmpty()) { + return false; + } + final boolean hasFavorites = mControlsComponent.getControlsController() .map(c -> !c.getFavorites().isEmpty()) .orElse(false); - if (!hasFavorites || !mControlServicesAvailable - || mControlsComponent.getVisibility() == UNAVAILABLE) { - mDreamOverlayStateController.removeComplication(mComplication); - } else { - mDreamOverlayStateController.addComplication(mComplication); - } + final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility(); + return hasFavorites && visibility != UNAVAILABLE; } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java index 7d2ce51ffbf6..69b85b5a5e51 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java @@ -48,9 +48,9 @@ public interface RegisteredComplicationsModule { int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1; int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 0; - int DREAM_MEDIA_COMPLICATION_WEIGHT = -1; - int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 1; - int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 0; + int DREAM_MEDIA_COMPLICATION_WEIGHT = 0; + int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 2; + int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 1; /** * Provides layout parameters for the clock time complication. @@ -60,10 +60,11 @@ public interface RegisteredComplicationsModule { static ComplicationLayoutParams provideClockTimeLayoutParams() { return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, - ComplicationLayoutParams.POSITION_TOP + ComplicationLayoutParams.POSITION_BOTTOM | ComplicationLayoutParams.POSITION_START, - ComplicationLayoutParams.DIRECTION_DOWN, - DREAM_CLOCK_TIME_COMPLICATION_WEIGHT); + ComplicationLayoutParams.DIRECTION_UP, + DREAM_CLOCK_TIME_COMPLICATION_WEIGHT, + 0 /*margin*/); } /** @@ -77,8 +78,10 @@ public interface RegisteredComplicationsModule { res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height), ComplicationLayoutParams.POSITION_BOTTOM | ComplicationLayoutParams.POSITION_START, - ComplicationLayoutParams.DIRECTION_END, - DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT); + ComplicationLayoutParams.DIRECTION_UP, + DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT, + // Add margin to the bottom of home controls to horizontally align with smartspace. + res.getDimensionPixelSize(R.dimen.dream_overlay_complication_clock_time_padding)); } /** @@ -101,14 +104,13 @@ public interface RegisteredComplicationsModule { */ @Provides @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS) - static ComplicationLayoutParams provideSmartspaceLayoutParams() { + static ComplicationLayoutParams provideSmartspaceLayoutParams(@Main Resources res) { return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, - ComplicationLayoutParams.POSITION_TOP + ComplicationLayoutParams.POSITION_BOTTOM | ComplicationLayoutParams.POSITION_START, - ComplicationLayoutParams.DIRECTION_DOWN, + ComplicationLayoutParams.DIRECTION_END, DREAM_SMARTSPACE_COMPLICATION_WEIGHT, - 0, - true /*snapToGuide*/); + res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_padding)); } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index bbb88809e1d0..39fda9d083ee 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -146,8 +146,10 @@ object Flags { // TODO(b/255607168): Tracking Bug @JvmField val DOZING_MIGRATION_1 = unreleasedFlag(213, "dozing_migration_1") + // TODO(b/252897742): Tracking Bug @JvmField val NEW_ELLIPSE_DETECTION = unreleasedFlag(214, "new_ellipse_detection") + // TODO(b/252897742): Tracking Bug @JvmField val NEW_UDFPS_OVERLAY = unreleasedFlag(215, "new_udfps_overlay") /** @@ -160,6 +162,10 @@ object Flags { val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false) + /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */ + // TODO(b/240196500): Tracking Bug + @JvmField val ACTIVE_UNLOCK_CHIPBAR = unreleasedFlag(217, "active_unlock_chipbar") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -233,6 +239,9 @@ object Flags { // TODO(b/256613548): Tracking Bug val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend") + // TODO(b/256623670): Tracking Bug + @JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon") + // 700 - dialer/calls // TODO(b/254512734): Tracking Bug val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt index d4514c5cb7aa..9a90fe7e60bd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt @@ -16,16 +16,17 @@ package com.android.systemui.keyguard.data.repository -import android.hardware.biometrics.BiometricSourceType import com.android.keyguard.KeyguardUpdateMonitor -import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.ViewMediatorCallback import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel import com.android.systemui.statusbar.phone.KeyguardBouncer import javax.inject.Inject +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow /** Encapsulates app state for the lock screen primary and alternate bouncer. */ @@ -70,33 +71,19 @@ constructor( private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null) /** Determines if user is already unlocked */ val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow() - - var bouncerPromptReason: Int? = null - private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null) - val showMessage = _showMessage.asStateFlow() + private val _showMessage = + MutableSharedFlow<BouncerShowMessageModel?>( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val showMessage = _showMessage.asSharedFlow() private val _resourceUpdateRequests = MutableStateFlow(false) val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow() - + val bouncerPromptReason: Int + get() = viewMediatorCallback.bouncerPromptReason val bouncerErrorMessage: CharSequence? get() = viewMediatorCallback.consumeCustomMessage() - init { - val callback = - object : KeyguardUpdateMonitorCallback() { - override fun onStrongAuthStateChanged(userId: Int) { - bouncerPromptReason = viewMediatorCallback.bouncerPromptReason - } - - override fun onLockedOutStateChanged(type: BiometricSourceType) { - if (type == BiometricSourceType.FINGERPRINT) { - bouncerPromptReason = viewMediatorCallback.bouncerPromptReason - } - } - } - - keyguardUpdateMonitor.registerCallback(callback) - } - fun setPrimaryScrimmed(isScrimmed: Boolean) { _primaryBouncerScrimmed.value = isScrimmed } @@ -138,7 +125,7 @@ constructor( } fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) { - _showMessage.value = bouncerShowMessageModel + _showMessage.tryEmit(bouncerShowMessageModel) } fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index ca25282ec2f0..9d5d8bbd4f40 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.repository import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.Position @@ -69,6 +70,9 @@ interface KeyguardRepository { */ val isKeyguardShowing: Flow<Boolean> + /** Observable for the signal that keyguard is about to go away. */ + val isKeyguardGoingAway: Flow<Boolean> + /** Observable for whether the bouncer is showing. */ val isBouncerShowing: Flow<Boolean> @@ -85,6 +89,14 @@ interface KeyguardRepository { val isDozing: Flow<Boolean> /** + * Observable for whether the device is dreaming. + * + * Dozing/AOD is a specific type of dream, but it is also possible for other non-systemui dreams + * to be active, such as screensavers. + */ + val isDreaming: Flow<Boolean> + + /** * Observable for the amount of doze we are currently in. * * While in doze state, this amount can change - driving a cycle of animations designed to avoid @@ -136,12 +148,12 @@ interface KeyguardRepository { class KeyguardRepositoryImpl @Inject constructor( - statusBarStateController: StatusBarStateController, - dozeHost: DozeHost, - wakefulnessLifecycle: WakefulnessLifecycle, - biometricUnlockController: BiometricUnlockController, - private val keyguardStateController: KeyguardStateController, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + statusBarStateController: StatusBarStateController, + dozeHost: DozeHost, + wakefulnessLifecycle: WakefulnessLifecycle, + biometricUnlockController: BiometricUnlockController, + private val keyguardStateController: KeyguardStateController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : KeyguardRepository { private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) override val animateBottomAreaDozingTransitions = @@ -176,6 +188,29 @@ constructor( awaitClose { keyguardStateController.removeCallback(callback) } } + override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow { + val callback = + object : KeyguardStateController.Callback { + override fun onKeyguardGoingAwayChanged() { + trySendWithFailureLogging( + keyguardStateController.isKeyguardGoingAway, + TAG, + "updated isKeyguardGoingAway" + ) + } + } + + keyguardStateController.addCallback(callback) + // Adding the callback does not send an initial update. + trySendWithFailureLogging( + keyguardStateController.isKeyguardGoingAway, + TAG, + "initial isKeyguardGoingAway" + ) + + awaitClose { keyguardStateController.removeCallback(callback) } + } + override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { @@ -218,6 +253,25 @@ constructor( } .distinctUntilChanged() + override val isDreaming: Flow<Boolean> = + conflatedCallbackFlow { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onDreamingStateChanged(isDreaming: Boolean) { + trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming") + } + } + keyguardUpdateMonitor.registerCallback(callback) + trySendWithFailureLogging( + keyguardUpdateMonitor.isDreaming, + TAG, + "initial isDreaming", + ) + + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } + } + .distinctUntilChanged() + override val dozeAmount: Flow<Float> = conflatedCallbackFlow { val callback = object : StatusBarStateController.StateListener { 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 e3d1a27dad2b..bce7d92cd8fb 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 @@ -94,11 +94,13 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio */ private val _transitions = MutableSharedFlow<TransitionStep>( + replay = 2, extraBufferCapacity = 10, - onBufferOverflow = BufferOverflow.DROP_OLDEST + onBufferOverflow = BufferOverflow.DROP_OLDEST, ) override val transitions = _transitions.asSharedFlow().distinctUntilChanged() private var lastStep: TransitionStep = TransitionStep() + private var lastAnimator: ValueAnimator? = null /* * When manual control of the transition is requested, a unique [UUID] is used as the handle @@ -106,19 +108,39 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio */ private var updateTransitionId: UUID? = null + init { + // Seed with transitions signaling a boot into lockscreen state + emitTransition( + TransitionStep( + KeyguardState.OFF, + KeyguardState.LOCKSCREEN, + 0f, + TransitionState.STARTED, + ) + ) + emitTransition( + TransitionStep( + KeyguardState.OFF, + KeyguardState.LOCKSCREEN, + 1f, + TransitionState.FINISHED, + ) + ) + } + override fun startTransition(info: TransitionInfo): UUID? { if (lastStep.transitionState != TransitionState.FINISHED) { - // Open questions: - // * Queue of transitions? buffer of 1? - // * Are transitions cancellable if a new one is triggered? - // * What validation does this need to do? - Log.wtf(TAG, "Transition still active: $lastStep") - return null + Log.i(TAG, "Transition still active: $lastStep, canceling") } + val startingValue = 1f - lastStep.value + lastAnimator?.cancel() + lastAnimator = info.animator + info.animator?.let { animator -> // An animator was provided, so use it to run the transition - animator.setFloatValues(0f, 1f) + animator.setFloatValues(startingValue, 1f) + animator.duration = ((1f - startingValue) * animator.duration).toLong() val updateListener = object : AnimatorUpdateListener { override fun onAnimationUpdate(animation: ValueAnimator) { @@ -134,15 +156,24 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio val adapter = object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator) { - emitTransition(TransitionStep(info, 0f, TransitionState.STARTED)) + emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED)) } override fun onAnimationCancel(animation: Animator) { - Log.i(TAG, "Cancelling transition: $info") + endAnimation(animation, lastStep.value, TransitionState.CANCELED) } override fun onAnimationEnd(animation: Animator) { - emitTransition(TransitionStep(info, 1f, TransitionState.FINISHED)) + endAnimation(animation, 1f, TransitionState.FINISHED) + } + + private fun endAnimation( + animation: Animator, + value: Float, + state: TransitionState + ) { + emitTransition(TransitionStep(info, value, state)) animator.removeListener(this) animator.removeUpdateListener(updateListener) + lastAnimator = null } } animator.addListener(adapter) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt index 0aeff7fc69fd..e5521c76705d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt @@ -20,10 +20,11 @@ import android.animation.ValueAnimator 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.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep +import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -35,18 +36,30 @@ class AodLockscreenTransitionInteractor @Inject constructor( @Application private val scope: CoroutineScope, - private val keyguardRepository: KeyguardRepository, + private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionRepository: KeyguardTransitionRepository, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, ) : TransitionInteractor("AOD<->LOCKSCREEN") { override fun start() { scope.launch { - keyguardRepository.isDozing - .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + /* + * Listening to the startedKeyguardTransitionStep (last started step) allows this code + * to interrupt an active transition, as long as they were either going to LOCKSCREEN or + * AOD state. One example is when the user presses the power button in the middle of an + * active transition. + */ + keyguardInteractor.wakefulnessState + .sample( + keyguardTransitionInteractor.startedKeyguardTransitionStep, + { a, b -> Pair(a, b) } + ) .collect { pair -> - val (isDozing, keyguardState) = pair - if (isDozing && keyguardState == KeyguardState.LOCKSCREEN) { + val (wakefulnessState, lastStartedStep) = pair + if ( + isSleepingOrStartingToSleep(wakefulnessState) && + lastStartedStep.to == KeyguardState.LOCKSCREEN + ) { keyguardTransitionRepository.startTransition( TransitionInfo( name, @@ -55,7 +68,10 @@ constructor( getAnimator(), ) ) - } else if (!isDozing && keyguardState == KeyguardState.AOD) { + } else if ( + isWakingOrStartingToWake(wakefulnessState) && + lastStartedStep.to == KeyguardState.AOD + ) { keyguardTransitionRepository.startTransition( TransitionInfo( name, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt new file mode 100644 index 000000000000..dd2967334307 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt @@ -0,0 +1,81 @@ +/* + * 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.keyguard.domain.interactor + +import android.animation.ValueAnimator +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.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.util.kotlin.sample +import java.util.UUID +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class BouncerToGoneTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val shadeRepository: ShadeRepository, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor +) : TransitionInteractor("BOUNCER->GONE") { + + private var transitionId: UUID? = null + + override fun start() { + listenForKeyguardGoingAway() + } + + private fun listenForKeyguardGoingAway() { + scope.launch { + keyguardInteractor.isKeyguardGoingAway + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isKeyguardGoingAway, keyguardState) = pair + if (isKeyguardGoingAway && keyguardState == KeyguardState.BOUNCER) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.BOUNCER, + to = KeyguardState.GONE, + animator = getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 300L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt new file mode 100644 index 000000000000..c44cda42c68d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt @@ -0,0 +1,81 @@ +/* + * 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.keyguard.domain.interactor + +import android.animation.ValueAnimator +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.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class DreamingLockscreenTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor("DREAMING<->LOCKSCREEN") { + + override fun start() { + scope.launch { + keyguardInteractor.isDreaming + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isDreaming, keyguardState) = pair + if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + getAnimator(), + ) + ) + } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + KeyguardState.LOCKSCREEN, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 500L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt new file mode 100644 index 000000000000..9e2b7241ade2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt @@ -0,0 +1,76 @@ +/* + * 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.keyguard.domain.interactor + +import android.animation.ValueAnimator +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.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class DreamingToAodTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor("DREAMING->AOD") { + + override fun start() { + scope.launch { + keyguardInteractor.wakefulnessState + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (wakefulnessState, keyguardState) = pair + if ( + isSleepingOrStartingToSleep(wakefulnessState) && + keyguardState == KeyguardState.DREAMING + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + KeyguardState.AOD, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 300L + } +} 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 614ff8d930d8..5a1c70264ee4 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 @@ -41,8 +41,15 @@ constructor( val dozeAmount: Flow<Float> = repository.dozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing + /** + * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true, + * but not vice-versa. + */ + val isDreaming: Flow<Boolean> = repository.isDreaming /** Whether the keyguard is showing or not. */ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing + /** Whether the keyguard is going away. */ + val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway /** Whether the bouncer is showing or not. */ val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing /** The device wake/sleep state */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 57fb4a114700..58a8093d49d2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -41,12 +41,24 @@ constructor( } scope.launch { + keyguardInteractor.isBouncerShowing.collect { logger.v("Bouncer showing", it) } + } + + scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } } + + scope.launch { interactor.finishedKeyguardTransitionStep.collect { logger.i("Finished transition", it) } } scope.launch { + interactor.canceledKeyguardTransitionStep.collect { + logger.i("Canceled transition", it) + } + } + + scope.launch { interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index a7c6d4450336..43dd358e4808 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -42,6 +42,9 @@ constructor( is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it") is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it") is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it") + is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it") + is DreamingLockscreenTransitionInteractor -> Log.d(TAG, "Started $it") + is DreamingToAodTransitionInteractor -> Log.d(TAG, "Started $it") } it.start() } 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 749183e241e5..54a4f493d21d 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 @@ -57,6 +57,14 @@ constructor( lockscreenToAodTransition, ) + /* The last [TransitionStep] with a [TransitionState] of STARTED */ + val startedKeyguardTransitionStep: Flow<TransitionStep> = + repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED } + + /* The last [TransitionStep] with a [TransitionState] of CANCELED */ + val canceledKeyguardTransitionStep: Flow<TransitionStep> = + repository.transitions.filter { step -> step.transitionState == TransitionState.CANCELED } + /* The last [TransitionStep] with a [TransitionState] of FINISHED */ val finishedKeyguardTransitionStep: Flow<TransitionStep> = repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED } @@ -64,8 +72,4 @@ constructor( /* The last completed [KeyguardState] transition */ val finishedKeyguardState: Flow<KeyguardState> = finishedKeyguardTransitionStep.map { step -> step.to } - - /* The last [TransitionStep] with a [TransitionState] of STARTED */ - val startedKeyguardTransitionStep: Flow<TransitionStep> = - repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt index fd4814d2bc94..cca2d566556e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt @@ -56,10 +56,20 @@ constructor( private fun listenForBouncerHiding() { scope.launch { keyguardInteractor.isBouncerShowing - .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) }) - .collect { pair -> - val (isBouncerShowing, wakefulnessState) = pair - if (!isBouncerShowing) { + .sample( + combine( + keyguardInteractor.wakefulnessState, + keyguardTransitionInteractor.startedKeyguardTransitionStep, + ) { a, b -> + Pair(a, b) + }, + { a, bc -> Triple(a, bc.first, bc.second) } + ) + .collect { triple -> + val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple + if ( + !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER + ) { val to = if ( wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP || @@ -90,10 +100,10 @@ constructor( combine( keyguardTransitionInteractor.finishedKeyguardState, keyguardInteractor.statusBarState, - ) { keyguardState, statusBarState -> - Pair(keyguardState, statusBarState) + ) { a, b -> + Pair(a, b) }, - { shadeModel, pair -> Triple(shadeModel, pair.first, pair.second) } + { a, bc -> Triple(a, bc.first, bc.second) } ) .collect { triple -> val (shadeModel, keyguardState, statusBarState) = triple @@ -116,8 +126,7 @@ constructor( ) } else { // TODO (b/251849525): Remove statusbarstate check when that state is - // integrated - // into KeyguardTransitionRepository + // integrated into KeyguardTransitionRepository if ( keyguardState == KeyguardState.LOCKSCREEN && shadeModel.isUserDragging && diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt index 6c1adbd68ef2..4100f7a8413a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt @@ -23,6 +23,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect @@ -34,23 +35,27 @@ class LockscreenGoneTransitionInteractor constructor( @Application private val scope: CoroutineScope, private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardTransitionRepository: KeyguardTransitionRepository, ) : TransitionInteractor("LOCKSCREEN->GONE") { override fun start() { scope.launch { - keyguardInteractor.isKeyguardShowing.collect { isShowing -> - if (!isShowing) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - name, - KeyguardState.LOCKSCREEN, - KeyguardState.GONE, - getAnimator(), + keyguardInteractor.isKeyguardGoingAway + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isKeyguardGoingAway, keyguardState) = pair + if (!isKeyguardGoingAway && keyguardState == KeyguardState.LOCKSCREEN) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.GONE, + getAnimator(), + ) ) - ) + } } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt index c22f4e7a2634..910cdf2df5a4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt @@ -117,7 +117,6 @@ constructor( @JvmOverloads fun show(isScrimmed: Boolean) { // Reset some states as we show the bouncer. - repository.setShowMessage(null) repository.setOnScreenTurnedOff(false) repository.setKeyguardAuthenticated(null) repository.setPrimaryHide(false) @@ -210,9 +209,12 @@ constructor( expansion == KeyguardBouncer.EXPANSION_HIDDEN && oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN ) { - repository.setPrimaryVisible(false) - repository.setPrimaryShow(null) - falsingCollector.onBouncerHidden() + /* + * There are cases where #hide() was not invoked, such as when + * NotificationPanelViewController controls the hide animation. Make sure the state gets + * updated by calling #hide() directly. + */ + hide() DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() } primaryBouncerCallbackInteractor.dispatchFullyHidden() } else if ( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt index 37f33afbf53e..dbffeab436a4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt @@ -46,5 +46,19 @@ abstract class StartKeyguardTransitionModule { @Binds @IntoSet + abstract fun bouncerGone(impl: BouncerToGoneTransitionInteractor): TransitionInteractor + + @Binds + @IntoSet abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor + + @Binds + @IntoSet + abstract fun dreamingLockscreen( + impl: DreamingLockscreenTransitionInteractor + ): TransitionInteractor + + @Binds + @IntoSet + abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt index 7958033ba017..dd908c420fcb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt @@ -17,12 +17,29 @@ package com.android.systemui.keyguard.shared.model /** List of all possible states to transition to/from */ enum class KeyguardState { + /* + * The display is completely off, as well as any sensors that would trigger the device to wake + * up. + */ + OFF, /** - * For initialization as well as when the security method is set to NONE, indicating that - * the keyguard should never be shown. + * The device has entered a special low-power mode within SystemUI. Doze is technically a + * special dream service implementation. No UI is visible. In this state, a least some + * low-powered sensors such as lift to wake or tap to wake are enabled, or wake screen for + * notifications is enabled, allowing the device to quickly wake up. + */ + DOZING, + /* + * A device state after the device times out, which can be from both LOCKSCREEN or GONE states. + * DOZING is an example of special version of this state. Dreams may be implemented by third + * parties to present their own UI over keyguard, like a screensaver. + */ + DREAMING, + /** + * The device has entered a special low-power mode within SystemUI, also called the Always-on + * Display (AOD). A minimal UI is presented to show critical information. If the device is in + * low-power mode without a UI, then it is DOZING. */ - NONE, - /* Always-on Display. The device is in a low-power mode with a minimal UI visible */ AOD, /* * The security screen prompt UI, containing PIN, Password, Pattern, and all FPS @@ -34,7 +51,6 @@ enum class KeyguardState { * unlocked if SWIPE security method is used, or if face lockscreen bypass is false. */ LOCKSCREEN, - /* * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard * is being removed, but there are other cases where the user is swiping away keyguard, such as diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt index 0e0465bb5207..38a93b50ea97 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt @@ -17,7 +17,12 @@ package com.android.systemui.keyguard.shared.model /** Possible states for a running transition between [State] */ enum class TransitionState { + /* Transition has begun. */ STARTED, + /* Transition is actively running. */ RUNNING, - FINISHED + /* Transition has completed successfully. */ + FINISHED, + /* Transition has been interrupted, and not completed successfully. */ + CANCELED, } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt index 732a6f7b887a..767fd58f78e9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt @@ -17,8 +17,8 @@ package com.android.systemui.keyguard.shared.model /** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */ data class TransitionStep( - val from: KeyguardState = KeyguardState.NONE, - val to: KeyguardState = KeyguardState.NONE, + val from: KeyguardState = KeyguardState.OFF, + val to: KeyguardState = KeyguardState.OFF, val value: Float = 0f, // constrained [0.0, 1.0] val transitionState: TransitionState = TransitionState.FINISHED, val ownerName: String = "", diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt index 64f834d6c5ab..92040f4f0348 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt @@ -24,5 +24,15 @@ enum class WakefulnessModel { /** Device is now fully awake and interactive. */ AWAKE, /** Signal that the device is now going to sleep. */ - STARTING_TO_SLEEP, + STARTING_TO_SLEEP; + + companion object { + fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean { + return model == ASLEEP || model == STARTING_TO_SLEEP + } + + fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean { + return model == AWAKE || model == STARTING_TO_WAKE + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index a22958b74bb9..7739a456fcb7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -94,6 +94,10 @@ object KeyguardBouncerViewBinder { viewModel.setBouncerViewDelegate(delegate) launch { viewModel.show.collect { + hostViewController.showPromptReason(it.promptReason) + it.errorMessage?.let { errorMessage -> + hostViewController.showErrorMessage(errorMessage) + } hostViewController.showPrimarySecurityScreen() hostViewController.appear( SystemBarUtils.getStatusBarHeight(view.context) @@ -102,18 +106,6 @@ object KeyguardBouncerViewBinder { } launch { - viewModel.showPromptReason.collect { prompt -> - hostViewController.showPromptReason(prompt) - } - } - - launch { - viewModel.showBouncerErrorMessage.collect { errorMessage -> - hostViewController.showErrorMessage(errorMessage) - } - } - - launch { viewModel.showWithFullExpansion.collect { model -> hostViewController.resetSecurityContainer() hostViewController.showPromptReason(model.promptReason) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt index 07816001f45c..526ae741793c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -26,7 +26,6 @@ import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map /** Models UI state for the lock screen bouncer; handles user input. */ @@ -45,13 +44,6 @@ constructor( /** Observe whether bouncer is showing. */ val show: Flow<KeyguardBouncerModel> = interactor.show - /** Observe bouncer prompt when bouncer is showing. */ - val showPromptReason: Flow<Int> = interactor.show.map { it.promptReason } - - /** Observe bouncer error message when bouncer is showing. */ - val showBouncerErrorMessage: Flow<CharSequence> = - interactor.show.map { it.errorMessage }.filterNotNull() - /** Observe visible expansion when bouncer is showing. */ val showWithFullExpansion: Flow<KeyguardBouncerModel> = interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index a4a968067462..647beb95a3bc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -61,7 +61,7 @@ class MediaTttCommandLineHelper @Inject constructor( @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager - val routeInfo = MediaRoute2Info.Builder("id", args[0]) + val routeInfo = MediaRoute2Info.Builder(if (args.size >= 4) args[3] else "id", args[0]) .addFeature("feature") val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false") if (useAppIcon) { @@ -107,7 +107,7 @@ class MediaTttCommandLineHelper @Inject constructor( override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND " + - "<deviceName> <chipState> useAppIcon=[true|false]") + "<deviceName> <chipState> useAppIcon=[true|false] <id>") } } @@ -127,8 +127,10 @@ class MediaTttCommandLineHelper @Inject constructor( @SuppressLint("WrongConstant") // sysui is allowed to call STATUS_BAR_SERVICE val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager - val routeInfo = MediaRoute2Info.Builder("id", "Test Name") - .addFeature("feature") + val routeInfo = MediaRoute2Info.Builder( + if (args.size >= 3) args[2] else "id", + "Test Name" + ).addFeature("feature") val useAppIcon = !(args.size >= 2 && args[1] == "useAppIcon=false") if (useAppIcon) { routeInfo.setClientPackageName(TEST_PACKAGE_NAME) @@ -144,7 +146,7 @@ class MediaTttCommandLineHelper @Inject constructor( override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar $RECEIVER_COMMAND " + - "<chipState> useAppIcon=[true|false]") + "<chipState> useAppIcon=[true|false] <id>") } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 8bddffc842f5..691953aaba36 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -121,18 +121,32 @@ class MediaTttChipControllerReceiver @Inject constructor( uiEventLogger.logReceiverStateChange(chipState) if (chipState == ChipStateReceiver.FAR_FROM_SENDER) { - removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER.name) + removeView(routeInfo.id, removalReason = ChipStateReceiver.FAR_FROM_SENDER.name) return } if (appIcon == null) { - displayView(ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appName)) + displayView( + ChipReceiverInfo( + routeInfo, + appIconDrawableOverride = null, + appName, + id = routeInfo.id, + ) + ) return } appIcon.loadDrawableAsync( context, Icon.OnDrawableLoadedListener { drawable -> - displayView(ChipReceiverInfo(routeInfo, drawable, appName)) + displayView( + ChipReceiverInfo( + routeInfo, + drawable, + appName, + id = routeInfo.id, + ) + ) }, // Notify the listener on the main handler since the listener will update // the UI. @@ -234,4 +248,5 @@ data class ChipReceiverInfo( val appNameOverride: CharSequence?, override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER, override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER, + override val id: String, ) : TemporaryViewInfo() diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt index d1ea2d0c83bd..bb7bc6fff99f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -108,7 +108,7 @@ constructor( } displayedState = null - chipbarCoordinator.removeView(removalReason) + chipbarCoordinator.removeView(routeInfo.id, removalReason) } else { displayedState = chipState chipbarCoordinator.displayView( @@ -162,6 +162,7 @@ constructor( windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER, wakeReason = MediaTttUtils.WAKE_REASON_SENDER, timeoutMs = chipStateSender.timeout, + id = routeInfo.id, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 20f1a8ed7689..1422a25bfe12 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -515,7 +515,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void setExpanded(boolean expanded) { if (DEBUG) Log.d(TAG, "setExpanded " + expanded); mQsExpanded = expanded; - updateQsPanelControllerListening(); + if (mInSplitShade && mQsExpanded) { + // in split shade QS is expanded immediately when shade expansion starts and then we + // also need to listen to changes - otherwise QS is updated only once its fully expanded + setListening(true); + } else { + updateQsPanelControllerListening(); + } updateQsState(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 2a80de0e24de..dd88c83949fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -25,6 +25,7 @@ import android.content.ComponentName; import android.content.res.Configuration; import android.content.res.Configuration.Orientation; import android.metrics.LogMaker; +import android.util.Log; import android.view.View; import com.android.internal.annotations.VisibleForTesting; @@ -38,6 +39,7 @@ import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileViewImpl; import com.android.systemui.util.LargeScreenUtils; import com.android.systemui.util.ViewController; import com.android.systemui.util.animation.DisappearParameters; @@ -237,6 +239,16 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr private void addTile(final QSTile tile, boolean collapsedView) { final TileRecord r = new TileRecord(tile, mHost.createTileView(getContext(), tile, collapsedView)); + // TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of + // b/250618218. + try { + QSTileViewImpl qsTileView = (QSTileViewImpl) (r.tileView); + if (qsTileView != null) { + qsTileView.setQsLogger(mQSLogger); + } + } catch (ClassCastException e) { + Log.e(TAG, "Failed to cast QSTileView to QSTileViewImpl", e); + } mView.addTile(r); mRecords.add(r); mCachedSpecs = getTilesSpecs(); 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 931dc8df151a..9f6317fd931b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -129,12 +129,36 @@ class QSLogger @Inject constructor( }) } - fun logInternetTileUpdate(lastType: Int, callback: String) { + fun logInternetTileUpdate(tileSpec: String, lastType: Int, callback: String) { log(VERBOSE, { + str1 = tileSpec int1 = lastType - str1 = callback + str2 = callback + }, { + "[$str1] mLastTileState=$int1, Callback=$str2." + }) + } + + // TODO(b/250618218): Remove this method once we know the root cause of b/250618218. + fun logTileBackgroundColorUpdateIfInternetTile( + tileSpec: String, + state: Int, + disabledByPolicy: Boolean, + color: Int + ) { + // This method is added to further debug b/250618218 which has only been observed from the + // InternetTile, so we are only logging the background color change for the InternetTile + // to avoid spamming the QSLogger. + if (tileSpec != "internet") { + return + } + log(VERBOSE, { + str1 = tileSpec + int1 = state + bool1 = disabledByPolicy + int2 = color }, { - "mLastTileState=$int1, Callback=$str1." + "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2." }) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 972b24343d10..b355d4bb67fe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -50,6 +50,7 @@ import com.android.systemui.plugins.qs.QSIconView import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.qs.QSTile.BooleanState import com.android.systemui.plugins.qs.QSTileView +import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH import java.util.Objects @@ -116,7 +117,7 @@ open class QSTileViewImpl @JvmOverloads constructor( protected lateinit var sideView: ViewGroup private lateinit var customDrawableView: ImageView private lateinit var chevronView: ImageView - + private var mQsLogger: QSLogger? = null protected var showRippleEffect = true private lateinit var ripple: RippleDrawable @@ -188,6 +189,10 @@ open class QSTileViewImpl @JvmOverloads constructor( updateHeight() } + fun setQsLogger(qsLogger: QSLogger) { + mQsLogger = qsLogger + } + fun updateResources() { FontSizeUtils.updateFontSize(label, R.dimen.qs_tile_text_size) FontSizeUtils.updateFontSize(secondaryLabel, R.dimen.qs_tile_text_size) @@ -493,6 +498,11 @@ open class QSTileViewImpl @JvmOverloads constructor( // Colors if (state.state != lastState || state.disabledByPolicy || lastDisabledByPolicy) { singleAnimator.cancel() + mQsLogger?.logTileBackgroundColorUpdateIfInternetTile( + state.spec, + state.state, + state.disabledByPolicy, + getBackgroundColorForState(state.state, state.disabledByPolicy)) if (allowAnimations) { singleAnimator.setValues( colorValuesHolder( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index d30402466d8d..5670b6de3c36 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -383,7 +383,8 @@ public class InternetTile extends QSTileImpl<SignalState> { @Override protected void handleUpdateState(SignalState state, Object arg) { - mQSLogger.logInternetTileUpdate(mLastTileState, arg == null ? "null" : arg.toString()); + mQSLogger.logInternetTileUpdate( + getTileSpec(), mLastTileState, arg == null ? "null" : arg.toString()); if (arg instanceof CellularCallbackInfo) { mLastTileState = 0; handleUpdateCellularState(state, arg); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 0dfb2f4063a9..27ad86f0ae12 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -32,12 +32,14 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; import android.text.Html; +import android.text.Layout; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewStub; import android.view.Window; import android.view.WindowManager; import android.widget.Button; @@ -61,6 +63,7 @@ import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -71,6 +74,8 @@ import com.android.wifitrackerlib.WifiEntry; import java.util.List; import java.util.concurrent.Executor; +import javax.inject.Inject; + /** * Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks. */ @@ -85,6 +90,7 @@ public class InternetDialog extends SystemUIDialog implements private final Handler mHandler; private final Executor mBackgroundExecutor; + private final DialogLaunchAnimator mDialogLaunchAnimator; @VisibleForTesting protected InternetAdapter mAdapter; @@ -108,6 +114,7 @@ public class InternetDialog extends SystemUIDialog implements private LinearLayout mInternetDialogLayout; private LinearLayout mConnectedWifListLayout; private LinearLayout mMobileNetworkLayout; + private LinearLayout mSecondaryMobileNetworkLayout; private LinearLayout mTurnWifiOnLayout; private LinearLayout mEthernetLayout; private TextView mWifiToggleTitleText; @@ -122,6 +129,8 @@ public class InternetDialog extends SystemUIDialog implements private ImageView mSignalIcon; private TextView mMobileTitleText; private TextView mMobileSummaryText; + private TextView mSecondaryMobileTitleText; + private TextView mSecondaryMobileSummaryText; private TextView mAirplaneModeSummaryText; private Switch mMobileDataToggle; private View mMobileToggleDivider; @@ -157,9 +166,11 @@ public class InternetDialog extends SystemUIDialog implements mInternetDialogSubTitle.setText(getSubtitleText()); }; + @Inject public InternetDialog(Context context, InternetDialogFactory internetDialogFactory, InternetDialogController internetDialogController, boolean canConfigMobileData, boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger, + DialogLaunchAnimator dialogLaunchAnimator, @Main Handler handler, @Background Executor executor, KeyguardStateController keyguardStateController) { super(context); @@ -182,6 +193,7 @@ public class InternetDialog extends SystemUIDialog implements mKeyguard = keyguardStateController; mUiEventLogger = uiEventLogger; + mDialogLaunchAnimator = dialogLaunchAnimator; mAdapter = new InternetAdapter(mInternetDialogController); if (!aboveStatusBar) { getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); @@ -286,6 +298,9 @@ public class InternetDialog extends SystemUIDialog implements mMobileNetworkLayout.setOnClickListener(null); mMobileDataToggle.setOnCheckedChangeListener(null); mConnectedWifListLayout.setOnClickListener(null); + if (mSecondaryMobileNetworkLayout != null) { + mSecondaryMobileNetworkLayout.setOnClickListener(null); + } mSeeAllLayout.setOnClickListener(null); mWiFiToggle.setOnCheckedChangeListener(null); mDoneButton.setOnClickListener(null); @@ -340,6 +355,10 @@ public class InternetDialog extends SystemUIDialog implements private void setOnClickListener() { mMobileNetworkLayout.setOnClickListener(v -> { + int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId(); + if (autoSwitchNonDdsSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + showTurnOffAutoDataSwitchDialog(autoSwitchNonDdsSubId); + } if (mInternetDialogController.isMobileDataEnabled() && !mInternetDialogController.isDeviceLocked()) { if (!mInternetDialogController.activeNetworkIsCellular()) { @@ -389,41 +408,28 @@ public class InternetDialog extends SystemUIDialog implements if (!mInternetDialogController.hasActiveSubId() && (!isWifiEnabled || !isCarrierNetworkActive)) { mMobileNetworkLayout.setVisibility(View.GONE); + if (mSecondaryMobileNetworkLayout != null) { + mSecondaryMobileNetworkLayout.setVisibility(View.GONE); + } } else { mMobileNetworkLayout.setVisibility(View.VISIBLE); mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled()); - mMobileTitleText.setText(getMobileNetworkTitle()); - String summary = getMobileNetworkSummary(); + mMobileTitleText.setText(getMobileNetworkTitle(mDefaultDataSubId)); + String summary = getMobileNetworkSummary(mDefaultDataSubId); if (!TextUtils.isEmpty(summary)) { mMobileSummaryText.setText( Html.fromHtml(summary, Html.FROM_HTML_MODE_LEGACY)); + mMobileSummaryText.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE); mMobileSummaryText.setVisibility(View.VISIBLE); } else { mMobileSummaryText.setVisibility(View.GONE); } mBackgroundExecutor.execute(() -> { - Drawable drawable = getSignalStrengthDrawable(); + Drawable drawable = getSignalStrengthDrawable(mDefaultDataSubId); mHandler.post(() -> { mSignalIcon.setImageDrawable(drawable); }); }); - mMobileTitleText.setTextAppearance(isNetworkConnected - ? R.style.TextAppearance_InternetDialog_Active - : R.style.TextAppearance_InternetDialog); - int secondaryRes = isNetworkConnected - ? R.style.TextAppearance_InternetDialog_Secondary_Active - : R.style.TextAppearance_InternetDialog_Secondary; - mMobileSummaryText.setTextAppearance(secondaryRes); - // Set airplane mode to the summary for carrier network - if (mInternetDialogController.isAirplaneModeEnabled()) { - mAirplaneModeSummaryText.setVisibility(View.VISIBLE); - mAirplaneModeSummaryText.setText(mContext.getText(R.string.airplane_mode)); - mAirplaneModeSummaryText.setTextAppearance(secondaryRes); - } else { - mAirplaneModeSummaryText.setVisibility(View.GONE); - } - mMobileNetworkLayout.setBackground( - isNetworkConnected ? mBackgroundOn : mBackgroundOff); TypedArray array = mContext.obtainStyledAttributes( R.style.InternetDialog_Divider_Active, new int[]{android.R.attr.background}); @@ -436,6 +442,86 @@ public class InternetDialog extends SystemUIDialog implements mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE); mMobileToggleDivider.setVisibility( mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE); + + // Display the info for the non-DDS if it's actively being used + int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId(); + int nonDdsVisibility = autoSwitchNonDdsSubId + != SubscriptionManager.INVALID_SUBSCRIPTION_ID ? View.VISIBLE : View.GONE; + + int secondaryRes = isNetworkConnected + ? R.style.TextAppearance_InternetDialog_Secondary_Active + : R.style.TextAppearance_InternetDialog_Secondary; + if (nonDdsVisibility == View.VISIBLE) { + // non DDS is the currently active sub, set primary visual for it + ViewStub stub = mDialogView.findViewById(R.id.secondary_mobile_network_stub); + if (stub != null) { + stub.inflate(); + } + mSecondaryMobileNetworkLayout = findViewById(R.id.secondary_mobile_network_layout); + mSecondaryMobileNetworkLayout.setOnClickListener( + this::onClickConnectedSecondarySub); + mSecondaryMobileNetworkLayout.setBackground(mBackgroundOn); + + mSecondaryMobileTitleText = mDialogView.requireViewById( + R.id.secondary_mobile_title); + mSecondaryMobileTitleText.setText(getMobileNetworkTitle(autoSwitchNonDdsSubId)); + mSecondaryMobileTitleText.setTextAppearance( + R.style.TextAppearance_InternetDialog_Active); + + mSecondaryMobileSummaryText = + mDialogView.requireViewById(R.id.secondary_mobile_summary); + summary = getMobileNetworkSummary(autoSwitchNonDdsSubId); + if (!TextUtils.isEmpty(summary)) { + mSecondaryMobileSummaryText.setText( + Html.fromHtml(summary, Html.FROM_HTML_MODE_LEGACY)); + mSecondaryMobileSummaryText.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE); + mSecondaryMobileSummaryText.setTextAppearance( + R.style.TextAppearance_InternetDialog_Active); + } + + ImageView mSecondarySignalIcon = + mDialogView.requireViewById(R.id.secondary_signal_icon); + mBackgroundExecutor.execute(() -> { + Drawable drawable = getSignalStrengthDrawable(autoSwitchNonDdsSubId); + mHandler.post(() -> { + mSecondarySignalIcon.setImageDrawable(drawable); + }); + }); + + ImageView mSecondaryMobileSettingsIcon = + mDialogView.requireViewById(R.id.secondary_settings_icon); + mSecondaryMobileSettingsIcon.setColorFilter( + mContext.getColor(R.color.connected_network_primary_color)); + + // set secondary visual for default data sub + mMobileNetworkLayout.setBackground(mBackgroundOff); + mMobileTitleText.setTextAppearance(R.style.TextAppearance_InternetDialog); + mMobileSummaryText.setTextAppearance( + R.style.TextAppearance_InternetDialog_Secondary); + mSignalIcon.setColorFilter( + mContext.getColor(R.color.connected_network_secondary_color)); + } else { + mMobileNetworkLayout.setBackground( + isNetworkConnected ? mBackgroundOn : mBackgroundOff); + mMobileTitleText.setTextAppearance(isNetworkConnected + ? + R.style.TextAppearance_InternetDialog_Active + : R.style.TextAppearance_InternetDialog); + mMobileSummaryText.setTextAppearance(secondaryRes); + } + + if (mSecondaryMobileNetworkLayout != null) { + mSecondaryMobileNetworkLayout.setVisibility(nonDdsVisibility); + } + + // Set airplane mode to the summary for carrier network + if (mInternetDialogController.isAirplaneModeEnabled()) { + mAirplaneModeSummaryText.setVisibility(View.VISIBLE); + mAirplaneModeSummaryText.setText(mContext.getText(R.string.airplane_mode)); + mAirplaneModeSummaryText.setTextAppearance(secondaryRes); + } else { + mAirplaneModeSummaryText.setVisibility(View.GONE); + } } } @@ -474,6 +560,10 @@ public class InternetDialog extends SystemUIDialog implements mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry)); mWifiSettingsIcon.setColorFilter( mContext.getColor(R.color.connected_network_primary_color)); + + if (mSecondaryMobileNetworkLayout != null) { + mSecondaryMobileNetworkLayout.setVisibility(View.GONE); + } } @MainThread @@ -544,6 +634,11 @@ public class InternetDialog extends SystemUIDialog implements mInternetDialogController.launchWifiDetailsSetting(mConnectedWifiEntry.getKey(), view); } + /** For DSDS auto data switch **/ + void onClickConnectedSecondarySub(View view) { + mInternetDialogController.launchMobileNetworkSettings(view); + } + void onClickSeeMoreButton(View view) { mInternetDialogController.launchNetworkSetting(view); } @@ -558,16 +653,16 @@ public class InternetDialog extends SystemUIDialog implements mIsProgressBarVisible && !mIsSearchingHidden); } - private Drawable getSignalStrengthDrawable() { - return mInternetDialogController.getSignalStrengthDrawable(); + private Drawable getSignalStrengthDrawable(int subId) { + return mInternetDialogController.getSignalStrengthDrawable(subId); } - CharSequence getMobileNetworkTitle() { - return mInternetDialogController.getMobileNetworkTitle(); + CharSequence getMobileNetworkTitle(int subId) { + return mInternetDialogController.getMobileNetworkTitle(subId); } - String getMobileNetworkSummary() { - return mInternetDialogController.getMobileNetworkSummary(); + String getMobileNetworkSummary(int subId) { + return mInternetDialogController.getMobileNetworkSummary(subId); } protected void showProgressBar() { @@ -605,8 +700,8 @@ public class InternetDialog extends SystemUIDialog implements } private void showTurnOffMobileDialog() { - CharSequence carrierName = getMobileNetworkTitle(); - boolean isInService = mInternetDialogController.isVoiceStateInService(); + CharSequence carrierName = getMobileNetworkTitle(mDefaultDataSubId); + boolean isInService = mInternetDialogController.isVoiceStateInService(mDefaultDataSubId); if (TextUtils.isEmpty(carrierName) || !isInService) { carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier); } @@ -630,7 +725,33 @@ public class InternetDialog extends SystemUIDialog implements SystemUIDialog.setShowForAllUsers(mAlertDialog, true); SystemUIDialog.registerDismissListener(mAlertDialog); SystemUIDialog.setWindowOnTop(mAlertDialog, mKeyguard.isShowing()); - mAlertDialog.show(); + mDialogLaunchAnimator.showFromDialog(mAlertDialog, this, null, false); + } + + private void showTurnOffAutoDataSwitchDialog(int subId) { + CharSequence carrierName = getMobileNetworkTitle(mDefaultDataSubId); + if (TextUtils.isEmpty(carrierName)) { + carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier); + } + mAlertDialog = new Builder(mContext) + .setTitle(mContext.getString(R.string.auto_data_switch_disable_title, carrierName)) + .setMessage(R.string.auto_data_switch_disable_message) + .setNegativeButton(R.string.auto_data_switch_dialog_negative_button, + (d, w) -> {}) + .setPositiveButton(R.string.auto_data_switch_dialog_positive_button, + (d, w) -> { + mInternetDialogController + .setAutoDataSwitchMobileDataPolicy(subId, false); + if (mSecondaryMobileNetworkLayout != null) { + mSecondaryMobileNetworkLayout.setVisibility(View.GONE); + } + }) + .create(); + mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + SystemUIDialog.setShowForAllUsers(mAlertDialog, true); + SystemUIDialog.registerDismissListener(mAlertDialog); + SystemUIDialog.setWindowOnTop(mAlertDialog, mKeyguard.isShowing()); + mDialogLaunchAnimator.showFromDialog(mAlertDialog, this, null, false); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 9c0a087c01b8..4c7f10e2bc6b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -37,6 +37,7 @@ import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.wifi.WifiManager; +import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; @@ -78,6 +79,8 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -90,6 +93,7 @@ import com.android.wifitrackerlib.MergedCarrierEntry; import com.android.wifitrackerlib.WifiEntry; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -113,6 +117,17 @@ public class InternetDialogController implements AccessPointController.AccessPoi "android.settings.NETWORK_PROVIDER_SETTINGS"; private static final String ACTION_WIFI_SCANNING_SETTINGS = "android.settings.WIFI_SCANNING_SETTINGS"; + /** + * Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} + */ + private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; + /** + * When starting this activity, this extra can also be specified to supply a Bundle of arguments + * to pass to that fragment when it is instantiated during the initial creation of the activity. + */ + private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS = + ":settings:show_fragment_args"; + private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch"; public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT); public static final int NO_CELL_DATA_TYPE_ICON = 0; private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off; @@ -130,9 +145,12 @@ public class InternetDialogController implements AccessPointController.AccessPoi static final int MAX_WIFI_ENTRY_COUNT = 3; + private final FeatureFlags mFeatureFlags; + private WifiManager mWifiManager; private Context mContext; private SubscriptionManager mSubscriptionManager; + private Map<Integer, TelephonyManager> mSubIdTelephonyManagerMap = new HashMap<>(); private TelephonyManager mTelephonyManager; private ConnectivityManager mConnectivityManager; private CarrierConfigTracker mCarrierConfigTracker; @@ -155,6 +173,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi private WindowManager mWindowManager; private ToastFactory mToastFactory; private SignalDrawable mSignalDrawable; + private SignalDrawable mSecondarySignalDrawable; // For the secondary mobile data sub in DSDS private LocationController mLocationController; private DialogLaunchAnimator mDialogLaunchAnimator; private boolean mHasWifiEntries; @@ -213,7 +232,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi CarrierConfigTracker carrierConfigTracker, LocationController locationController, DialogLaunchAnimator dialogLaunchAnimator, - WifiStateWorker wifiStateWorker + WifiStateWorker wifiStateWorker, + FeatureFlags featureFlags ) { if (DEBUG) { Log.d(TAG, "Init InternetDialogController"); @@ -242,10 +262,12 @@ public class InternetDialogController implements AccessPointController.AccessPoi mWindowManager = windowManager; mToastFactory = toastFactory; mSignalDrawable = new SignalDrawable(mContext); + mSecondarySignalDrawable = new SignalDrawable(mContext); mLocationController = locationController; mDialogLaunchAnimator = dialogLaunchAnimator; mConnectedWifiInternetMonitor = new ConnectedWifiInternetMonitor(); mWifiStateWorker = wifiStateWorker; + mFeatureFlags = featureFlags; } void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) { @@ -267,6 +289,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi } mConfig = MobileMappings.Config.readConfig(mContext); mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId); + mSubIdTelephonyManagerMap.put(mDefaultDataSubId, mTelephonyManager); mInternetTelephonyCallback = new InternetTelephonyCallback(); mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback); // Listen the connectivity changes @@ -280,7 +303,9 @@ public class InternetDialogController implements AccessPointController.AccessPoi Log.d(TAG, "onStop"); } mBroadcastDispatcher.unregisterReceiver(mConnectionStateReceiver); - mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback); + for (TelephonyManager tm : mSubIdTelephonyManagerMap.values()) { + tm.unregisterTelephonyCallback(mInternetTelephonyCallback); + } mSubscriptionManager.removeOnSubscriptionsChangedListener( mOnSubscriptionsChangedListener); mAccessPointController.removeAccessPointCallback(this); @@ -371,7 +396,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi if (DEBUG) { Log.d(TAG, "No Wi-Fi item."); } - if (!hasActiveSubId() || (!isVoiceStateInService() && !isDataStateInService())) { + boolean isActiveOnNonDds = getActiveAutoSwitchNonDdsSubId() != SubscriptionManager + .INVALID_SUBSCRIPTION_ID; + if (!hasActiveSubId() || (!isVoiceStateInService(mDefaultDataSubId) + && !isDataStateInService(mDefaultDataSubId) && !isActiveOnNonDds)) { if (DEBUG) { Log.d(TAG, "No carrier or service is out of service."); } @@ -412,7 +440,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi return drawable; } - Drawable getSignalStrengthDrawable() { + Drawable getSignalStrengthDrawable(int subId) { Drawable drawable = mContext.getDrawable( R.drawable.ic_signal_strength_zero_bar_no_internet); try { @@ -424,9 +452,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi } boolean isCarrierNetworkActive = isCarrierNetworkActive(); - if (isDataStateInService() || isVoiceStateInService() || isCarrierNetworkActive) { + if (isDataStateInService(subId) || isVoiceStateInService(subId) + || isCarrierNetworkActive) { AtomicReference<Drawable> shared = new AtomicReference<>(); - shared.set(getSignalStrengthDrawableWithLevel(isCarrierNetworkActive)); + shared.set(getSignalStrengthDrawableWithLevel(isCarrierNetworkActive, subId)); drawable = shared.get(); } @@ -447,24 +476,30 @@ public class InternetDialogController implements AccessPointController.AccessPoi * * @return The Drawable which is a signal bar icon with level. */ - Drawable getSignalStrengthDrawableWithLevel(boolean isCarrierNetworkActive) { - final SignalStrength strength = mTelephonyManager.getSignalStrength(); + Drawable getSignalStrengthDrawableWithLevel(boolean isCarrierNetworkActive, int subId) { + TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager); + final SignalStrength strength = tm.getSignalStrength(); int level = (strength == null) ? 0 : strength.getLevel(); int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS; if (isCarrierNetworkActive) { level = getCarrierNetworkLevel(); numLevels = WifiEntry.WIFI_LEVEL_MAX + 1; - } else if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) { + } else if (mSubscriptionManager != null && shouldInflateSignalStrength(subId)) { level += 1; numLevels += 1; } - return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON, + return getSignalStrengthIcon(subId, mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON, !isMobileDataEnabled()); } - Drawable getSignalStrengthIcon(Context context, int level, int numLevels, + Drawable getSignalStrengthIcon(int subId, Context context, int level, int numLevels, int iconType, boolean cutOut) { - mSignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut)); + boolean isForDds = subId == mDefaultDataSubId; + if (isForDds) { + mSignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut)); + } else { + mSecondarySignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut)); + } // Make the network type drawable final Drawable networkDrawable = @@ -473,7 +508,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi : context.getResources().getDrawable(iconType, context.getTheme()); // Overlay the two drawables - final Drawable[] layers = {networkDrawable, mSignalDrawable}; + final Drawable[] layers = {networkDrawable, isForDds + ? mSignalDrawable : mSecondarySignalDrawable}; final int iconSize = context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size); @@ -571,14 +607,39 @@ public class InternetDialogController implements AccessPointController.AccessPoi info -> info.uniqueName)); } - CharSequence getMobileNetworkTitle() { - return getUniqueSubscriptionDisplayName(mDefaultDataSubId, mContext); + /** + * @return the subId of the visible non-DDS if it's actively being used for data, otherwise + * return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. + */ + int getActiveAutoSwitchNonDdsSubId() { + if (!mFeatureFlags.isEnabled(Flags.QS_SECONDARY_DATA_SUB_INFO)) { + // sets the non-DDS to be not found to hide its visual + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo( + SubscriptionManager.getActiveDataSubscriptionId()); + if (subInfo != null && subInfo.getSubscriptionId() != mDefaultDataSubId + && !subInfo.isOpportunistic()) { + int subId = subInfo.getSubscriptionId(); + if (mSubIdTelephonyManagerMap.get(subId) == null) { + TelephonyManager secondaryTm = mTelephonyManager.createForSubscriptionId(subId); + secondaryTm.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback); + mSubIdTelephonyManagerMap.put(subId, secondaryTm); + } + return subId; + } + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + + } + + CharSequence getMobileNetworkTitle(int subId) { + return getUniqueSubscriptionDisplayName(subId, mContext); } - String getMobileNetworkSummary() { + String getMobileNetworkSummary(int subId) { String description = getNetworkTypeDescription(mContext, mConfig, - mTelephonyDisplayInfo, mDefaultDataSubId); - return getMobileSummary(mContext, description); + mTelephonyDisplayInfo, subId); + return getMobileSummary(mContext, description, subId); } /** @@ -606,22 +667,28 @@ public class InternetDialogController implements AccessPointController.AccessPoi ? SubscriptionManager.getResourcesForSubId(context, subId).getString(resId) : ""; } - private String getMobileSummary(Context context, String networkTypeDescription) { + private String getMobileSummary(Context context, String networkTypeDescription, int subId) { if (!isMobileDataEnabled()) { return context.getString(R.string.mobile_data_off_summary); } String summary = networkTypeDescription; + boolean isForDds = subId == mDefaultDataSubId; + int activeSubId = getActiveAutoSwitchNonDdsSubId(); + boolean isOnNonDds = activeSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID; // Set network description for the carrier network when connecting to the carrier network // under the airplane mode ON. if (activeNetworkIsCellular() || isCarrierNetworkActive()) { summary = context.getString(R.string.preference_summary_default_combination, - context.getString(R.string.mobile_data_connection_active), + context.getString( + isForDds // if nonDds is active, explains Dds status as poor connection + ? (isOnNonDds ? R.string.mobile_data_poor_connection + : R.string.mobile_data_connection_active) + : R.string.mobile_data_temp_connection_active), networkTypeDescription); - } else if (!isDataStateInService()) { + } else if (!isDataStateInService(subId)) { summary = context.getString(R.string.mobile_data_no_connection); } - return summary; } @@ -647,6 +714,26 @@ public class InternetDialogController implements AccessPointController.AccessPoi } } + void launchMobileNetworkSettings(View view) { + final int subId = getActiveAutoSwitchNonDdsSubId(); + if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + Log.w(TAG, "launchMobileNetworkSettings fail, invalid subId:" + subId); + return; + } + startActivity(getSubSettingIntent(subId), view); + } + + Intent getSubSettingIntent(int subId) { + final Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS); + + final Bundle fragmentArgs = new Bundle(); + // Special contract for Settings to highlight permission row + fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID); + fragmentArgs.putInt(Settings.EXTRA_SUB_ID, subId); + intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs); + return intent; + } + void launchWifiScanningSetting(View view) { final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -796,8 +883,20 @@ public class InternetDialogController implements AccessPointController.AccessPoi mWorkerHandler.post(() -> setMergedCarrierWifiEnabledIfNeed(subId, enabled)); } - boolean isDataStateInService() { - final ServiceState serviceState = mTelephonyManager.getServiceState(); + void setAutoDataSwitchMobileDataPolicy(int subId, boolean enable) { + TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager); + if (tm == null) { + if (DEBUG) { + Log.d(TAG, "TelephonyManager is null, can not set mobile data."); + } + return; + } + tm.setMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, enable); + } + + boolean isDataStateInService(int subId) { + TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager); + final ServiceState serviceState = tm.getServiceState(); NetworkRegistrationInfo regInfo = (serviceState == null) ? null : serviceState.getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, @@ -805,7 +904,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi return (regInfo == null) ? false : regInfo.isRegistered(); } - boolean isVoiceStateInService() { + boolean isVoiceStateInService(int subId) { if (mTelephonyManager == null) { if (DEBUG) { Log.d(TAG, "TelephonyManager is null, can not detect voice state."); @@ -813,7 +912,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi return false; } - final ServiceState serviceState = mTelephonyManager.getServiceState(); + TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager); + final ServiceState serviceState = tm.getServiceState(); return serviceState != null && serviceState.getState() == serviceState.STATE_IN_SERVICE; } @@ -1104,6 +1204,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi if (SubscriptionManager.isUsableSubscriptionId(mDefaultDataSubId)) { mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback); mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId); + mSubIdTelephonyManagerMap.put(mDefaultDataSubId, mTelephonyManager); mTelephonyManager.registerTelephonyCallback(mHandler::post, mInternetTelephonyCallback); mCallback.onSubscriptionsChanged(mDefaultDataSubId); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt index 8566ca308738..796672dc0ead 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt @@ -66,7 +66,8 @@ class InternetDialogFactory @Inject constructor( } else { internetDialog = InternetDialog( context, this, internetDialogController, - canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler, + canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, + dialogLaunchAnimator, handler, executor, keyguardStateController ) if (view != null) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt index 5961635a0dba..01e32b7ada5f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -32,7 +32,7 @@ import android.view.WindowManagerGlobal import com.android.internal.infra.ServiceConnector import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import javax.inject.Inject import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineDispatcher @@ -45,7 +45,7 @@ class ActionIntentExecutor @Inject constructor( @Application private val applicationScope: CoroutineScope, - @Background private val bgDispatcher: CoroutineDispatcher, + @Main private val mainDispatcher: CoroutineDispatcher, private val context: Context, ) { /** @@ -70,23 +70,21 @@ constructor( userId: Int, overrideTransition: Boolean, ) { - withContext(bgDispatcher) { - dismissKeyguard() + dismissKeyguard() - if (userId == UserHandle.myUserId()) { - context.startActivity(intent, bundle) - } else { - launchCrossProfileIntent(userId, intent, bundle) - } + if (userId == UserHandle.myUserId()) { + withContext(mainDispatcher) { context.startActivity(intent, bundle) } + } else { + launchCrossProfileIntent(userId, intent, bundle) + } - if (overrideTransition) { - val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0) - try { - WindowManagerGlobal.getWindowManagerService() - .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY) - } catch (e: Exception) { - Log.e(TAG, "Error overriding screenshot app transition", e) - } + if (overrideTransition) { + val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0) + try { + WindowManagerGlobal.getWindowManagerService() + .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY) + } catch (e: Exception) { + Log.e(TAG, "Error overriding screenshot app transition", e) } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 8bf956b86683..5450db98af52 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -46,6 +46,8 @@ import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot; import com.google.common.util.concurrent.ListenableFuture; @@ -67,6 +69,7 @@ public class LongScreenshotActivity extends Activity { private static final String TAG = LogConfig.logTag(LongScreenshotActivity.class); public static final String EXTRA_CAPTURE_RESPONSE = "capture-response"; + public static final String EXTRA_SCREENSHOT_USER_HANDLE = "screenshot-userhandle"; private static final String KEY_SAVED_IMAGE_PATH = "saved-image-path"; private final UiEventLogger mUiEventLogger; @@ -74,6 +77,8 @@ public class LongScreenshotActivity extends Activity { private final Executor mBackgroundExecutor; private final ImageExporter mImageExporter; private final LongScreenshotData mLongScreenshotHolder; + private final ActionIntentExecutor mActionExecutor; + private final FeatureFlags mFeatureFlags; private ImageView mPreview; private ImageView mTransitionView; @@ -85,6 +90,7 @@ public class LongScreenshotActivity extends Activity { private CropView mCropView; private MagnifierView mMagnifierView; private ScrollCaptureResponse mScrollCaptureResponse; + private UserHandle mScreenshotUserHandle; private File mSavedImagePath; private ListenableFuture<File> mCacheSaveFuture; @@ -103,12 +109,15 @@ public class LongScreenshotActivity extends Activity { @Inject public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter, @Main Executor mainExecutor, @Background Executor bgExecutor, - LongScreenshotData longScreenshotHolder) { + LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor, + FeatureFlags featureFlags) { mUiEventLogger = uiEventLogger; mUiExecutor = mainExecutor; mBackgroundExecutor = bgExecutor; mImageExporter = imageExporter; mLongScreenshotHolder = longScreenshotHolder; + mActionExecutor = actionExecutor; + mFeatureFlags = featureFlags; } @@ -139,6 +148,11 @@ public class LongScreenshotActivity extends Activity { Intent intent = getIntent(); mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE); + mScreenshotUserHandle = intent.getParcelableExtra(EXTRA_SCREENSHOT_USER_HANDLE, + UserHandle.class); + if (mScreenshotUserHandle == null) { + mScreenshotUserHandle = Process.myUserHandle(); + } if (savedInstanceState != null) { String savedImagePath = savedInstanceState.getString(KEY_SAVED_IMAGE_PATH); @@ -318,36 +332,51 @@ public class LongScreenshotActivity extends Activity { } private void doEdit(Uri uri) { - String editorPackage = getString(R.string.config_screenshotEditor); - Intent intent = new Intent(Intent.ACTION_EDIT); - if (!TextUtils.isEmpty(editorPackage)) { - intent.setComponent(ComponentName.unflattenFromString(editorPackage)); + if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY) && mScreenshotUserHandle + != Process.myUserHandle()) { + // TODO: Fix transition for work profile. Omitting it in the meantime. + mActionExecutor.launchIntentAsync( + ActionIntentCreator.INSTANCE.createEditIntent(uri, this), + null, + mScreenshotUserHandle.getIdentifier(), false); + } else { + String editorPackage = getString(R.string.config_screenshotEditor); + Intent intent = new Intent(Intent.ACTION_EDIT); + if (!TextUtils.isEmpty(editorPackage)) { + intent.setComponent(ComponentName.unflattenFromString(editorPackage)); + } + intent.setDataAndType(uri, "image/png"); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + mTransitionView.setImageBitmap(mOutputBitmap); + mTransitionView.setVisibility(View.VISIBLE); + mTransitionView.setTransitionName( + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); + // TODO: listen for transition completing instead of finishing onStop + mTransitionStarted = true; + startActivity(intent, + ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView, + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle()); } - intent.setDataAndType(uri, "image/png"); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - - mTransitionView.setImageBitmap(mOutputBitmap); - mTransitionView.setVisibility(View.VISIBLE); - mTransitionView.setTransitionName( - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); - // TODO: listen for transition completing instead of finishing onStop - mTransitionStarted = true; - startActivity(intent, - ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView, - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle()); } private void doShare(Uri uri) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("image/png"); - intent.putExtra(Intent.EXTRA_STREAM, uri); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_GRANT_READ_URI_PERMISSION); - Intent sharingChooserIntent = Intent.createChooser(intent, null) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT); + if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { + Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri, null); + mActionExecutor.launchIntentAsync(shareIntent, null, + mScreenshotUserHandle.getIdentifier(), false); + } else { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("image/png"); + intent.putExtra(Intent.EXTRA_STREAM, uri); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_GRANT_READ_URI_PERMISSION); + Intent sharingChooserIntent = Intent.createChooser(intent, null) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT); + } } private void onClicked(View v) { @@ -389,8 +418,8 @@ public class LongScreenshotActivity extends Activity { mOutputBitmap = renderBitmap(drawable, bounds); ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export( mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now(), - // TODO: Owner must match the owner of the captured window. - Process.myUserHandle()); + mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY) + ? mScreenshotUserHandle : Process.myUserHandle()); exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index d395bd33241d..d94c8277b82c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -591,7 +591,7 @@ public class ScreenshotController { // Wait until this window is attached to request because it is // the reference used to locate the target window (below). withWindowAttached(() -> { - requestScrollCapture(); + requestScrollCapture(owner); mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback( new ViewRootImpl.ActivityConfigCallback() { @Override @@ -603,11 +603,11 @@ public class ScreenshotController { mScreenshotView.hideScrollChip(); // Delay scroll capture eval a bit to allow the underlying activity // to set up in the new orientation. - mScreenshotHandler.postDelayed( - ScreenshotController.this::requestScrollCapture, 150); + mScreenshotHandler.postDelayed(() -> { + requestScrollCapture(owner); + }, 150); mScreenshotView.updateInsets( - mWindowManager.getCurrentWindowMetrics() - .getWindowInsets()); + mWindowManager.getCurrentWindowMetrics().getWindowInsets()); // Screenshot animation calculations won't be valid anymore, // so just end if (mScreenshotAnimation != null @@ -655,7 +655,7 @@ public class ScreenshotController { mScreenshotHandler.cancelTimeout(); // restarted after animation } - private void requestScrollCapture() { + private void requestScrollCapture(UserHandle owner) { if (!allowLongScreenshots()) { Log.d(TAG, "Long screenshots not supported on this device"); return; @@ -668,10 +668,11 @@ public class ScreenshotController { mScrollCaptureClient.request(DEFAULT_DISPLAY); mLastScrollCaptureRequest = future; mLastScrollCaptureRequest.addListener(() -> - onScrollCaptureResponseReady(future), mMainExecutor); + onScrollCaptureResponseReady(future, owner), mMainExecutor); } - private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) { + private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture, + UserHandle owner) { try { if (mLastScrollCaptureResponse != null) { mLastScrollCaptureResponse.close(); @@ -701,7 +702,7 @@ public class ScreenshotController { mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait); // delay starting scroll capture to make sure the scrim is up before the app moves - mScreenshotView.post(() -> runBatchScrollCapture(response)); + mScreenshotView.post(() -> runBatchScrollCapture(response, owner)); }); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "requestScrollCapture failed", e); @@ -710,7 +711,7 @@ public class ScreenshotController { ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture; - private void runBatchScrollCapture(ScrollCaptureResponse response) { + private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) { // Clear the reference to prevent close() in dismissScreenshot mLastScrollCaptureResponse = null; @@ -744,6 +745,8 @@ public class ScreenshotController { longScreenshot)); final Intent intent = new Intent(mContext, LongScreenshotActivity.class); + intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE, + owner); intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java index 8b5a24c0e2ff..c891686ada8f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java @@ -89,7 +89,9 @@ public enum ScreenshotEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "User has saved a long screenshot to a file") SCREENSHOT_LONG_SCREENSHOT_SAVED(910), @UiEvent(doc = "User has discarded the result of a long screenshot") - SCREENSHOT_LONG_SCREENSHOT_EXIT(911); + SCREENSHOT_LONG_SCREENSHOT_EXIT(911), + @UiEvent(doc = "A screenshot has been taken and saved to work profile") + SCREENSHOT_SAVED_TO_WORK_PROFILE(1240); private final int mId; diff --git a/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java new file mode 100644 index 000000000000..fc61e90ab8f7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java @@ -0,0 +1,73 @@ +/* + * 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.shade; + +import com.android.systemui.camera.CameraGestureHelper; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.phone.KeyguardBypassController; + +import javax.inject.Inject; + +/** Handles launching camera from Shade. */ +@SysUISingleton +public class CameraLauncher { + private final CameraGestureHelper mCameraGestureHelper; + private final KeyguardBypassController mKeyguardBypassController; + + private boolean mLaunchingAffordance; + + @Inject + public CameraLauncher( + CameraGestureHelper cameraGestureHelper, + KeyguardBypassController keyguardBypassController + ) { + mCameraGestureHelper = cameraGestureHelper; + mKeyguardBypassController = keyguardBypassController; + } + + /** Launches the camera. */ + public void launchCamera(int source, boolean isShadeFullyCollapsed) { + if (!isShadeFullyCollapsed) { + setLaunchingAffordance(true); + } + + mCameraGestureHelper.launchCamera(source); + } + + /** + * Set whether we are currently launching an affordance. This is currently only set when + * launched via a camera gesture. + */ + public void setLaunchingAffordance(boolean launchingAffordance) { + mLaunchingAffordance = launchingAffordance; + mKeyguardBypassController.setLaunchingAffordance(launchingAffordance); + } + + /** + * Return true when a bottom affordance is launching an occluded activity with a splash screen. + */ + public boolean isLaunchingAffordance() { + return mLaunchingAffordance; + } + + /** + * Whether the camera application can be launched for the camera launch gesture. + */ + public boolean canCameraGestureBeLaunched(int barState) { + return mCameraGestureHelper.canCameraGestureBeLaunched(barState); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt index 4063af3cbc36..954534d42fdd 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt @@ -92,7 +92,6 @@ object CombinedShadeHeadersConstraintManagerImpl : CombinedShadeHeadersConstrain centerEnd, ConstraintSet.END ) - constrainWidth(R.id.statusIcons, 0) }, qsConstraintsChanges = { setGuidelineBegin(centerStart, offsetFromEdge) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 8040e40f4e91..32c8f3bba6c1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -124,7 +124,6 @@ import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.LaunchAnimator; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.camera.CameraGestureHelper; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.DisplayId; @@ -464,7 +463,6 @@ public final class NotificationPanelViewController implements Dumpable { private boolean mCollapsedOnDown; private boolean mClosingWithAlphaFadeOut; private boolean mHeadsUpAnimatingAway; - private boolean mLaunchingAffordance; private final FalsingManager mFalsingManager; private final FalsingCollector mFalsingCollector; @@ -615,7 +613,6 @@ public final class NotificationPanelViewController implements Dumpable { private final NotificationListContainer mNotificationListContainer; private final NotificationStackSizeCalculator mNotificationStackSizeCalculator; private final NPVCDownEventState.Buffer mLastDownEvents; - private final CameraGestureHelper mCameraGestureHelper; private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; private float mMinExpandHeight; @@ -743,7 +740,6 @@ public final class NotificationPanelViewController implements Dumpable { UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, ShadeTransitionController shadeTransitionController, SystemClock systemClock, - CameraGestureHelper cameraGestureHelper, KeyguardBottomAreaViewModel keyguardBottomAreaViewModel, KeyguardBottomAreaInteractor keyguardBottomAreaInteractor, DumpManager dumpManager) { @@ -924,7 +920,6 @@ public final class NotificationPanelViewController implements Dumpable { unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay); } }); - mCameraGestureHelper = cameraGestureHelper; mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor; dumpManager.registerDumpable(this); } @@ -3946,6 +3941,10 @@ public final class NotificationPanelViewController implements Dumpable { } } + public int getBarState() { + return mBarState; + } + private boolean isOnKeyguard() { return mBarState == KEYGUARD; } @@ -3991,35 +3990,6 @@ public final class NotificationPanelViewController implements Dumpable { && mBarState == StatusBarState.SHADE; } - /** Launches the camera. */ - public void launchCamera(int source) { - if (!isFullyCollapsed()) { - setLaunchingAffordance(true); - } - - mCameraGestureHelper.launchCamera(source); - } - - public void onAffordanceLaunchEnded() { - setLaunchingAffordance(false); - } - - /** Set whether we are currently launching an affordance (i.e. camera gesture). */ - private void setLaunchingAffordance(boolean launchingAffordance) { - mLaunchingAffordance = launchingAffordance; - mKeyguardBypassController.setLaunchingAffordance(launchingAffordance); - } - - /** Returns whether a bottom affordance is launching an occluded activity with splash screen. */ - public boolean isLaunchingAffordanceWithPreview() { - return mLaunchingAffordance; - } - - /** Whether the camera application can be launched by the camera launch gesture. */ - public boolean canCameraGestureBeLaunched() { - return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState); - } - public boolean hideStatusBarIconsWhenExpanded() { if (mIsLaunchAnimationRunning) { return mHideIconsDuringLaunchAnimation; @@ -4358,7 +4328,6 @@ public final class NotificationPanelViewController implements Dumpable { ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown); ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut); ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway); - ipw.print("mLaunchingAffordance="); ipw.println(mLaunchingAffordance); ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded); ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding); ipw.print("mAmbientIndicationBottomPadding="); ipw.println(mAmbientIndicationBottomPadding); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index e52170e13292..400b0baea01b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -16,6 +16,7 @@ package com.android.systemui.shade; +import static android.os.Trace.TRACE_TAG_ALWAYS; import static android.view.WindowInsets.Type.systemBars; import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; @@ -33,6 +34,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.os.Trace; import android.util.AttributeSet; import android.view.ActionMode; import android.view.DisplayCutout; @@ -299,6 +301,19 @@ public class NotificationShadeWindowView extends FrameLayout { return mode; } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Trace.beginSection("NotificationShadeWindowView#onMeasure"); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + Trace.endSection(); + } + + @Override + public void requestLayout() { + Trace.instant(TRACE_TAG_ALWAYS, "NotificationShadeWindowView#requestLayout"); + super.requestLayout(); + } + private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { private final ActionMode.Callback mWrapped; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 0deb47d73460..15f4b12ba10e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -1179,16 +1179,14 @@ public class KeyguardIndicationController { @Override public void onTrustChanged(int userId) { - if (getCurrentUser() != userId) { - return; - } + if (!isCurrentUser(userId)) return; updateDeviceEntryIndication(false); } @Override - public void showTrustGrantedMessage(CharSequence message) { - mTrustGrantedIndication = message; - updateDeviceEntryIndication(false); + public void onTrustGrantedWithFlags(int flags, int userId, @Nullable String message) { + if (!isCurrentUser(userId)) return; + showTrustGrantedMessage(flags, message); } @Override @@ -1248,6 +1246,15 @@ public class KeyguardIndicationController { } } + private boolean isCurrentUser(int userId) { + return getCurrentUser() == userId; + } + + void showTrustGrantedMessage(int flags, @Nullable CharSequence message) { + mTrustGrantedIndication = message; + updateDeviceEntryIndication(false); + } + private void handleFaceLockoutError(String errString) { int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint : R.string.keyguard_unlock; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 9d2750fa7b5f..bc456d5d4613 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -18,6 +18,8 @@ import android.view.View import com.android.systemui.animation.Interpolators import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold import com.android.systemui.util.getColorWithAlpha +import com.android.systemui.util.leak.RotationUtils +import com.android.systemui.util.leak.RotationUtils.Rotation import java.util.function.Consumer /** @@ -67,22 +69,19 @@ object LiftReveal : LightRevealEffect { override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) { val interpolatedAmount = INTERPOLATOR.getInterpolation(amount) val ovalWidthIncreaseAmount = - getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD) + getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD) val initialWidthMultiplier = (1f - OVAL_INITIAL_WIDTH_PERCENT) / 2f with(scrim) { - revealGradientEndColorAlpha = 1f - getPercentPastThreshold( - amount, FADE_END_COLOR_OUT_THRESHOLD) + revealGradientEndColorAlpha = + 1f - getPercentPastThreshold(amount, FADE_END_COLOR_OUT_THRESHOLD) setRevealGradientBounds( - scrim.width * initialWidthMultiplier + - -scrim.width * ovalWidthIncreaseAmount, - scrim.height * OVAL_INITIAL_TOP_PERCENT - - scrim.height * interpolatedAmount, - scrim.width * (1f - initialWidthMultiplier) + - scrim.width * ovalWidthIncreaseAmount, - scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + - scrim.height * interpolatedAmount) + scrim.width * initialWidthMultiplier + -scrim.width * ovalWidthIncreaseAmount, + scrim.height * OVAL_INITIAL_TOP_PERCENT - scrim.height * interpolatedAmount, + scrim.width * (1f - initialWidthMultiplier) + scrim.width * ovalWidthIncreaseAmount, + scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount + ) } } } @@ -97,12 +96,17 @@ class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffe scrim.interpolatedRevealAmount = interpolatedAmount scrim.startColorAlpha = - getPercentPastThreshold(1 - interpolatedAmount, - threshold = 1 - START_COLOR_REVEAL_PERCENTAGE) + getPercentPastThreshold( + 1 - interpolatedAmount, + threshold = 1 - START_COLOR_REVEAL_PERCENTAGE + ) scrim.revealGradientEndColorAlpha = - 1f - getPercentPastThreshold(interpolatedAmount, - threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE) + 1f - + getPercentPastThreshold( + interpolatedAmount, + threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE + ) // Start changing gradient bounds later to avoid harsh gradient in the beginning val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1.0f, interpolatedAmount) @@ -179,7 +183,7 @@ class PowerButtonReveal( */ private val OFF_SCREEN_START_AMOUNT = 0.05f - private val WIDTH_INCREASE_MULTIPLIER = 1.25f + private val INCREASE_MULTIPLIER = 1.25f override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) { val interpolatedAmount = Interpolators.FAST_OUT_SLOW_IN_REVERSE.getInterpolation(amount) @@ -188,15 +192,36 @@ class PowerButtonReveal( with(scrim) { revealGradientEndColorAlpha = 1f - fadeAmount interpolatedRevealAmount = interpolatedAmount - setRevealGradientBounds( + @Rotation val rotation = RotationUtils.getRotation(scrim.getContext()) + if (rotation == RotationUtils.ROTATION_NONE) { + setRevealGradientBounds( width * (1f + OFF_SCREEN_START_AMOUNT) - - width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount, - powerButtonY - - height * interpolatedAmount, + width * INCREASE_MULTIPLIER * interpolatedAmount, + powerButtonY - height * interpolatedAmount, width * (1f + OFF_SCREEN_START_AMOUNT) + - width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount, - powerButtonY + - height * interpolatedAmount) + width * INCREASE_MULTIPLIER * interpolatedAmount, + powerButtonY + height * interpolatedAmount + ) + } else if (rotation == RotationUtils.ROTATION_LANDSCAPE) { + setRevealGradientBounds( + powerButtonY - width * interpolatedAmount, + (-height * OFF_SCREEN_START_AMOUNT) - + height * INCREASE_MULTIPLIER * interpolatedAmount, + powerButtonY + width * interpolatedAmount, + (-height * OFF_SCREEN_START_AMOUNT) + + height * INCREASE_MULTIPLIER * interpolatedAmount + ) + } else { + // RotationUtils.ROTATION_SEASCAPE + setRevealGradientBounds( + (width - powerButtonY) - width * interpolatedAmount, + height * (1f + OFF_SCREEN_START_AMOUNT) - + height * INCREASE_MULTIPLIER * interpolatedAmount, + (width - powerButtonY) + width * interpolatedAmount, + height * (1f + OFF_SCREEN_START_AMOUNT) + + height * INCREASE_MULTIPLIER * interpolatedAmount + ) + } } } } @@ -208,9 +233,7 @@ class PowerButtonReveal( */ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - /** - * Listener that is called if the scrim's opaqueness changes - */ + /** Listener that is called if the scrim's opaqueness changes */ lateinit var isScrimOpaqueChangedListener: Consumer<Boolean> /** @@ -224,8 +247,11 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, revealEffect.setRevealAmountOnScrim(value, this) updateScrimOpaque() - Trace.traceCounter(Trace.TRACE_TAG_APP, "light_reveal_amount", - (field * 100).toInt()) + Trace.traceCounter( + Trace.TRACE_TAG_APP, + "light_reveal_amount", + (field * 100).toInt() + ) invalidate() } } @@ -250,10 +276,10 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, /** * Alpha of the fill that can be used in the beginning of the animation to hide the content. - * Normally the gradient bounds are animated from small size so the content is not visible, - * but if the start gradient bounds allow to see some content this could be used to make the - * reveal smoother. It can help to add fade in effect in the beginning of the animation. - * The color of the fill is determined by [revealGradientEndColor]. + * Normally the gradient bounds are animated from small size so the content is not visible, but + * if the start gradient bounds allow to see some content this could be used to make the reveal + * smoother. It can help to add fade in effect in the beginning of the animation. The color of + * the fill is determined by [revealGradientEndColor]. * * 0 - no fill and content is visible, 1 - the content is covered with the start color */ @@ -281,9 +307,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } } - /** - * Is the scrim currently fully opaque - */ + /** Is the scrim currently fully opaque */ var isScrimOpaque = false private set(value) { if (field != value) { @@ -318,16 +342,22 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, * Paint used to draw a transparent-to-white radial gradient. This will be scaled and translated * via local matrix in [onDraw] so we never need to construct a new shader. */ - private val gradientPaint = Paint().apply { - shader = RadialGradient( - 0f, 0f, 1f, - intArrayOf(Color.TRANSPARENT, Color.WHITE), floatArrayOf(0f, 1f), - Shader.TileMode.CLAMP) - - // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same - // window, rather than outright replacing them. - xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER) - } + private val gradientPaint = + Paint().apply { + shader = + RadialGradient( + 0f, + 0f, + 1f, + intArrayOf(Color.TRANSPARENT, Color.WHITE), + floatArrayOf(0f, 1f), + Shader.TileMode.CLAMP + ) + + // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same + // window, rather than outright replacing them. + xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER) + } /** * Matrix applied to [gradientPaint]'s RadialGradient shader to move the gradient to @@ -347,8 +377,8 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, * simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and * [revealGradientHeight] for you. * - * This method does not call [invalidate] - you should do so once you're done changing - * properties. + * This method does not call [invalidate] + * - you should do so once you're done changing properties. */ fun setRevealGradientBounds(left: Float, top: Float, right: Float, bottom: Float) { revealGradientWidth = right - left @@ -359,8 +389,12 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } override fun onDraw(canvas: Canvas?) { - if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0 || - revealAmount == 0f) { + if ( + canvas == null || + revealGradientWidth <= 0 || + revealGradientHeight <= 0 || + revealAmount == 0f + ) { if (revealAmount < 1f) { canvas?.drawColor(revealGradientEndColor) } @@ -383,8 +417,10 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } private fun setPaintColorFilter() { - gradientPaint.colorFilter = PorterDuffColorFilter( - getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha), - PorterDuff.Mode.MULTIPLY) + gradientPaint.colorFilter = + PorterDuffColorFilter( + getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha), + PorterDuff.Mode.MULTIPLY + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java index 5cf1abc18274..035b90faf7da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java @@ -15,9 +15,7 @@ */ package com.android.systemui.statusbar.connectivity; -import static com.android.settingslib.mobile.MobileMappings.getDefaultIcons; -import static com.android.settingslib.mobile.MobileMappings.getIconKey; -import static com.android.settingslib.mobile.MobileMappings.mapIconSets; +import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID; import android.content.Context; import android.content.Intent; @@ -46,6 +44,7 @@ import com.android.settingslib.mobile.MobileStatusTracker.SubscriptionDefaults; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.SignalStrengthUtil; import com.android.systemui.R; +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy; import com.android.systemui.util.CarrierConfigTracker; import java.io.PrintWriter; @@ -63,6 +62,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile private final TelephonyManager mPhone; private final CarrierConfigTracker mCarrierConfigTracker; private final SubscriptionDefaults mDefaults; + private final MobileMappingsProxy mMobileMappingsProxy; private final String mNetworkNameDefault; private final String mNetworkNameSeparator; private final ContentObserver mObserver; @@ -121,6 +121,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile TelephonyManager phone, CallbackHandler callbackHandler, NetworkControllerImpl networkController, + MobileMappingsProxy mobileMappingsProxy, SubscriptionInfo info, SubscriptionDefaults defaults, Looper receiverLooper, @@ -135,13 +136,14 @@ public class MobileSignalController extends SignalController<MobileState, Mobile mPhone = phone; mDefaults = defaults; mSubscriptionInfo = info; + mMobileMappingsProxy = mobileMappingsProxy; mNetworkNameSeparator = getTextIfExists( R.string.status_bar_network_name_separator).toString(); mNetworkNameDefault = getTextIfExists( com.android.internal.R.string.lockscreen_carrier_default).toString(); - mNetworkToIconLookup = mapIconSets(mConfig); - mDefaultIcons = getDefaultIcons(mConfig); + mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig); + mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig); String networkName = info.getCarrierName() != null ? info.getCarrierName().toString() : mNetworkNameDefault; @@ -161,8 +163,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile void setConfiguration(Config config) { mConfig = config; updateInflateSignalStrength(); - mNetworkToIconLookup = mapIconSets(mConfig); - mDefaultIcons = getDefaultIcons(mConfig); + mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig); + mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig); updateTelephony(); } @@ -271,8 +273,9 @@ public class MobileSignalController extends SignalController<MobileState, Mobile dataContentDescription = mContext.getString(R.string.data_connection_no_internet); } - final QsInfo qsInfo = getQsInfo(contentDescription, icons.dataType); - final SbInfo sbInfo = getSbInfo(contentDescription, icons.dataType); + int iconId = mCurrentState.getNetworkTypeIcon(mContext); + final QsInfo qsInfo = getQsInfo(contentDescription, iconId); + final SbInfo sbInfo = getSbInfo(contentDescription, iconId); MobileDataIndicators mobileDataIndicators = new MobileDataIndicators( sbInfo.icon, @@ -373,6 +376,10 @@ public class MobileSignalController extends SignalController<MobileState, Mobile } else if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) { updateDataSim(); notifyListenersIfNecessary(); + } else if (action.equals(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED)) { + int carrierId = intent.getIntExtra( + TelephonyManager.EXTRA_CARRIER_ID, UNKNOWN_CARRIER_ID); + mCurrentState.setCarrierId(carrierId); } } @@ -477,7 +484,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile mCurrentState.level = getSignalLevel(mCurrentState.signalStrength); } - String iconKey = getIconKey(mCurrentState.telephonyDisplayInfo); + mCurrentState.setCarrierId(mPhone.getSimCarrierId()); + String iconKey = mMobileMappingsProxy.getIconKey(mCurrentState.telephonyDisplayInfo); if (mNetworkToIconLookup.get(iconKey) != null) { mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt index 793817948803..a323454a7ed8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt @@ -22,6 +22,7 @@ import android.telephony.TelephonyManager import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileStatusTracker import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.util.CarrierConfigTracker import javax.inject.Inject @@ -33,6 +34,7 @@ internal class MobileSignalControllerFactory @Inject constructor( val context: Context, val callbackHandler: CallbackHandler, val carrierConfigTracker: CarrierConfigTracker, + val mobileMappings: MobileMappingsProxy, ) { fun createMobileSignalController( config: MobileMappings.Config, @@ -56,6 +58,7 @@ internal class MobileSignalControllerFactory @Inject constructor( phone, callbackHandler, networkController, + mobileMappings, subscriptionInfo, subscriptionDefaults, receiverLooper, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt index f20d20631c95..1fb6a982fdf3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt @@ -16,10 +16,14 @@ package com.android.systemui.statusbar.connectivity +import android.annotation.DrawableRes +import android.content.Context import android.telephony.ServiceState import android.telephony.SignalStrength import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyManager +import com.android.internal.annotations.VisibleForTesting +import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.Utils import com.android.settingslib.mobile.MobileStatusTracker.MobileStatus import com.android.settingslib.mobile.TelephonyIcons @@ -41,7 +45,7 @@ internal class MobileState( @JvmField var roaming: Boolean = false, @JvmField var dataState: Int = TelephonyManager.DATA_DISCONNECTED, // Tracks the on/off state of the defaultDataSubscription - @JvmField var defaultDataOff: Boolean = false + @JvmField var defaultDataOff: Boolean = false, ) : ConnectivityState() { @JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN, @@ -49,6 +53,11 @@ internal class MobileState( @JvmField var serviceState: ServiceState? = null @JvmField var signalStrength: SignalStrength? = null + var carrierId = TelephonyManager.UNKNOWN_CARRIER_ID + + @VisibleForTesting + var networkTypeResIdCache: NetworkTypeResIdCache = NetworkTypeResIdCache() + /** @return true if this state is disabled or not default data */ val isDataDisabledOrNotDefault: Boolean get() = (iconGroup === TelephonyIcons.DATA_DISABLED || @@ -125,6 +134,21 @@ internal class MobileState( return serviceState != null && serviceState!!.roaming } + /** + * + * Load the (potentially customized) icon resource id for the current network type. Note that + * this operation caches the result. Note that reading the [MobileIconGroup.dataType] field + * directly will not yield correct results in cases where the carrierId has an associated + * override. This is the preferred method for getting the network type indicator. + * + * @return a drawable res id appropriate for the current (carrierId, networkType) pair + */ + @DrawableRes + fun getNetworkTypeIcon(context: Context): Int { + val icon = (iconGroup as MobileIconGroup) + return networkTypeResIdCache.get(icon, carrierId, context) + } + fun setFromMobileStatus(mobileStatus: MobileStatus) { activityIn = mobileStatus.activityIn activityOut = mobileStatus.activityOut @@ -140,6 +164,7 @@ internal class MobileState( super.toString(builder) builder.append(',') builder.append("dataSim=$dataSim,") + builder.append("carrierId=$carrierId") builder.append("networkName=$networkName,") builder.append("networkNameData=$networkNameData,") builder.append("dataConnected=$dataConnected,") @@ -157,6 +182,8 @@ internal class MobileState( builder.append("voiceServiceState=${getVoiceServiceState()},") builder.append("isInService=${isInService()},") + builder.append("networkTypeIconCache=$networkTypeResIdCache") + builder.append("serviceState=${serviceState?.minLog() ?: "(null)"},") builder.append("signalStrength=${signalStrength?.minLog() ?: "(null)"},") builder.append("displayInfo=$telephonyDisplayInfo") @@ -164,6 +191,7 @@ internal class MobileState( override fun tableColumns(): List<String> { val columns = listOf("dataSim", + "carrierId", "networkName", "networkNameData", "dataConnected", @@ -178,6 +206,7 @@ internal class MobileState( "showQuickSettingsRatIcon", "voiceServiceState", "isInService", + "networkTypeIconCache", "serviceState", "signalStrength", "displayInfo") @@ -187,6 +216,7 @@ internal class MobileState( override fun tableData(): List<String> { val columns = listOf(dataSim, + carrierId, networkName, networkNameData, dataConnected, @@ -201,6 +231,7 @@ internal class MobileState( showQuickSettingsRatIcon(), getVoiceServiceState(), isInService(), + networkTypeResIdCache, serviceState?.minLog() ?: "(null)", signalStrength?.minLog() ?: "(null)", telephonyDisplayInfo).map { it.toString() } @@ -217,6 +248,7 @@ internal class MobileState( if (networkName != other.networkName) return false if (networkNameData != other.networkNameData) return false + if (carrierId != other.carrierId) return false if (dataSim != other.dataSim) return false if (dataConnected != other.dataConnected) return false if (isEmergency != other.isEmergency) return false @@ -238,6 +270,7 @@ internal class MobileState( var result = super.hashCode() result = 31 * result + (networkName?.hashCode() ?: 0) result = 31 * result + (networkNameData?.hashCode() ?: 0) + result = 31 * result + (carrierId.hashCode()) result = 31 * result + dataSim.hashCode() result = 31 * result + dataConnected.hashCode() result = 31 * result + isEmergency.hashCode() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index 402217dac185..3cc53c16f28b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -22,6 +22,7 @@ import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -139,7 +140,7 @@ public class NetworkControllerImpl extends BroadcastReceiver private final MobileSignalControllerFactory mMobileFactory; private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener; - private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private int mActiveMobileDataSubscription = INVALID_SUBSCRIPTION_ID; // Subcontrollers. @VisibleForTesting @@ -503,6 +504,7 @@ public class NetworkControllerImpl extends BroadcastReceiver filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); + filter.addAction(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler); mListening = true; @@ -793,6 +795,20 @@ public class NetworkControllerImpl extends BroadcastReceiver mConfig = Config.readConfig(mContext); mReceiverHandler.post(this::handleConfigurationChanged); break; + + case TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED: { + // Notify the relevant MobileSignalController of the change + int subId = intent.getIntExtra( + TelephonyManager.EXTRA_SUBSCRIPTION_ID, + INVALID_SUBSCRIPTION_ID + ); + if (SubscriptionManager.isValidSubscriptionId(subId)) { + if (mMobileSignalControllers.indexOfKey(subId) >= 0) { + mMobileSignalControllers.get(subId).handleBroadcast(intent); + } + } + } + break; case Intent.ACTION_SIM_STATE_CHANGED: // Avoid rebroadcast because SysUI is direct boot aware. if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) { @@ -820,7 +836,7 @@ public class NetworkControllerImpl extends BroadcastReceiver break; default: int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); + INVALID_SUBSCRIPTION_ID); if (SubscriptionManager.isValidSubscriptionId(subId)) { if (mMobileSignalControllers.indexOfKey(subId) >= 0) { mMobileSignalControllers.get(subId).handleBroadcast(intent); @@ -1336,6 +1352,9 @@ public class NetworkControllerImpl extends BroadcastReceiver String slotString = args.getString("slot"); int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString); slot = MathUtils.constrain(slot, 0, 8); + String carrierIdString = args.getString("carrierid"); + int carrierId = TextUtils.isEmpty(carrierIdString) ? 0 + : Integer.parseInt(carrierIdString); // Ensure we have enough sim slots List<SubscriptionInfo> subs = new ArrayList<>(); while (mMobileSignalControllers.size() <= slot) { @@ -1347,6 +1366,9 @@ public class NetworkControllerImpl extends BroadcastReceiver } // Hack to index linearly for easy use. MobileSignalController controller = mMobileSignalControllers.valueAt(slot); + if (carrierId != 0) { + controller.getState().setCarrierId(carrierId); + } controller.getState().dataSim = datatype != null; controller.getState().isDefault = datatype != null; controller.getState().dataConnected = datatype != null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt new file mode 100644 index 000000000000..9be7ee99cf02 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt @@ -0,0 +1,78 @@ +/* + * 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.connectivity + +import android.annotation.DrawableRes +import android.content.Context +import com.android.settingslib.SignalIcon.MobileIconGroup +import com.android.settingslib.mobile.MobileIconCarrierIdOverrides +import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl + +/** + * Cache for network type resource IDs. + * + * The default framework behavior is to have a statically defined icon per network type. See + * [MobileIconGroup] for the standard mapping. + * + * For the case of carrierId-defined overrides, we want to check [MobileIconCarrierIdOverrides] for + * an existing icon override, and cache the result of the operation + */ +class NetworkTypeResIdCache( + private val overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() +) { + @DrawableRes private var cachedResId: Int = 0 + private var lastCarrierId: Int? = null + private var lastIconGroup: MobileIconGroup? = null + private var isOverridden: Boolean = false + + @DrawableRes + fun get(iconGroup: MobileIconGroup, carrierId: Int, context: Context): Int { + if (lastCarrierId != carrierId || lastIconGroup != iconGroup) { + lastCarrierId = carrierId + lastIconGroup = iconGroup + + val maybeOverride = calculateOverriddenIcon(iconGroup, carrierId, context) + if (maybeOverride > 0) { + cachedResId = maybeOverride + isOverridden = true + } else { + cachedResId = iconGroup.dataType + isOverridden = false + } + } + + return cachedResId + } + + override fun toString(): String { + return "networkTypeResIdCache={id=$cachedResId, isOverridden=$isOverridden}" + } + + @DrawableRes + private fun calculateOverriddenIcon( + iconGroup: MobileIconGroup, + carrierId: Int, + context: Context, + ): Int { + val name = iconGroup.name + if (!overrides.carrierIdEntryExists(carrierId)) { + return 0 + } + + return overrides.getOverrideFor(carrierId, name, context.resources) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt index a02dd3490341..42b874fd7156 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt @@ -37,6 +37,13 @@ import javax.inject.Inject * own [Configuration] and track resources based on the full set of available mcc-mnc combinations. * * (for future reference: b/240555502 is the initiating bug for this) + * + * NOTE: MCC/MNC qualifiers are not sufficient to fully describe a network type icon qualified by + * network type + carrier ID. This class exists to keep the legacy behavior of using the MCC/MNC + * resource qualifiers working, but if a carrier-specific icon is requested, then the override + * provided by [MobileIconCarrierIdOverrides] will take precedence. + * + * TODO(b/258503704): consider removing this class in favor of the `carrierId` overrides */ @SysUISingleton class MobileContextProvider diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 3021414847c0..b93e1500e570 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -135,6 +135,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private static final String TAG = "ExpandableNotifRow"; private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG_ONMEASURE = + Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); private static final int DEFAULT_DIVIDER_ALPHA = 0x29; private static final int COLORED_DIVIDER_ALPHA = 0x7B; private static final int MENU_VIEW_INDEX = 0; @@ -1724,6 +1726,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure")); + if (DEBUG_ONMEASURE) { + Log.d(TAG, "onMeasure(" + + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", " + + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")"); + } super.onMeasure(widthMeasureSpec, heightMeasureSpec); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 645a02dbda14..d43ca823089f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -25,6 +25,7 @@ import android.graphics.Canvas; import android.graphics.Path; import android.graphics.Path.Direction; import android.graphics.drawable.ColorDrawable; +import android.os.Trace; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.Log; @@ -219,6 +220,7 @@ public class NotificationChildrenContainer extends ViewGroup @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Trace.beginSection("NotificationChildrenContainer#onMeasure"); int heightMode = MeasureSpec.getMode(heightMeasureSpec); boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; @@ -267,6 +269,7 @@ public class NotificationChildrenContainer extends ViewGroup } setMeasuredDimension(width, height); + Trace.endSection(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 2c3330e12229..41dbf1d6dc60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.stack; +import static android.os.Trace.TRACE_TAG_ALWAYS; + import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; @@ -44,6 +46,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; import android.os.Bundle; +import android.os.Trace; import android.provider.Settings; import android.util.AttributeSet; import android.util.IndentingPrintWriter; @@ -1074,6 +1077,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Trace.beginSection("NotificationStackScrollLayout#onMeasure"); + if (SPEW) { + Log.d(TAG, "onMeasure(" + + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", " + + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")"); + } super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); @@ -1090,6 +1099,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable for (int i = 0; i < size; i++) { measureChild(getChildAt(i), childWidthSpec, childHeightSpec); } + Trace.endSection(); + } + + @Override + public void requestLayout() { + Trace.instant(TRACE_TAG_ALWAYS, "NotificationStackScrollLayout#requestLayout"); + super.requestLayout(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 1e95dad6b115..9e5a66f1e306 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -55,6 +55,7 @@ import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.qs.QSPanelController; +import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.CommandQueue; @@ -71,6 +72,8 @@ import java.util.Optional; import javax.inject.Inject; +import dagger.Lazy; + /** */ @CentralSurfacesComponent.CentralSurfacesScope public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks { @@ -99,6 +102,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba private final boolean mVibrateOnOpening; private final VibrationEffect mCameraLaunchGestureVibrationEffect; private final SystemBarAttributesListener mSystemBarAttributesListener; + private final Lazy<CameraLauncher> mCameraLauncherLazy; private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); @@ -128,8 +132,8 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba Optional<Vibrator> vibratorOptional, DisableFlagsLogger disableFlagsLogger, @DisplayId int displayId, - SystemBarAttributesListener systemBarAttributesListener) { - + SystemBarAttributesListener systemBarAttributesListener, + Lazy<CameraLauncher> cameraLauncherLazy) { mCentralSurfaces = centralSurfaces; mContext = context; mShadeController = shadeController; @@ -152,6 +156,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mVibratorOptional = vibratorOptional; mDisableFlagsLogger = disableFlagsLogger; mDisplayId = displayId; + mCameraLauncherLazy = cameraLauncherLazy; mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation); mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect( @@ -346,7 +351,8 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mCentralSurfaces.setLaunchCameraOnFinishedGoingToSleep(true); return; } - if (!mNotificationPanelViewController.canCameraGestureBeLaunched()) { + if (!mCameraLauncherLazy.get().canCameraGestureBeLaunched( + mNotificationPanelViewController.getBarState())) { if (CentralSurfaces.DEBUG_CAMERA_LIFT) { Slog.d(CentralSurfaces.TAG, "Can't launch camera right now"); } @@ -383,7 +389,8 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.reset(true /* hide */); } - mNotificationPanelViewController.launchCamera(source); + mCameraLauncherLazy.get().launchCamera(source, + mNotificationPanelViewController.isFullyCollapsed()); mCentralSurfaces.updateScrimController(); } else { // We need to defer the camera launch until the screen comes on, since otherwise diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 37027ea13321..4e0d7ac1512a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -177,6 +177,7 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.ripple.RippleShader.RippleShape; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; @@ -485,6 +486,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final PluginManager mPluginManager; private final ShadeController mShadeController; private final InitController mInitController; + private final Lazy<CameraLauncher> mCameraLauncherLazy; private final PluginDependencyProvider mPluginDependencyProvider; private final KeyguardDismissUtil mKeyguardDismissUtil; @@ -617,6 +619,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private Runnable mLaunchTransitionEndRunnable; private Runnable mLaunchTransitionCancelRunnable; + private boolean mLaunchingAffordance; private boolean mLaunchCameraWhenFinishedWaking; private boolean mLaunchCameraOnFinishedGoingToSleep; private boolean mLaunchEmergencyActionWhenFinishedWaking; @@ -761,7 +764,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { InteractionJankMonitor jankMonitor, DeviceStateManager deviceStateManager, WiredChargingRippleController wiredChargingRippleController, - IDreamManager dreamManager) { + IDreamManager dreamManager, + Lazy<CameraLauncher> cameraLauncherLazy) { mContext = context; mNotificationsController = notificationsController; mFragmentService = fragmentService; @@ -838,6 +842,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mMessageRouter = messageRouter; mWallpaperManager = wallpaperManager; mJankMonitor = jankMonitor; + mCameraLauncherLazy = cameraLauncherLazy; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -2969,7 +2974,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private void onLaunchTransitionFadingEnded() { mNotificationPanelViewController.resetAlpha(); - mNotificationPanelViewController.onAffordanceLaunchEnded(); + mCameraLauncherLazy.get().setLaunchingAffordance(false); releaseGestureWakeLock(); runLaunchTransitionEndRunnable(); mKeyguardStateController.setLaunchTransitionFadingAway(false); @@ -3039,7 +3044,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private void onLaunchTransitionTimeout() { Log.w(TAG, "Launch transition: Timeout!"); - mNotificationPanelViewController.onAffordanceLaunchEnded(); + mCameraLauncherLazy.get().setLaunchingAffordance(false); releaseGestureWakeLock(); mNotificationPanelViewController.resetViews(false /* animate */); } @@ -3092,7 +3097,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); releaseGestureWakeLock(); - mNotificationPanelViewController.onAffordanceLaunchEnded(); + mCameraLauncherLazy.get().setLaunchingAffordance(false); mNotificationPanelViewController.resetAlpha(); mNotificationPanelViewController.resetTranslation(); mNotificationPanelViewController.resetViewGroupFade(); @@ -3250,7 +3255,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void endAffordanceLaunch() { releaseGestureWakeLock(); - mNotificationPanelViewController.onAffordanceLaunchEnded(); + mCameraLauncherLazy.get().setLaunchingAffordance(false); } /** @@ -3522,7 +3527,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() { @Override public void onFinishedGoingToSleep() { - mNotificationPanelViewController.onAffordanceLaunchEnded(); + mCameraLauncherLazy.get().setLaunchingAffordance(false); releaseGestureWakeLock(); mLaunchCameraWhenFinishedWaking = false; mDeviceInteractive = false; @@ -3623,7 +3628,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { .updateSensitivenessForOccludedWakeup(); } if (mLaunchCameraWhenFinishedWaking) { - mNotificationPanelViewController.launchCamera(mLastCameraLaunchSource); + mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource, + mNotificationPanelViewController.isFullyCollapsed()); mLaunchCameraWhenFinishedWaking = false; } if (mLaunchEmergencyActionWhenFinishedWaking) { @@ -3814,8 +3820,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScrimController.setExpansionAffectsAlpha(!unlocking); - boolean launchingAffordanceWithPreview = - mNotificationPanelViewController.isLaunchingAffordanceWithPreview(); + boolean launchingAffordanceWithPreview = mLaunchingAffordance; mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview); if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 532b8b839fdc..5f8da4161092 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -279,10 +279,7 @@ public class KeyguardBouncer { * @see #onFullyShown() */ private void onFullyHidden() { - cancelShowRunnable(); - setVisibility(View.INVISIBLE); - mFalsingCollector.onBouncerHidden(); - DejankUtils.postAfterTraversal(mResetRunnable); + } private void setVisibility(@View.Visibility int visibility) { @@ -459,7 +456,13 @@ public class KeyguardBouncer { onFullyShown(); dispatchFullyShown(); } else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) { - onFullyHidden(); + DejankUtils.postAfterTraversal(mResetRunnable); + /* + * There are cases where #hide() was not invoked, such as when + * NotificationPanelViewController controls the hide animation. Make sure the state gets + * updated by calling #hide() directly. + */ + hide(false /* destroyView */); dispatchFullyHidden(); } else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) { dispatchStartingToHide(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 18877f9fb437..7a49a495155b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Trace; import android.util.AttributeSet; import android.util.Pair; import android.util.TypedValue; @@ -527,4 +528,11 @@ public class KeyguardStatusBarView extends RelativeLayout { mClipRect.set(0, mTopClipping, getWidth(), getHeight()); setClipBounds(mClipRect); } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Trace.beginSection("KeyguardStatusBarView#onMeasure"); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + Trace.endSection(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 86e27aba65f0..d54a8638f2e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -895,7 +895,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha(); float behindAlpha; - int behindTint; + int behindTint = state.getBehindTint(); if (mDarkenWhileDragging) { behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind, interpolatedFract); @@ -903,12 +903,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump behindAlpha = MathUtils.lerp(0 /* start */, stateBehind, interpolatedFract); } - if (mClipsQsScrim) { - behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(), + if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) { + if (mClipsQsScrim) { + behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(), state.getNotifTint(), interpolatedFract); - } else { - behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), + } else { + behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), state.getBehindTint(), interpolatedFract); + } } if (mQsExpansion > 0) { behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 56fb337e5b4f..01a1ebe7fd68 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -165,6 +165,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void onFullyHidden() { mPrimaryBouncerAnimating = false; + updateStates(); } @Override @@ -468,7 +469,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // Don't expand to the bouncer. Instead transition back to the lock screen (see // CentralSurfaces#showBouncerOrLockScreenIfKeyguard) return; - } else if (primaryBouncerNeedsScrimming()) { + } else if (needsFullscreenBouncer()) { if (mPrimaryBouncer != null) { mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); } else { @@ -1184,12 +1185,16 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb updateNavigationBarVisibility(navBarVisible); } - if (primaryBouncerShowing != mLastPrimaryBouncerShowing || mFirstUpdate) { + boolean isPrimaryBouncerShowingChanged = + primaryBouncerShowing != mLastPrimaryBouncerShowing; + mLastPrimaryBouncerShowing = primaryBouncerShowing; + + if (isPrimaryBouncerShowingChanged || mFirstUpdate) { mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing); mCentralSurfaces.setBouncerShowing(primaryBouncerShowing); } if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate - || primaryBouncerShowing != mLastPrimaryBouncerShowing) { + || isPrimaryBouncerShowingChanged) { mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, primaryBouncerShowing); } @@ -1198,7 +1203,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLastShowing = showing; mLastGlobalActionsVisible = mGlobalActionsVisible; mLastOccluded = occluded; - mLastPrimaryBouncerShowing = primaryBouncerShowing; mLastPrimaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing; mLastBouncerDismissible = primaryBouncerDismissible; mLastRemoteInputActive = remoteInputActive; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index dc902666874d..615f2304ecf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -226,6 +226,7 @@ public abstract class StatusBarViewModule { BroadcastDispatcher broadcastDispatcher, @Main Handler mainHandler, ContentResolver contentResolver, + FeatureFlags featureFlags, BatteryController batteryController ) { return new BatteryMeterViewController( @@ -235,6 +236,7 @@ public abstract class StatusBarViewModule { broadcastDispatcher, mainHandler, contentResolver, + featureFlags, batteryController); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt index 60bd0383f8c7..501467f13007 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt @@ -32,6 +32,7 @@ import javax.inject.Inject interface MobileMappingsProxy { fun mapIconSets(config: Config): Map<String, MobileIconGroup> fun getDefaultIcons(config: Config): MobileIconGroup + fun getIconKey(displayInfo: TelephonyDisplayInfo): String fun toIconKey(@NetworkType networkType: Int): String fun toIconKeyOverride(@NetworkType networkType: Int): String } @@ -44,6 +45,9 @@ class MobileMappingsProxyImpl @Inject constructor() : MobileMappingsProxy { override fun getDefaultIcons(config: Config): MobileIconGroup = MobileMappings.getDefaultIcons(config) + override fun getIconKey(displayInfo: TelephonyDisplayInfo): String = + MobileMappings.getIconKey(displayInfo) + override fun toIconKey(@NetworkType networkType: Int): String = MobileMappings.toIconKey(networkType) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java index 149ed0a71b91..d10d7cf8460b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java @@ -155,6 +155,9 @@ public interface BatteryController extends DemoMode, default void onWirelessChargingChanged(boolean isWirlessCharging) { } + + default void onIsOverheatedChanged(boolean isOverheated) { + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index c7ad76722929..3c2ac7b7a124 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.policy; +import static android.os.BatteryManager.BATTERY_HEALTH_OVERHEAT; +import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; +import static android.os.BatteryManager.EXTRA_HEALTH; import static android.os.BatteryManager.EXTRA_PRESENT; import android.annotation.WorkerThread; @@ -87,6 +90,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC protected boolean mPowerSave; private boolean mAodPowerSave; private boolean mWirelessCharging; + private boolean mIsOverheated = false; private boolean mTestMode = false; @VisibleForTesting boolean mHasReceivedBattery = false; @@ -184,6 +188,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC cb.onPowerSaveChanged(mPowerSave); cb.onBatteryUnknownStateChanged(mStateUnknown); cb.onWirelessChargingChanged(mWirelessCharging); + cb.onIsOverheatedChanged(mIsOverheated); } @Override @@ -222,6 +227,13 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC fireBatteryUnknownStateChanged(); } + int batteryHealth = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN); + boolean isOverheated = batteryHealth == BATTERY_HEALTH_OVERHEAT; + if (isOverheated != mIsOverheated) { + mIsOverheated = isOverheated; + fireIsOverheatedChanged(); + } + fireBatteryLevelChanged(); } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) { updatePowerSave(); @@ -292,6 +304,10 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS; } + public boolean isOverheated() { + return mIsOverheated; + } + @Override public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) { // Need to fetch or refresh the estimate, but it may involve binder calls so offload the @@ -402,6 +418,15 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } } + private void fireIsOverheatedChanged() { + synchronized (mChangeCallbacks) { + final int n = mChangeCallbacks.size(); + for (int i = 0; i < n; i++) { + mChangeCallbacks.get(i).onIsOverheatedChanged(mIsOverheated); + } + } + } + @Override public void dispatchDemoCommand(String command, Bundle args) { if (!mDemoModeController.isInDemoMode()) { @@ -412,6 +437,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC String plugged = args.getString("plugged"); String powerSave = args.getString("powersave"); String present = args.getString("present"); + String overheated = args.getString("overheated"); if (level != null) { mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100); } @@ -426,6 +452,10 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC mStateUnknown = !present.equals("true"); fireBatteryUnknownStateChanged(); } + if (overheated != null) { + mIsOverheated = overheated.equals("true"); + fireIsOverheatedChanged(); + } fireBatteryLevelChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index 82703364a1d5..a9d05d11dc00 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -94,6 +94,13 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora private var wakeReasonAcquired: String? = null /** + * A stack of pairs of device id and temporary view info. This is used when there may be + * multiple devices in range, and we want to always display the chip for the most recently + * active device. + */ + internal val activeViews: ArrayDeque<Pair<String, T>> = ArrayDeque() + + /** * Displays the view with the provided [newInfo]. * * This method handles inflating and attaching the view, then delegates to [updateView] to @@ -102,6 +109,12 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora fun displayView(newInfo: T) { val currentDisplayInfo = displayInfo + // Update our list of active devices by removing it if necessary, then adding back at the + // front of the list + val id = newInfo.id + val position = findAndRemoveFromActiveViewsList(id) + activeViews.addFirst(Pair(id, newInfo)) + if (currentDisplayInfo != null && currentDisplayInfo.info.windowTitle == newInfo.windowTitle) { // We're already displaying information in the correctly-titled window, so we just need @@ -113,7 +126,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora // We're already displaying information but that information is under a different // window title. So, we need to remove the old window with the old title and add a // new window with the new title. - removeView(removalReason = "New info has new window title: ${newInfo.windowTitle}") + removeView( + id, + removalReason = "New info has new window title: ${newInfo.windowTitle}" + ) } // At this point, we're guaranteed to no longer be displaying a view. @@ -140,7 +156,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } wakeLock?.acquire(newInfo.wakeReason) wakeReasonAcquired = newInfo.wakeReason - logger.logViewAddition(newInfo.windowTitle) + logger.logViewAddition(id, newInfo.windowTitle) inflateAndUpdateView(newInfo) } @@ -151,9 +167,13 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora // include it just to be safe. FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS ) - cancelViewTimeout?.run() + + // Only cancel timeout of the most recent view displayed, as it will be reset. + if (position == 0) { + cancelViewTimeout?.run() + } cancelViewTimeout = mainExecutor.executeDelayed( - { removeView(REMOVAL_REASON_TIMEOUT) }, + { removeView(id, REMOVAL_REASON_TIMEOUT) }, timeout.toLong() ) } @@ -196,28 +216,67 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } /** - * Hides the view. + * Hides the view given its [id]. * + * @param id the id of the device responsible of displaying the temp view. * @param removalReason a short string describing why the view was removed (timeout, state * change, etc.) */ - fun removeView(removalReason: String) { + fun removeView(id: String, removalReason: String) { val currentDisplayInfo = displayInfo ?: return + val removalPosition = findAndRemoveFromActiveViewsList(id) + if (removalPosition == null) { + logger.logViewRemovalIgnored(id, "view not found in the list") + return + } + if (removalPosition != 0) { + logger.logViewRemovalIgnored(id, "most recent view is being displayed.") + return + } + logger.logViewRemoval(id, removalReason) + + val newViewToDisplay = if (activeViews.isEmpty()) { + null + } else { + activeViews[0].second + } + val currentView = currentDisplayInfo.view animateViewOut(currentView) { windowManager.removeView(currentView) wakeLock?.release(wakeReasonAcquired) } - logger.logViewRemoval(removalReason) configurationController.removeCallback(displayScaleListener) // Re-set to null immediately (instead as part of the animation end runnable) so - // that if a new view event comes in while this view is animating out, we still display the - // new view appropriately. + // that if a new view event comes in while this view is animating out, we still display + // the new view appropriately. displayInfo = null // No need to time the view out since it's already gone cancelViewTimeout?.run() + + if (newViewToDisplay != null) { + mainExecutor.executeDelayed({ displayView(newViewToDisplay)}, DISPLAY_VIEW_DELAY) + } + } + + /** + * Finds and removes the active view with the given [id] from the stack, or null if there is no + * active view with that ID + * + * @param id that temporary view belonged to. + * + * @return index of the view in the stack , otherwise null. + */ + private fun findAndRemoveFromActiveViewsList(id: String): Int? { + for (i in 0 until activeViews.size) { + if (activeViews[i].first == id) { + activeViews.removeAt(i) + return i + } + } + return null } /** @@ -258,6 +317,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT" +const val DISPLAY_VIEW_DELAY = 50L private data class IconInfo( val iconName: String, diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt index cbb500296888..df8396051dda 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt @@ -37,6 +37,11 @@ abstract class TemporaryViewInfo { * disappears. */ open val timeoutMs: Int = DEFAULT_TIMEOUT_MILLIS + + /** + * The id of the temporary view. + */ + abstract val id: String } const val DEFAULT_TIMEOUT_MILLIS = 10000 diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt index 428a104484a7..133a384e7e17 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt @@ -24,13 +24,42 @@ open class TemporaryViewLogger( internal val buffer: LogBuffer, internal val tag: String, ) { - /** Logs that we added the view in a window titled [windowTitle]. */ - fun logViewAddition(windowTitle: String) { - buffer.log(tag, LogLevel.DEBUG, { str1 = windowTitle }, { "View added. window=$str1" }) + /** Logs that we added the view with the given [id] in a window titled [windowTitle]. */ + fun logViewAddition(id: String, windowTitle: String) { + buffer.log( + tag, + LogLevel.DEBUG, + { + str1 = windowTitle + str2 = id + }, + { "View added. window=$str1 id=$str2" } + ) } - /** Logs that we removed the chip for the given [reason]. */ - fun logViewRemoval(reason: String) { - buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "View removed due to: $str1" }) + /** Logs that we removed the view with the given [id] for the given [reason]. */ + fun logViewRemoval(id: String, reason: String) { + buffer.log( + tag, + LogLevel.DEBUG, + { + str1 = reason + str2 = id + }, + { "View with id=$str2 is removed due to: $str1" } + ) + } + + /** Logs that we ignored removal of the view with the given [id]. */ + fun logViewRemovalIgnored(id: String, reason: String) { + buffer.log( + tag, + LogLevel.DEBUG, + { + str1 = reason + str2 = id + }, + { "Removal of view with id=$str2 is ignored because $str1" } + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt index 6237365d0cee..b92e0ec0428f 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt @@ -40,6 +40,7 @@ data class ChipbarInfo( override val windowTitle: String, override val wakeReason: String, override val timeoutMs: Int, + override val id: String, ) : TemporaryViewInfo() /** The possible items to display at the end of the chipbar. */ diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java index 5d2b0ca4e7ea..829008403e02 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java @@ -16,8 +16,11 @@ package com.android.keyguard; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -90,4 +93,11 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase { mMessageAreaController.setIsVisible(true); verify(mKeyguardMessageArea).setIsVisible(true); } + + @Test + public void testGetMessage() { + String msg = "abc"; + when(mKeyguardMessageArea.getText()).thenReturn(msg); + assertThat(mMessageAreaController.getMessage()).isEqualTo(msg); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index b369098cafc0..ffd95f4041f9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -31,6 +31,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -118,4 +119,14 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { keyguardPasswordViewController.startAppearAnimation() verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password) } + + @Test + fun startAppearAnimation_withExistingMessage() { + `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.") + keyguardPasswordViewController.startAppearAnimation() + verify( + mKeyguardMessageAreaController, + never() + ).setMessage(R.string.keyguard_enter_your_password) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index 9eff70487c74..b3d1c8f909d8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -33,6 +33,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` +import org.mockito.Mockito.never import org.mockito.MockitoAnnotations @SmallTest @@ -112,4 +113,14 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { mKeyguardPatternViewController.startAppearAnimation() verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern) } + + @Test + fun startAppearAnimation_withExistingMessage() { + `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.") + mKeyguardPatternViewController.startAppearAnimation() + verify( + mKeyguardMessageAreaController, + never() + ).setMessage(R.string.keyguard_enter_your_password) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index d9efdeaea04c..8bcfe6f2b6f5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -100,4 +100,12 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { pinViewController.startAppearAnimation() verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin) } + + @Test + fun startAppearAnimation_withExistingMessage() { + Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.") + pinViewController.startAppearAnimation() + verify(keyguardMessageAreaController, Mockito.never()) + .setMessage(R.string.keyguard_enter_your_password) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index aa4469f12161..4d58b09f1076 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -548,6 +548,22 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { verify(mKeyguardPasswordViewControllerMock, never()).showMessage(null, null); } + @Test + public void onDensityorFontScaleChanged() { + ArgumentCaptor<ConfigurationController.ConfigurationListener> + configurationListenerArgumentCaptor = ArgumentCaptor.forClass( + ConfigurationController.ConfigurationListener.class); + mKeyguardSecurityContainerController.onViewAttached(); + verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture()); + configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged(); + + verify(mView).onDensityOrFontScaleChanged(); + verify(mKeyguardSecurityViewFlipperController).onDensityOrFontScaleChanged(); + verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class), + any(KeyguardSecurityCallback.class)); + } + + private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() { mKeyguardSecurityContainerController.onViewAttached(); verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 1bd14e558fa0..36ed669e299c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -262,9 +262,12 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { ConstraintSet.Constraint userSwitcherConstraint = getViewConstraint(R.id.keyguard_bouncer_user_switcher); - assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.topToBottom).isEqualTo( + R.id.keyguard_bouncer_user_switcher); assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID); assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID); + assertThat(userSwitcherConstraint.layout.bottomToTop).isEqualTo( + mSecurityViewFlipper.getId()); assertThat(userSwitcherConstraint.layout.topMargin).isEqualTo( getContext().getResources().getDimensionPixelSize( R.dimen.bouncer_user_switcher_y_trans)); @@ -308,6 +311,17 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { } @Test + public void testOnDensityOrFontScaleChanged() { + setupUserSwitcher(); + View oldUserSwitcher = mKeyguardSecurityContainer.findViewById( + R.id.keyguard_bouncer_user_switcher); + mKeyguardSecurityContainer.onDensityOrFontScaleChanged(); + View newUserSwitcher = mKeyguardSecurityContainer.findViewById( + R.id.keyguard_bouncer_user_switcher); + assertThat(oldUserSwitcher).isNotEqualTo(newUserSwitcher); + } + + @Test public void testTouchesAreRecognizedAsBeingOnTheOtherSideOfSecurity() { setupUserSwitcher(); setViewWidth(VIEW_WIDTH); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java index 9296d3d5ec82..fd02ac97cec2 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java @@ -106,4 +106,10 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { } } } + + @Test + public void onDensityOrFontScaleChanged() { + mKeyguardSecurityViewFlipperController.onDensityOrFontScaleChanged(); + verify(mView).removeAllViews(); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index c6200daca6f6..a3a089f4647f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -1314,7 +1314,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { Arrays.asList("Unlocked by wearable")); // THEN the showTrustGrantedMessage should be called with the first message - verify(mTestCallback).showTrustGrantedMessage("Unlocked by wearable"); + verify(mTestCallback).onTrustGrantedWithFlags( + eq(0), + eq(KeyguardUpdateMonitor.getCurrentUser()), + eq("Unlocked by wearable")); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt index a4e0825360df..588620646b73 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt @@ -16,6 +16,7 @@ package com.android.systemui +import android.content.Context import android.graphics.Canvas import android.graphics.Insets import android.graphics.Path @@ -48,6 +49,7 @@ class DisplayCutoutBaseViewTest : SysuiTestCase() { @Mock private lateinit var mockCanvas: Canvas @Mock private lateinit var mockRootView: View @Mock private lateinit var mockDisplay: Display + @Mock private lateinit var mockContext: Context private lateinit var cutoutBaseView: DisplayCutoutBaseView private val cutout: DisplayCutout = DisplayCutout.Builder() @@ -168,7 +170,9 @@ class DisplayCutoutBaseViewTest : SysuiTestCase() { R.bool.config_fillMainBuiltInDisplayCutout, fillCutout) cutoutBaseView = spy(DisplayCutoutBaseView(mContext)) - whenever(cutoutBaseView.display).thenReturn(mockDisplay) + + whenever(cutoutBaseView.context).thenReturn(mockContext) + whenever(mockContext.display).thenReturn(mockDisplay) whenever(mockDisplay.uniqueId).thenReturn("mockDisplayUniqueId") whenever(cutoutBaseView.rootView).thenReturn(mockRootView) whenever(cutoutBaseView.getPhysicalPixelDisplaySizeRatio()).thenReturn(1f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt index 054650bb8a75..8207fa6958f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui +import android.content.Context import android.graphics.Insets import android.graphics.PixelFormat import android.graphics.Rect @@ -44,6 +45,7 @@ class ScreenDecorHwcLayerTest : SysuiTestCase() { @Mock private lateinit var mockDisplay: Display @Mock private lateinit var mockRootView: View + @Mock private lateinit var mockContext: Context private val displayWidth = 100 private val displayHeight = 200 @@ -75,7 +77,8 @@ class ScreenDecorHwcLayerTest : SysuiTestCase() { decorHwcLayer = Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport)) whenever(decorHwcLayer.width).thenReturn(displayWidth) whenever(decorHwcLayer.height).thenReturn(displayHeight) - whenever(decorHwcLayer.display).thenReturn(mockDisplay) + whenever(decorHwcLayer.context).thenReturn(mockContext) + whenever(mockContext.display).thenReturn(mockDisplay) whenever(decorHwcLayer.rootView).thenReturn(mockRootView) whenever(mockRootView.left).thenReturn(0) whenever(mockRootView.top).thenReturn(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt new file mode 100644 index 000000000000..982f033d05e5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt @@ -0,0 +1,52 @@ +/* + * 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.battery + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT +import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD +import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH +import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +@SmallTest +class AccessorizedBatteryDrawableTest : SysuiTestCase() { + @Test + fun intrinsicSize_shieldFalse_isBatterySize() { + val drawable = AccessorizedBatteryDrawable(context, frameColor = 0) + drawable.displayShield = false + + val density = context.resources.displayMetrics.density + assertThat(drawable.intrinsicHeight).isEqualTo((BATTERY_HEIGHT * density).toInt()) + assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH * density).toInt()) + } + + @Test + fun intrinsicSize_shieldTrue_isBatteryPlusShieldSize() { + val drawable = AccessorizedBatteryDrawable(context, frameColor = 0) + drawable.displayShield = true + + val density = context.resources.displayMetrics.density + assertThat(drawable.intrinsicHeight) + .isEqualTo((BATTERY_HEIGHT_WITH_SHIELD * density).toInt()) + assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH_WITH_SHIELD * density).toInt()) + } + + // TODO(b/255625888): Screenshot tests for this drawable would be amazing! +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java index 1d038a43ec8e..bc8f96198b27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java @@ -35,6 +35,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; @@ -59,6 +61,7 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase { private Handler mHandler; @Mock private ContentResolver mContentResolver; + private FakeFeatureFlags mFeatureFlags; @Mock private BatteryController mBatteryController; @@ -71,19 +74,13 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase { when(mBatteryMeterView.getContext()).thenReturn(mContext); when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources()); - mController = new BatteryMeterViewController( - mBatteryMeterView, - mConfigurationController, - mTunerService, - mBroadcastDispatcher, - mHandler, - mContentResolver, - mBatteryController - ); + mFeatureFlags = new FakeFeatureFlags(); + mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false); } @Test public void onViewAttached_callbacksRegistered() { + initController(); mController.onViewAttached(); verify(mConfigurationController).addCallback(any()); @@ -101,6 +98,7 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase { @Test public void onViewDetached_callbacksUnregistered() { + initController(); // Set everything up first. mController.onViewAttached(); @@ -114,6 +112,7 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase { @Test public void ignoreTunerUpdates_afterOnViewAttached_callbackUnregistered() { + initController(); // Start out receiving tuner updates mController.onViewAttached(); @@ -124,10 +123,43 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase { @Test public void ignoreTunerUpdates_beforeOnViewAttached_callbackNeverRegistered() { + initController(); + mController.ignoreTunerUpdates(); mController.onViewAttached(); verify(mTunerService, never()).addTunable(any(), any()); } + + @Test + public void shieldFlagDisabled_viewNotified() { + mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false); + + initController(); + + verify(mBatteryMeterView).setDisplayShieldEnabled(false); + } + + @Test + public void shieldFlagEnabled_viewNotified() { + mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, true); + + initController(); + + verify(mBatteryMeterView).setDisplayShieldEnabled(true); + } + + private void initController() { + mController = new BatteryMeterViewController( + mBatteryMeterView, + mConfigurationController, + mTunerService, + mBroadcastDispatcher, + mHandler, + mContentResolver, + mFeatureFlags, + mBatteryController + ); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt index b4ff2a5e1fbf..eb7d9c3900f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt @@ -17,7 +17,9 @@ package com.android.systemui.battery import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import android.widget.ImageView import androidx.test.filters.SmallTest +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion @@ -58,6 +60,182 @@ class BatteryMeterViewTest : SysuiTestCase() { // No assert needed } + @Test + fun contentDescription_unknown() { + mBatteryMeterView.onBatteryUnknownStateChanged(true) + + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString(R.string.accessibility_battery_unknown) + ) + } + + @Test + fun contentDescription_estimate() { + mBatteryMeterView.onBatteryLevelChanged(15, false) + mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) + mBatteryMeterView.setBatteryEstimateFetcher(Fetcher()) + + mBatteryMeterView.updatePercentText() + + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString( + R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE + ) + ) + } + + @Test + fun contentDescription_estimateAndOverheated() { + mBatteryMeterView.onBatteryLevelChanged(17, false) + mBatteryMeterView.onIsOverheatedChanged(true) + mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) + mBatteryMeterView.setBatteryEstimateFetcher(Fetcher()) + + mBatteryMeterView.updatePercentText() + + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString( + R.string.accessibility_battery_level_charging_paused_with_estimate, + 17, + ESTIMATE, + ) + ) + } + + @Test + fun contentDescription_overheated() { + mBatteryMeterView.onBatteryLevelChanged(90, false) + mBatteryMeterView.onIsOverheatedChanged(true) + + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString(R.string.accessibility_battery_level_charging_paused, 90) + ) + } + + @Test + fun contentDescription_charging() { + mBatteryMeterView.onBatteryLevelChanged(45, true) + + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString(R.string.accessibility_battery_level_charging, 45) + ) + } + + @Test + fun contentDescription_notCharging() { + mBatteryMeterView.onBatteryLevelChanged(45, false) + + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString(R.string.accessibility_battery_level, 45) + ) + } + + @Test + fun changesFromEstimateToPercent_textAndContentDescriptionChanges() { + mBatteryMeterView.onBatteryLevelChanged(15, false) + mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) + mBatteryMeterView.setBatteryEstimateFetcher(Fetcher()) + + mBatteryMeterView.updatePercentText() + + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString( + R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE + ) + ) + + // Update the show mode from estimate to percent + mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON) + + assertThat(mBatteryMeterView.batteryPercentViewText).isEqualTo("15%") + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString(R.string.accessibility_battery_level, 15) + ) + } + + @Test + fun contentDescription_manyUpdates_alwaysUpdated() { + // Overheated + mBatteryMeterView.onBatteryLevelChanged(90, false) + mBatteryMeterView.onIsOverheatedChanged(true) + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString(R.string.accessibility_battery_level_charging_paused, 90) + ) + + // Overheated & estimate + mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) + mBatteryMeterView.setBatteryEstimateFetcher(Fetcher()) + mBatteryMeterView.updatePercentText() + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString( + R.string.accessibility_battery_level_charging_paused_with_estimate, + 90, + ESTIMATE, + ) + ) + + // Just estimate + mBatteryMeterView.onIsOverheatedChanged(false) + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString( + R.string.accessibility_battery_level_with_estimate, + 90, + ESTIMATE, + ) + ) + + // Just percent + mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON) + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString(R.string.accessibility_battery_level, 90) + ) + + // Charging + mBatteryMeterView.onBatteryLevelChanged(90, true) + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString(R.string.accessibility_battery_level_charging, 90) + ) + } + + @Test + fun isOverheatedChanged_true_drawableGetsTrue() { + mBatteryMeterView.setDisplayShieldEnabled(true) + val drawable = getBatteryDrawable() + + mBatteryMeterView.onIsOverheatedChanged(true) + + assertThat(drawable.displayShield).isTrue() + } + + @Test + fun isOverheatedChanged_false_drawableGetsFalse() { + mBatteryMeterView.setDisplayShieldEnabled(true) + val drawable = getBatteryDrawable() + + // Start as true + mBatteryMeterView.onIsOverheatedChanged(true) + + // Update to false + mBatteryMeterView.onIsOverheatedChanged(false) + + assertThat(drawable.displayShield).isFalse() + } + + @Test + fun isOverheatedChanged_true_featureflagOff_drawableGetsFalse() { + mBatteryMeterView.setDisplayShieldEnabled(false) + val drawable = getBatteryDrawable() + + mBatteryMeterView.onIsOverheatedChanged(true) + + assertThat(drawable.displayShield).isFalse() + } + + private fun getBatteryDrawable(): AccessorizedBatteryDrawable { + return (mBatteryMeterView.getChildAt(0) as ImageView) + .drawable as AccessorizedBatteryDrawable + } + private class Fetcher : BatteryEstimateFetcher { override fun fetchBatteryTimeRemainingEstimate( completion: EstimateFetchCompletion) { @@ -68,4 +246,4 @@ class BatteryMeterViewTest : SysuiTestCase() { private companion object { const val ESTIMATE = "2 hours 2 minutes" } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt new file mode 100644 index 000000000000..39cb0476e429 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt @@ -0,0 +1,101 @@ +/* + * 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.battery + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT +import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD +import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH +import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +@SmallTest +class BatterySpecsTest : SysuiTestCase() { + @Test + fun getFullBatteryHeight_shieldFalse_returnsMainHeight() { + val fullHeight = BatterySpecs.getFullBatteryHeight(56f, displayShield = false) + + assertThat(fullHeight).isEqualTo(56f) + } + + @Test + fun getFullBatteryHeight_shieldTrue_returnsMainHeightPlusShield() { + val mainHeight = BATTERY_HEIGHT * 5 + val fullHeight = BatterySpecs.getFullBatteryHeight(mainHeight, displayShield = true) + + // Since the main battery was scaled 5x, the output height should also be scaled 5x + val expectedFullHeight = BATTERY_HEIGHT_WITH_SHIELD * 5 + + assertThat(fullHeight).isWithin(.0001f).of(expectedFullHeight) + } + + @Test + fun getFullBatteryWidth_shieldFalse_returnsMainWidth() { + val fullWidth = BatterySpecs.getFullBatteryWidth(33f, displayShield = false) + + assertThat(fullWidth).isEqualTo(33f) + } + + @Test + fun getFullBatteryWidth_shieldTrue_returnsMainWidthPlusShield() { + val mainWidth = BATTERY_WIDTH * 3.3f + + val fullWidth = BatterySpecs.getFullBatteryWidth(mainWidth, displayShield = true) + + // Since the main battery was scaled 3.3x, the output width should also be scaled 5x + val expectedFullWidth = BATTERY_WIDTH_WITH_SHIELD * 3.3f + assertThat(fullWidth).isWithin(.0001f).of(expectedFullWidth) + } + + @Test + fun getMainBatteryHeight_shieldFalse_returnsFullHeight() { + val mainHeight = BatterySpecs.getMainBatteryHeight(89f, displayShield = false) + + assertThat(mainHeight).isEqualTo(89f) + } + + @Test + fun getMainBatteryHeight_shieldTrue_returnsNotFullHeight() { + val fullHeight = BATTERY_HEIGHT_WITH_SHIELD * 7.7f + + val mainHeight = BatterySpecs.getMainBatteryHeight(fullHeight, displayShield = true) + + // Since the full height was scaled 7.7x, the main height should also be scaled 7.7x. + val expectedHeight = BATTERY_HEIGHT * 7.7f + assertThat(mainHeight).isWithin(.0001f).of(expectedHeight) + } + + @Test + fun getMainBatteryWidth_shieldFalse_returnsFullWidth() { + val mainWidth = BatterySpecs.getMainBatteryWidth(2345f, displayShield = false) + + assertThat(mainWidth).isEqualTo(2345f) + } + + @Test + fun getMainBatteryWidth_shieldTrue_returnsNotFullWidth() { + val fullWidth = BATTERY_WIDTH_WITH_SHIELD * 0.6f + + val mainWidth = BatterySpecs.getMainBatteryWidth(fullWidth, displayShield = true) + + // Since the full width was scaled 0.6x, the main height should also be scaled 0.6x. + val expectedWidth = BATTERY_WIDTH * 0.6f + assertThat(mainWidth).isWithin(.0001f).of(expectedWidth) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 1b5f9b6d45cd..acdafe3e1c7d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -114,10 +114,8 @@ public class UdfpsControllerTest extends SysuiTestCase { @Rule public MockitoRule rule = MockitoJUnit.rule(); - // Unit under test private UdfpsController mUdfpsController; - // Dependencies private FakeExecutor mBiometricsExecutor; @Mock @@ -171,7 +169,6 @@ public class UdfpsControllerTest extends SysuiTestCase { private UdfpsDisplayMode mUdfpsDisplayMode; @Mock private FeatureFlags mFeatureFlags; - // Stuff for configuring mocks @Mock private UdfpsView mUdfpsView; @@ -249,54 +246,42 @@ public class UdfpsControllerTest extends SysuiTestCase { FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC, true /* resetLockoutRequiresHardwareAuthToken */); - List<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); - props.add(mOpticalProps); - props.add(mUltrasonicProps); - when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props); - mFgExecutor = new FakeExecutor(new FakeSystemClock()); // Create a fake background executor. mBiometricsExecutor = new FakeExecutor(new FakeSystemClock()); - mUdfpsController = new UdfpsController( - mContext, - execution, - mLayoutInflater, - mFingerprintManager, - mWindowManager, - mStatusBarStateController, - mFgExecutor, - new ShadeExpansionStateManager(), - mStatusBarKeyguardViewManager, - mDumpManager, - mKeyguardUpdateMonitor, - mFeatureFlags, - mFalsingManager, - mPowerManager, - mAccessibilityManager, - mLockscreenShadeTransitionController, - mScreenLifecycle, - mVibrator, - mUdfpsHapticsSimulator, - mUdfpsShell, - mKeyguardStateController, - mDisplayManager, - mHandler, - mConfigurationController, - mSystemClock, - mUnlockedScreenOffAnimationController, - mSystemUIDialogManager, - mLatencyTracker, - mActivityLaunchAnimator, - Optional.of(mAlternateTouchProvider), - mBiometricsExecutor, + initUdfpsController(true /* hasAlternateTouchProvider */); + } + + private void initUdfpsController(boolean hasAlternateTouchProvider) { + initUdfpsController(mOpticalProps, hasAlternateTouchProvider); + } + + private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps, + boolean hasAlternateTouchProvider) { + reset(mFingerprintManager); + reset(mScreenLifecycle); + + final Optional<AlternateUdfpsTouchProvider> alternateTouchProvider = + hasAlternateTouchProvider ? Optional.of(mAlternateTouchProvider) : Optional.empty(); + + mUdfpsController = new UdfpsController(mContext, new FakeExecution(), mLayoutInflater, + mFingerprintManager, mWindowManager, mStatusBarStateController, mFgExecutor, + new ShadeExpansionStateManager(), mStatusBarKeyguardViewManager, mDumpManager, + mKeyguardUpdateMonitor, mFeatureFlags, mFalsingManager, mPowerManager, + mAccessibilityManager, mLockscreenShadeTransitionController, mScreenLifecycle, + mVibrator, mUdfpsHapticsSimulator, mUdfpsShell, mKeyguardStateController, + mDisplayManager, mHandler, mConfigurationController, mSystemClock, + mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker, + mActivityLaunchAnimator, alternateTouchProvider, mBiometricsExecutor, mPrimaryBouncerInteractor); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); mScreenObserver = mScreenObserverCaptor.getValue(); - mUdfpsController.updateOverlayParams(mOpticalProps, new UdfpsOverlayParams()); + + mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams()); mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode); } @@ -333,8 +318,7 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice() - throws RemoteException { + public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException { onActionMoveTouch_whenCanDismissLockScreen_entersDevice(false /* stale */); } @@ -521,8 +505,37 @@ public class UdfpsControllerTest extends SysuiTestCase { new MotionEvent.PointerCoords[]{pc}, 0, 0, 1f, 1f, 0, 0, 0, 0); } + private static class TestParams { + public final FingerprintSensorPropertiesInternal sensorProps; + public final boolean hasAlternateTouchProvider; + + TestParams(FingerprintSensorPropertiesInternal sensorProps, + boolean hasAlternateTouchProvider) { + this.sensorProps = sensorProps; + this.hasAlternateTouchProvider = hasAlternateTouchProvider; + } + } + + private void runWithAllParams(ThrowingConsumer<TestParams> testParamsConsumer) { + for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps, + mUltrasonicProps)) { + for (boolean hasAlternateTouchProvider : new boolean[]{false, true}) { + initUdfpsController(sensorProps, hasAlternateTouchProvider); + testParamsConsumer.accept(new TestParams(sensorProps, hasAlternateTouchProvider)); + } + } + } + @Test - public void onTouch_propagatesTouchInNativeOrientationAndResolution() throws RemoteException { + public void onTouch_propagatesTouchInNativeOrientationAndResolution() { + runWithAllParams( + this::onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized); + } + + private void onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized( + TestParams testParams) throws RemoteException { + reset(mUdfpsView); + final Rect sensorBounds = new Rect(1000, 1900, 1080, 1920); // Bottom right corner. final int displayWidth = 1080; final int displayHeight = 1920; @@ -541,13 +554,13 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // Show the overlay. - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); // Test ROTATION_0 - mUdfpsController.updateOverlayParams(mOpticalProps, + mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, scaleFactor, Surface.ROTATION_0)); MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, @@ -559,12 +572,19 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricsExecutor.runAllReady(); event.recycle(); - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), + eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor)); + } // Test ROTATION_90 reset(mAlternateTouchProvider); - mUdfpsController.updateOverlayParams(mOpticalProps, + reset(mFingerprintManager); + mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, scaleFactor, Surface.ROTATION_90)); event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor); @@ -575,12 +595,19 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricsExecutor.runAllReady(); event.recycle(); - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), + eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor)); + } // Test ROTATION_270 reset(mAlternateTouchProvider); - mUdfpsController.updateOverlayParams(mOpticalProps, + reset(mFingerprintManager); + mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, scaleFactor, Surface.ROTATION_270)); event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor); @@ -591,12 +618,19 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricsExecutor.runAllReady(); event.recycle(); - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), + eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor)); + } // Test ROTATION_180 reset(mAlternateTouchProvider); - mUdfpsController.updateOverlayParams(mOpticalProps, + reset(mFingerprintManager); + mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, scaleFactor, Surface.ROTATION_180)); // ROTATION_180 is not supported. It should be treated like ROTATION_0. @@ -608,26 +642,22 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricsExecutor.runAllReady(); event.recycle(); - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); - } - - private void runForAllUdfpsTypes( - ThrowingConsumer<FingerprintSensorPropertiesInternal> sensorPropsConsumer) { - for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps, - mUltrasonicProps)) { - mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams()); - sensorPropsConsumer.accept(sensorProps); + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), + eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor)); } } @Test public void fingerDown() { - runForAllUdfpsTypes(this::fingerDownForSensor); + runWithAllParams(this::fingerDownParameterized); } - private void fingerDownForSensor(FingerprintSensorPropertiesInternal sensorProps) - throws RemoteException { + private void fingerDownParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker, mKeyguardUpdateMonitor); @@ -637,7 +667,7 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); // GIVEN that the overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); @@ -655,14 +685,22 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor.runAllReady(); - // THEN FingerprintManager is notified about onPointerDown - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f), - eq(0f)); - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(), - anyFloat(), anyFloat()); + // THEN the touch provider is notified about onPointerDown. + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f), + eq(0f)); + verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), + anyInt(), anyFloat(), anyFloat()); + verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(0f), eq(0f)); + verify(mAlternateTouchProvider, never()).onPointerDown(anyInt(), anyInt(), anyInt(), + anyFloat(), anyFloat()); + } // AND display configuration begins - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture()); } else { @@ -671,16 +709,27 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mUdfpsView, never()).configureDisplay(any()); } verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // AND onDisplayConfigured notifies FingerprintManager about onUiReady mOnDisplayConfiguredCaptor.getValue().run(); mBiometricsExecutor.runAllReady(); - InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker); - inOrder.verify(mAlternateTouchProvider).onUiReady(); - inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); + if (testParams.hasAlternateTouchProvider) { + InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker); + inOrder.verify(mAlternateTouchProvider).onUiReady(); + inOrder.verify(mLatencyTracker).onActionEnd( + eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); + verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt()); + } else { + InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker); + inOrder.verify(mFingerprintManager).onUiReady(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId)); + inOrder.verify(mLatencyTracker).onActionEnd( + eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); + verify(mAlternateTouchProvider, never()).onUiReady(); + } } else { + verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt()); verify(mAlternateTouchProvider, never()).onUiReady(); verify(mLatencyTracker, never()).onActionEnd( eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); @@ -689,24 +738,23 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterrupt() { - runForAllUdfpsTypes(this::aodInterruptForSensor); + runWithAllParams(this::aodInterruptParameterized); } - private void aodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps) - throws RemoteException { + private void aodInterruptParameterized(TestParams testParams) throws RemoteException { mUdfpsController.cancelAodInterrupt(); reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor); when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); // GIVEN that the overlay is showing and screen is on and fp is running - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); // WHEN fingerprint is requested because of AOD interrupt mUdfpsController.onAodInterrupt(0, 0, 2f, 3f); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // THEN display configuration begins // AND onDisplayConfigured notifies FingerprintManager about onUiReady verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture()); @@ -715,29 +763,37 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mUdfpsView, never()).configureDisplay(mOnDisplayConfiguredCaptor.capture()); } mBiometricsExecutor.runAllReady(); - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), - eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */); - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(), - anyFloat(), anyFloat()); - verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); + + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), + eq(3f) /* minor */, eq(2f) /* major */); + verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), + anyInt(), anyFloat(), anyFloat()); + verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(3f) /* minor */, + eq(2f) /* major */); + verify(mAlternateTouchProvider, never()).onPointerDown(anyLong(), anyInt(), anyInt(), + anyFloat(), anyFloat()); + } } @Test public void cancelAodInterrupt() { - runForAllUdfpsTypes(this::cancelAodInterruptForSensor); + runWithAllParams(this::cancelAodInterruptParameterized); } - private void cancelAodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps) - throws RemoteException { + private void cancelAodInterruptParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { when(mUdfpsView.isDisplayConfigured()).thenReturn(true); // WHEN it is cancelled mUdfpsController.cancelAodInterrupt(); @@ -754,21 +810,20 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterruptTimeout() { - runForAllUdfpsTypes(this::aodInterruptTimeoutForSensor); + runWithAllParams(this::aodInterruptTimeoutParameterized); } - private void aodInterruptTimeoutForSensor(FingerprintSensorPropertiesInternal sensorProps) - throws RemoteException { + private void aodInterruptTimeoutParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { when(mUdfpsView.isDisplayConfigured()).thenReturn(true); } else { when(mUdfpsView.isDisplayConfigured()).thenReturn(false); @@ -776,7 +831,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // WHEN it times out mFgExecutor.advanceClockToNext(); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // THEN the display is unconfigured. verify(mUdfpsView).unconfigureDisplay(); } else { @@ -787,23 +842,23 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterruptCancelTimeoutActionOnFingerUp() { - runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnFingerUpForSensor); + runWithAllParams(this::aodInterruptCancelTimeoutActionOnFingerUpParameterized); } - private void aodInterruptCancelTimeoutActionOnFingerUpForSensor( - FingerprintSensorPropertiesInternal sensorProps) throws RemoteException { + private void aodInterruptCancelTimeoutActionOnFingerUpParameterized(TestParams testParams) + throws RemoteException { reset(mUdfpsView); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the ACTION_UP event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); } else { @@ -833,7 +888,7 @@ public class UdfpsControllerTest extends SysuiTestCase { moveEvent.recycle(); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the finger up event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); } else { @@ -844,7 +899,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor.advanceClockToNext(); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // THEN the display should be unconfigured once. If the timeout action is not // cancelled, the display would be unconfigured twice which would cause two // FP attempts. @@ -856,23 +911,23 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterruptCancelTimeoutActionOnAcquired() { - runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnAcquiredForSensor); + runWithAllParams(this::aodInterruptCancelTimeoutActionOnAcquiredParameterized); } - private void aodInterruptCancelTimeoutActionOnAcquiredForSensor( - FingerprintSensorPropertiesInternal sensorProps) throws RemoteException { + private void aodInterruptCancelTimeoutActionOnAcquiredParameterized(TestParams testParams) + throws RemoteException { reset(mUdfpsView); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the acquired event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); } else { @@ -880,7 +935,7 @@ public class UdfpsControllerTest extends SysuiTestCase { } // WHEN acquired is received - mOverlayController.onAcquired(sensorProps.sensorId, + mOverlayController.onAcquired(testParams.sensorProps.sensorId, BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD); // Configure UdfpsView to accept the ACTION_DOWN event @@ -900,7 +955,7 @@ public class UdfpsControllerTest extends SysuiTestCase { moveEvent.recycle(); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the finger up event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); } else { @@ -911,7 +966,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor.advanceClockToNext(); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // THEN the display should be unconfigured once. If the timeout action is not // cancelled, the display would be unconfigured twice which would cause two // FP attempts. @@ -923,15 +978,14 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterruptScreenOff() { - runForAllUdfpsTypes(this::aodInterruptScreenOffForSensor); + runWithAllParams(this::aodInterruptScreenOffParameterized); } - private void aodInterruptScreenOffForSensor(FingerprintSensorPropertiesInternal sensorProps) - throws RemoteException { + private void aodInterruptScreenOffParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView); // GIVEN screen off - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOff(); mFgExecutor.runAllReady(); @@ -945,17 +999,16 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterrupt_fingerprintNotRunning() { - runForAllUdfpsTypes(this::aodInterrupt_fingerprintNotRunningForSensor); + runWithAllParams(this::aodInterrupt_fingerprintNotRunningParameterized); } - private void aodInterrupt_fingerprintNotRunningForSensor( - FingerprintSensorPropertiesInternal sensorProps) throws RemoteException { + private void aodInterrupt_fingerprintNotRunningParameterized(TestParams testParams) + throws RemoteException { reset(mUdfpsView); // GIVEN showing overlay - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, - mUdfpsOverlayControllerCallback); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java index 849ac5ef90d7..7a2ba95f74a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java @@ -347,21 +347,22 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { addComplication(engine, thirdViewInfo); - // The first added view should now be underneath the second view. + // The first added view should now be underneath the third view. verifyChange(firstViewInfo, false, lp -> { assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue(); assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); assertThat(lp.topMargin).isEqualTo(margin); }); - // The second view should be in underneath the third view. + // The second view should be to the start of the third view. verifyChange(secondViewInfo, false, lp -> { assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue(); assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); assertThat(lp.getMarginEnd()).isEqualTo(margin); }); - // The third view should be in at the top. + // The third view should be at the top end corner. No margin should be applied if not + // specified. verifyChange(thirdViewInfo, true, lp -> { assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); @@ -425,14 +426,14 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { addComplication(engine, thirdViewInfo); - // The first added view should now be underneath the second view. + // The first added view should now be underneath the third view. verifyChange(firstViewInfo, false, lp -> { assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue(); assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); assertThat(lp.topMargin).isEqualTo(complicationMargin); }); - // The second view should be in underneath the third view. + // The second view should be to the start of the third view. verifyChange(secondViewInfo, false, lp -> { assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue(); assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); @@ -441,6 +442,69 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { } /** + * Ensures the root complication applies margin if specified. + */ + @Test + public void testRootComplicationSpecifiedMargin() { + final int defaultMargin = 5; + final int complicationMargin = 10; + final ComplicationLayoutEngine engine = + new ComplicationLayoutEngine(mLayout, defaultMargin, mTouchSession, 0, 0); + + final ViewInfo firstViewInfo = new ViewInfo( + new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_TOP + | ComplicationLayoutParams.POSITION_END, + ComplicationLayoutParams.DIRECTION_DOWN, + 0), + Complication.CATEGORY_STANDARD, + mLayout); + + addComplication(engine, firstViewInfo); + + final ViewInfo secondViewInfo = new ViewInfo( + new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_TOP + | ComplicationLayoutParams.POSITION_END, + ComplicationLayoutParams.DIRECTION_START, + 0), + Complication.CATEGORY_SYSTEM, + mLayout); + + addComplication(engine, secondViewInfo); + + firstViewInfo.clearInvocations(); + secondViewInfo.clearInvocations(); + + final ViewInfo thirdViewInfo = new ViewInfo( + new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_TOP + | ComplicationLayoutParams.POSITION_END, + ComplicationLayoutParams.DIRECTION_START, + 1, + complicationMargin), + Complication.CATEGORY_SYSTEM, + mLayout); + + addComplication(engine, thirdViewInfo); + + // The third view is the root view and has specified margin, which should be applied based + // on its direction. + verifyChange(thirdViewInfo, true, lp -> { + assertThat(lp.getMarginStart()).isEqualTo(0); + assertThat(lp.getMarginEnd()).isEqualTo(complicationMargin); + assertThat(lp.topMargin).isEqualTo(0); + assertThat(lp.bottomMargin).isEqualTo(0); + }); + } + + /** * Ensures layout in a particular position updates. */ @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java index cb7e47b28bcd..ce7561e95f1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java @@ -97,6 +97,31 @@ public class ComplicationLayoutParamsTest extends SysuiTestCase { } /** + * Ensures ComplicationLayoutParams correctly returns whether the complication specified margin. + */ + @Test + public void testIsMarginSpecified() { + final ComplicationLayoutParams paramsNoMargin = new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_TOP + | ComplicationLayoutParams.POSITION_START, + ComplicationLayoutParams.DIRECTION_DOWN, + 0); + assertThat(paramsNoMargin.isMarginSpecified()).isFalse(); + + final ComplicationLayoutParams paramsWithMargin = new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_TOP + | ComplicationLayoutParams.POSITION_START, + ComplicationLayoutParams.DIRECTION_DOWN, + 0, + 20 /*margin*/); + assertThat(paramsWithMargin.isMarginSpecified()).isTrue(); + } + + /** * Ensures unspecified margin uses default. */ @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java index aa8c93edce68..30ad485d7ac3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java @@ -90,7 +90,10 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { private ActivityStarter mActivityStarter; @Mock - UiEventLogger mUiEventLogger; + private UiEventLogger mUiEventLogger; + + @Captor + private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor; @Before public void setup() { @@ -164,6 +167,29 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { verify(mDreamOverlayStateController).addComplication(mComplication); } + @Test + public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() { + final DreamHomeControlsComplication.Registrant registrant = + new DreamHomeControlsComplication.Registrant(mComplication, + mDreamOverlayStateController, mControlsComponent); + registrant.start(); + + setServiceAvailable(true); + setHaveFavorites(false); + + // Complication not available on start. + verify(mDreamOverlayStateController, never()).addComplication(mComplication); + + // Favorite controls added, complication should be available now. + setHaveFavorites(true); + + // Dream overlay becomes active. + setDreamOverlayActive(true); + + // Verify complication is added. + verify(mDreamOverlayStateController).addComplication(mComplication); + } + /** * Ensures clicking home controls chip logs UiEvent. */ @@ -196,10 +222,17 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { private void setServiceAvailable(boolean value) { final List<ControlsServiceInfo> serviceInfos = mock(List.class); + when(mControlsListingController.getCurrentServices()).thenReturn(serviceInfos); when(serviceInfos.isEmpty()).thenReturn(!value); triggerControlsListingCallback(serviceInfos); } + private void setDreamOverlayActive(boolean value) { + when(mDreamOverlayStateController.isOverlayActive()).thenReturn(value); + verify(mDreamOverlayStateController).addCallback(mStateCallbackCaptor.capture()); + mStateCallbackCaptor.getValue().onStateChanged(); + } + private void triggerControlsListingCallback(List<ControlsServiceInfo> serviceInfos) { verify(mControlsListingController).addCallback(mCallbackCaptor.capture()); mCallbackCaptor.getValue().onServicesUpdated(serviceInfos); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index ade83cf58e6e..6ba06344314c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Position import com.android.systemui.doze.DozeHost @@ -60,12 +61,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { underTest = KeyguardRepositoryImpl( - statusBarStateController, - dozeHost, - wakefulnessLifecycle, - biometricUnlockController, - keyguardStateController, - keyguardUpdateMonitor, + statusBarStateController, + dozeHost, + wakefulnessLifecycle, + biometricUnlockController, + keyguardStateController, + keyguardUpdateMonitor, ) } @@ -257,6 +258,48 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun isKeyguardGoingAway() = runBlockingTest { + whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false) + var latest: Boolean? = null + val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + val captor = argumentCaptor<KeyguardStateController.Callback>() + verify(keyguardStateController).addCallback(captor.capture()) + + whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true) + captor.value.onKeyguardGoingAwayChanged() + assertThat(latest).isTrue() + + whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false) + captor.value.onKeyguardGoingAwayChanged() + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun isDreaming() = runBlockingTest { + whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false) + var latest: Boolean? = null + val job = underTest.isDreaming.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(captor.capture()) + + captor.value.onDreamingStateChanged(true) + assertThat(latest).isTrue() + + captor.value.onDreamingStateChanged(false) + assertThat(latest).isFalse() + + job.cancel() + } + + @Test fun biometricUnlockState() = runBlockingTest { val values = mutableListOf<BiometricUnlockModel>() val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this) 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 27d5d0a98978..2b03722f9f31 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 @@ -25,8 +25,8 @@ import android.view.Choreographer.FrameCallback import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Interpolators +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.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState @@ -38,7 +38,6 @@ import java.util.UUID import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn @@ -91,18 +90,51 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } } - assertSteps(steps, listWithStep(BigDecimal(.1))) + assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN) job.cancel() provider.stop() } @Test - fun `startTransition called during another transition fails`() { - underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null)) - underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null)) + fun `starting second transition will cancel the first transition`() { + runBlocking(IMMEDIATE) { + val (animator, provider) = setupAnimator(this) - assertThat(wtfHandler.failed).isTrue() + val steps = mutableListOf<TransitionStep>() + val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this) + + underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator)) + // 3 yields(), alternating with the animator, results in a value 0.1, which can be + // canceled and tested against + yield() + yield() + yield() + + // Now start 2nd transition, which will interrupt the first + val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this) + val (animator2, provider2) = setupAnimator(this) + underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, animator2)) + + val startTime = System.currentTimeMillis() + while (animator2.isRunning()) { + yield() + if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) { + fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION") + } + } + + val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1)) + assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN) + + val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.9)) + assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD) + + job.cancel() + job2.cancel() + provider.stop() + provider2.stop() + } } @Test @@ -165,11 +197,15 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { assertThat(wtfHandler.failed).isTrue() } - private fun listWithStep(step: BigDecimal): List<BigDecimal> { + private fun listWithStep( + step: BigDecimal, + start: BigDecimal = BigDecimal.ZERO, + stop: BigDecimal = BigDecimal.ONE, + ): List<BigDecimal> { val steps = mutableListOf<BigDecimal>() - var i = BigDecimal.ZERO - while (i.compareTo(BigDecimal.ONE) <= 0) { + var i = start + while (i.compareTo(stop) <= 0) { steps.add(i) i = (i + step).setScale(2, RoundingMode.HALF_UP) } @@ -177,23 +213,43 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { return steps } - private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) { + private fun assertSteps( + steps: List<TransitionStep>, + fractions: List<BigDecimal>, + from: KeyguardState, + to: KeyguardState, + ) { assertThat(steps[0]) - .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME)) + .isEqualTo( + TransitionStep( + from, + to, + fractions[0].toFloat(), + TransitionState.STARTED, + OWNER_NAME + ) + ) fractions.forEachIndexed { index, fraction -> assertThat(steps[index + 1]) .isEqualTo( TransitionStep( - AOD, - LOCKSCREEN, + from, + to, fraction.toFloat(), TransitionState.RUNNING, OWNER_NAME ) ) } + val lastValue = fractions[fractions.size - 1].toFloat() + val status = + if (lastValue < 1f) { + TransitionState.CANCELED + } else { + TransitionState.FINISHED + } assertThat(steps[steps.size - 1]) - .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)) + .isEqualTo(TransitionStep(from, to, lastValue, status, OWNER_NAME)) assertThat(wtfHandler.failed).isFalse() } @@ -230,7 +286,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { scope.launch { frames.collect { // Delay is required for AnimationHandler to properly register a callback - delay(1) + yield() val (frameNumber, callback) = it callback?.doFrame(frameNumber) } @@ -243,7 +299,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } override fun postFrameCallback(cb: FrameCallback) { - frames.value = Pair(++frameCount, cb) + frames.value = Pair(frameCount++, cb) } override fun postCommitCallback(runnable: Runnable) {} override fun getFrameTime() = frameCount diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt index c85f7b9e6885..3269f5a913ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -93,7 +93,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Test fun testShow_isScrimmed() { mPrimaryBouncerInteractor.show(true) - verify(repository).setShowMessage(null) verify(repository).setOnScreenTurnedOff(false) verify(repository).setKeyguardAuthenticated(null) verify(repository).setPrimaryHide(false) @@ -155,6 +154,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN) verify(repository).setPrimaryVisible(false) verify(repository).setPrimaryShow(null) + verify(repository).setPrimaryHide(true) verify(falsingCollector).onBouncerHidden() verify(mPrimaryBouncerCallbackInteractor).dispatchReset() verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 68a5f47c5e0b..885cc54af7cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -261,7 +261,12 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Test fun updateView_noOverrides_usesInfoFromAppIcon() { controllerReceiver.displayView( - ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride = null) + ChipReceiverInfo( + routeInfo, + appIconDrawableOverride = null, + appNameOverride = null, + id = "id", + ) ) val view = getChipView() @@ -274,7 +279,12 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { val drawableOverride = context.getDrawable(R.drawable.ic_celebration)!! controllerReceiver.displayView( - ChipReceiverInfo(routeInfo, drawableOverride, appNameOverride = null) + ChipReceiverInfo( + routeInfo, + drawableOverride, + appNameOverride = null, + id = "id", + ) ) val view = getChipView() @@ -286,7 +296,12 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { val appNameOverride = "Sweet New App" controllerReceiver.displayView( - ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride) + ChipReceiverInfo( + routeInfo, + appIconDrawableOverride = null, + appNameOverride, + id = "id", + ) ) val view = getChipView() @@ -340,7 +355,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { .addFeature("feature") .setClientPackageName(packageName) .build() - return ChipReceiverInfo(routeInfo, null, null) + return ChipReceiverInfo(routeInfo, null, null, id = "id") } private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index cd7a949443c9..72e022ed7bbb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -439,6 +439,17 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { verify(mQSPanelController).setExpanded(false); } + @Test + public void startsListeningAfterStateChangeToExpanded_inSplitShade() { + QSFragment fragment = resumeAndGetFragment(); + enableSplitShade(); + fragment.setQsVisible(true); + clearInvocations(mQSPanelController); + + fragment.setExpanded(true); + verify(mQSPanelController).setListening(true, true); + } + @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { MockitoAnnotations.initMocks(this); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index 7b1e5c9f7264..05c1f1574d1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -5,6 +5,7 @@ import static android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS; import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT; import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT; import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT; import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX; @@ -13,6 +14,7 @@ import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -36,6 +38,7 @@ import android.net.wifi.WifiManager; import android.os.Handler; import android.telephony.ServiceState; import android.telephony.SignalStrength; +import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; @@ -55,6 +58,8 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.UnreleasedFlag; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -68,12 +73,15 @@ import com.android.systemui.util.time.FakeSystemClock; import com.android.wifitrackerlib.MergedCarrierEntry; import com.android.wifitrackerlib.WifiEntry; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import java.util.ArrayList; import java.util.List; @@ -84,6 +92,9 @@ import java.util.List; public class InternetDialogControllerTest extends SysuiTestCase { private static final int SUB_ID = 1; + private static final int SUB_ID2 = 2; + + private MockitoSession mStaticMockSession; //SystemUIToast private static final int GRAVITY_FLAGS = Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL; @@ -150,6 +161,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { private WifiStateWorker mWifiStateWorker; @Mock private SignalStrength mSignalStrength; + @Mock + private FeatureFlags mFlags; private TestableResources mTestableResources; private InternetDialogController mInternetDialogController; @@ -159,6 +172,10 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Before public void setUp() { + mStaticMockSession = mockitoSession() + .mockStatic(SubscriptionManager.class) + .strictness(Strictness.LENIENT) + .startMocking(); MockitoAnnotations.initMocks(this); mTestableResources = mContext.getOrCreateTestableResources(); doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt()); @@ -175,6 +192,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { mAccessPoints.add(mWifiEntry1); when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry); when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID}); + when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(SUB_ID); when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt())) .thenReturn(mSystemUIToast); when(mSystemUIToast.getView()).thenReturn(mToastView); @@ -188,7 +206,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher, mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController, mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker, - mLocationController, mDialogLaunchAnimator, mWifiStateWorker); + mLocationController, mDialogLaunchAnimator, mWifiStateWorker, mFlags); mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, mInternetDialogController.mOnSubscriptionsChangedListener); mInternetDialogController.onStart(mInternetDialogCallback, true); @@ -197,6 +215,11 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.mWifiIconInjector = mWifiIconInjector; } + @After + public void tearDown() { + mStaticMockSession.finishMocking(); + } + @Test public void connectCarrierNetwork_mergedCarrierEntryCanConnect_connectAndCreateSysUiToast() { when(mMergedCarrierEntry.canConnect()).thenReturn(true); @@ -325,15 +348,45 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withNoService_returnNoNetworksAvailable() { + when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true); + InternetDialogController spyController = spy(mInternetDialogController); fakeAirplaneModeEnabled(false); when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); - mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); + spyController.onAccessPointsChanged(null /* accessPoints */); + doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId(); doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState(); doReturn(mServiceState).when(mTelephonyManager).getServiceState(); doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState(); - assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false), + assertFalse(TextUtils.equals(spyController.getSubtitleText(false), + getResourcesString("all_network_unavailable"))); + + doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID) + .when(spyController).getActiveAutoSwitchNonDdsSubId(); + spyController.onAccessPointsChanged(null /* accessPoints */); + assertTrue(TextUtils.equals(spyController.getSubtitleText(false), + getResourcesString("all_network_unavailable"))); + } + + @Test + public void getSubtitleText_withNoService_returnNoNetworksAvailable_flagOff() { + InternetDialogController spyController = spy(mInternetDialogController); + fakeAirplaneModeEnabled(false); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); + spyController.onAccessPointsChanged(null /* accessPoints */); + + doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState(); + doReturn(mServiceState).when(mTelephonyManager).getServiceState(); + doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState(); + + assertTrue(TextUtils.equals(spyController.getSubtitleText(false), + getResourcesString("all_network_unavailable"))); + + doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID) + .when(spyController).getActiveAutoSwitchNonDdsSubId(); + spyController.onAccessPointsChanged(null /* accessPoints */); + assertTrue(TextUtils.equals(spyController.getSubtitleText(false), getResourcesString("all_network_unavailable"))); } @@ -651,6 +704,108 @@ public class InternetDialogControllerTest extends SysuiTestCase { } @Test + public void getSignalStrengthIcon_differentSubId() { + when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true); + InternetDialogController spyController = spy(mInternetDialogController); + Drawable icons = spyController.getSignalStrengthIcon(SUB_ID, mContext, 1, 1, 0, false); + Drawable icons2 = spyController.getSignalStrengthIcon(SUB_ID2, mContext, 1, 1, 0, false); + + assertThat(icons).isNotEqualTo(icons2); + } + + @Test + public void getActiveAutoSwitchNonDdsSubId() { + when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true); + // active on non-DDS + SubscriptionInfo info = mock(SubscriptionInfo.class); + doReturn(SUB_ID2).when(info).getSubscriptionId(); + when(mSubscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(info); + + int subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId(); + assertThat(subId).isEqualTo(SUB_ID2); + + // active on CBRS + doReturn(true).when(info).isOpportunistic(); + subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId(); + assertThat(subId).isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID); + + // active on DDS + doReturn(false).when(info).isOpportunistic(); + doReturn(SUB_ID).when(info).getSubscriptionId(); + when(mSubscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(info); + + subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId(); + assertThat(subId).isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID); + } + + @Test + public void getActiveAutoSwitchNonDdsSubId_flagOff() { + // active on non-DDS + SubscriptionInfo info = mock(SubscriptionInfo.class); + doReturn(SUB_ID2).when(info).getSubscriptionId(); + when(mSubscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(info); + + int subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId(); + assertThat(subId).isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID); + } + + @Test + public void getMobileNetworkSummary() { + when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true); + InternetDialogController spyController = spy(mInternetDialogController); + doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId(); + doReturn(true).when(spyController).isMobileDataEnabled(); + doReturn(true).when(spyController).activeNetworkIsCellular(); + String dds = spyController.getMobileNetworkSummary(SUB_ID); + String nonDds = spyController.getMobileNetworkSummary(SUB_ID2); + + assertThat(dds).contains(mContext.getString(R.string.mobile_data_poor_connection)); + assertThat(dds).isNotEqualTo(nonDds); + } + + @Test + public void getMobileNetworkSummary_flagOff() { + InternetDialogController spyController = spy(mInternetDialogController); + doReturn(true).when(spyController).isMobileDataEnabled(); + doReturn(true).when(spyController).activeNetworkIsCellular(); + String dds = spyController.getMobileNetworkSummary(SUB_ID); + + assertThat(dds).contains(mContext.getString(R.string.mobile_data_connection_active)); + } + + @Test + public void launchMobileNetworkSettings_validSubId() { + when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true); + InternetDialogController spyController = spy(mInternetDialogController); + doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId(); + spyController.launchMobileNetworkSettings(mDialogLaunchView); + + verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt(), + any()); + } + + @Test + public void launchMobileNetworkSettings_invalidSubId() { + when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true); + InternetDialogController spyController = spy(mInternetDialogController); + doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID) + .when(spyController).getActiveAutoSwitchNonDdsSubId(); + spyController.launchMobileNetworkSettings(mDialogLaunchView); + + verify(mActivityStarter, never()) + .postStartActivityDismissingKeyguard(any(Intent.class), anyInt()); + } + + @Test + public void setAutoDataSwitchMobileDataPolicy() { + when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true); + mInternetDialogController.setAutoDataSwitchMobileDataPolicy(SUB_ID, true); + + verify(mTelephonyManager).setMobileDataPolicyEnabled(eq( + TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH), eq(true)); + } + + @Test public void getSignalStrengthDrawableWithLevel_carrierNetworkIsNotActive_useMobileDataLevel() { // Fake mobile data level as SIGNAL_STRENGTH_POOR(1) when(mSignalStrength.getLevel()).thenReturn(SIGNAL_STRENGTH_POOR); @@ -658,9 +813,9 @@ public class InternetDialogControllerTest extends SysuiTestCase { when(mInternetDialogController.getCarrierNetworkLevel()).thenReturn(WIFI_LEVEL_MAX); InternetDialogController spyController = spy(mInternetDialogController); - spyController.getSignalStrengthDrawableWithLevel(false /* isCarrierNetworkActive */); + spyController.getSignalStrengthDrawableWithLevel(false /* isCarrierNetworkActive */, 0); - verify(spyController).getSignalStrengthIcon(any(), eq(SIGNAL_STRENGTH_POOR), + verify(spyController).getSignalStrengthIcon(eq(0), any(), eq(SIGNAL_STRENGTH_POOR), eq(NUM_SIGNAL_STRENGTH_BINS), anyInt(), anyBoolean()); } @@ -672,9 +827,9 @@ public class InternetDialogControllerTest extends SysuiTestCase { when(mInternetDialogController.getCarrierNetworkLevel()).thenReturn(WIFI_LEVEL_MAX); InternetDialogController spyController = spy(mInternetDialogController); - spyController.getSignalStrengthDrawableWithLevel(true /* isCarrierNetworkActive */); + spyController.getSignalStrengthDrawableWithLevel(true /* isCarrierNetworkActive */, 0); - verify(spyController).getSignalStrengthIcon(any(), eq(WIFI_LEVEL_MAX), + verify(spyController).getSignalStrengthIcon(eq(0), any(), eq(WIFI_LEVEL_MAX), eq(WIFI_LEVEL_MAX + 1), anyInt(), anyBoolean()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index f92247580df0..8c8fdc5bf126 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -8,12 +8,15 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.os.Handler; import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; @@ -31,6 +34,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -72,6 +76,8 @@ public class InternetDialogTest extends SysuiTestCase { private InternetDialogController mInternetDialogController; @Mock private KeyguardStateController mKeyguard; + @Mock + private DialogLaunchAnimator mDialogLaunchAnimator; private FakeExecutor mBgExecutor = new FakeExecutor(new FakeSystemClock()); private InternetDialog mInternetDialog; @@ -100,8 +106,9 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetWifiEntry.hasInternetAccess()).thenReturn(true); when(mWifiEntries.size()).thenReturn(1); - when(mInternetDialogController.getMobileNetworkTitle()).thenReturn(MOBILE_NETWORK_TITLE); - when(mInternetDialogController.getMobileNetworkSummary()) + when(mInternetDialogController.getMobileNetworkTitle(anyInt())) + .thenReturn(MOBILE_NETWORK_TITLE); + when(mInternetDialogController.getMobileNetworkSummary(anyInt())) .thenReturn(MOBILE_NETWORK_SUMMARY); when(mInternetDialogController.isWifiEnabled()).thenReturn(true); @@ -115,7 +122,8 @@ public class InternetDialogTest extends SysuiTestCase { private void createInternetDialog() { mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class), - mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler, + mInternetDialogController, true, true, true, mock(UiEventLogger.class), + mDialogLaunchAnimator, mHandler, mBgExecutor, mKeyguard); mInternetDialog.mAdapter = mInternetAdapter; mInternetDialog.mConnectedWifiEntry = mInternetWifiEntry; @@ -307,12 +315,18 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() { + mInternetDialog.dismissDialog(); + doReturn(true).when(mInternetDialogController).hasActiveSubId(); + createInternetDialog(); // The preconditions WiFi ON and Internet WiFi are already in setUp() doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); - mInternetDialog.updateDialog(false); + mInternetDialog.updateDialog(true); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE); + LinearLayout secondaryLayout = mDialogView.requireViewById( + R.id.secondary_mobile_network_layout); + assertThat(secondaryLayout.getVisibility()).isEqualTo(View.GONE); } @Test @@ -460,6 +474,45 @@ public class InternetDialogTest extends SysuiTestCase { } @Test + public void updateDialog_showSecondaryDataSub() { + mInternetDialog.dismissDialog(); + doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId(); + doReturn(true).when(mInternetDialogController).hasActiveSubId(); + doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled(); + createInternetDialog(); + + clearInvocations(mInternetDialogController); + mInternetDialog.updateDialog(true); + + LinearLayout primaryLayout = mDialogView.requireViewById( + R.id.mobile_network_layout); + LinearLayout secondaryLayout = mDialogView.requireViewById( + R.id.secondary_mobile_network_layout); + + verify(mInternetDialogController).getMobileNetworkSummary(1); + assertThat(primaryLayout.getBackground()).isNotEqualTo(secondaryLayout.getBackground()); + + // Tap the primary sub info + primaryLayout.performClick(); + ArgumentCaptor<AlertDialog> dialogArgumentCaptor = + ArgumentCaptor.forClass(AlertDialog.class); + verify(mDialogLaunchAnimator).showFromDialog(dialogArgumentCaptor.capture(), + eq(mInternetDialog), eq(null), eq(false)); + AlertDialog dialog = dialogArgumentCaptor.getValue(); + dialog.show(); + dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); + // TODO(b/253399304) + // TestableLooper.get(this).processAllMessages(); + // verify(mInternetDialogController).setAutoDataSwitchMobileDataPolicy(1, false); + + // Tap the secondary sub info + secondaryLayout.performClick(); + verify(mInternetDialogController).launchMobileNetworkSettings(any(View.class)); + + dialog.dismiss(); + } + + @Test public void updateDialog_wifiOn_hideWifiScanNotify() { // The preconditions WiFi ON and WiFi entries are already in setUp() 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 0ce9056dc1d1..d7eb337efd3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt @@ -24,6 +24,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -320,6 +321,48 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { assertThat(changes.largeScreenConstraintsChanges).isNull() } + @Test + fun testRelevantViewsAreNotMatchConstraints() { + 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") + .that(qqsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0) + assertWithMessage("$name has 0 width in qqs") + .that(qqsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0) + assertWithMessage("$name has 0 height in qs") + .that(qsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0) + assertWithMessage("$name has 0 width in qs") + .that(qsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0) + } + } + + @Test + 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) -> + assertWithMessage("$name changes height") + .that(qqsConstraint.getConstraint(id).layout.mHeight) + .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight) + assertWithMessage("$name changes width") + .that(qqsConstraint.getConstraint(id).layout.mWidth) + .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth) + } + } + private operator fun ConstraintsChanges.invoke() { qqsConstraintsChanges?.invoke(qqsConstraint) qsConstraintsChanges?.invoke(qsConstraint) 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 1f71e3c64ec4..7d2251e20021 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -94,7 +94,6 @@ import com.android.systemui.DejankUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.camera.CameraGestureHelper; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.doze.DozeLog; @@ -492,7 +491,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mUnlockedScreenOffAnimationController, mShadeTransitionController, systemClock, - mock(CameraGestureHelper.class), mKeyguardBottomAreaViewModel, mKeyguardBottomAreaInteractor, mDumpManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 797256f91022..1598dbb7d29e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -1064,7 +1064,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // GIVEN a trust granted message but trust isn't granted final String trustGrantedMsg = "testing trust granted message"; - mController.getKeyguardCallback().showTrustGrantedMessage(trustGrantedMsg); + mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, trustGrantedMsg); verifyHideIndication(INDICATION_TYPE_TRUST); @@ -1088,7 +1088,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN the showTrustGranted method is called final String trustGrantedMsg = "testing trust granted message"; - mController.getKeyguardCallback().showTrustGrantedMessage(trustGrantedMsg); + mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, trustGrantedMsg); // THEN verify the trust granted message shows verifyIndicationMessage( @@ -1105,7 +1105,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true); // WHEN the showTrustGranted method is called with a null message - mController.getKeyguardCallback().showTrustGrantedMessage(null); + mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, null); // THEN verify the default trust granted message shows verifyIndicationMessage( @@ -1122,7 +1122,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true); // WHEN the showTrustGranted method is called with an EMPTY string - mController.getKeyguardCallback().showTrustGrantedMessage(""); + mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, ""); // THEN verify NO trust message is shown verifyNoMessage(INDICATION_TYPE_TRUST); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt new file mode 100644 index 000000000000..62b4e7b79f5e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt @@ -0,0 +1,48 @@ +/* + * 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.connectivity + +import android.content.res.Resources +import com.android.settingslib.mobile.MobileIconCarrierIdOverrides + +typealias CarrierId = Int + +typealias NetworkType = String + +typealias ResId = Int + +class MobileIconCarrierIdOverridesFake : MobileIconCarrierIdOverrides { + /** Backing for [carrierIdEntryExists] */ + var overriddenIds = mutableSetOf<Int>() + + /** Backing for [getOverrideFor]. Map should be Map< CarrierId < NetworkType, ResId>> */ + var overridesByCarrierId = mutableMapOf<CarrierId, Map<NetworkType, ResId>>() + + override fun getOverrideFor( + carrierId: CarrierId, + networkType: NetworkType, + resources: Resources + ): ResId { + if (!overriddenIds.contains(carrierId)) return 0 + + return overridesByCarrierId[carrierId]?.get(networkType) ?: 0 + } + + override fun carrierIdEntryExists(carrierId: Int): Boolean { + return overriddenIds.contains(carrierId) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java deleted file mode 100644 index 7ddfde370afa..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.connectivity; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; - -import com.android.settingslib.mobile.TelephonyIcons; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class MobileStateTest extends SysuiTestCase { - - private final MobileState mState = new MobileState(); - - @Before - public void setUp() { - } - - @Test - public void testIsDataDisabledOrNotDefault_dataDisabled() { - mState.iconGroup = TelephonyIcons.DATA_DISABLED; - mState.userSetup = true; - - assertTrue(mState.isDataDisabledOrNotDefault()); - } - - @Test - public void testIsDataDisabledOrNotDefault_notDefaultData() { - mState.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA; - mState.userSetup = true; - - assertTrue(mState.isDataDisabledOrNotDefault()); - } - - @Test - public void testIsDataDisabledOrNotDefault_notDisabled() { - mState.iconGroup = TelephonyIcons.G; - mState.userSetup = true; - - assertFalse(mState.isDataDisabledOrNotDefault()); - } - - @Test - public void testHasActivityIn_noData_noActivity() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityIn = false; - - assertFalse(mState.hasActivityIn()); - } - - @Test - public void testHasActivityIn_noData_activityIn() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityIn = true; - - assertFalse(mState.hasActivityIn()); - } - - @Test - public void testHasActivityIn_dataConnected_activityIn() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = false; - mState.activityIn = true; - - assertTrue(mState.hasActivityIn()); - } - - @Test - public void testHasActivityIn_carrierNetworkChange() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = true; - mState.activityIn = true; - - assertFalse(mState.hasActivityIn()); - } - - @Test - public void testHasActivityOut_noData_noActivity() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityOut = false; - - assertFalse(mState.hasActivityOut()); - } - - @Test - public void testHasActivityOut_noData_activityOut() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityOut = true; - - assertFalse(mState.hasActivityOut()); - } - - @Test - public void testHasActivityOut_dataConnected_activityOut() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = false; - mState.activityOut = true; - - assertTrue(mState.hasActivityOut()); - } - - @Test - public void testHasActivityOut_carrierNetworkChange() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = true; - mState.activityOut = true; - - assertFalse(mState.hasActivityOut()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt new file mode 100644 index 000000000000..a226ded06111 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.connectivity + +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import com.android.settingslib.mobile.TelephonyIcons +import com.android.systemui.SysuiTestCase +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class MobileStateTest : SysuiTestCase() { + + private val state = MobileState() + @Before fun setUp() {} + + @Test + fun testIsDataDisabledOrNotDefault_dataDisabled() { + state.iconGroup = TelephonyIcons.DATA_DISABLED + state.userSetup = true + assertTrue(state.isDataDisabledOrNotDefault) + } + + @Test + fun testIsDataDisabledOrNotDefault_notDefaultData() { + state.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA + state.userSetup = true + assertTrue(state.isDataDisabledOrNotDefault) + } + + @Test + fun testIsDataDisabledOrNotDefault_notDisabled() { + state.iconGroup = TelephonyIcons.G + state.userSetup = true + assertFalse(state.isDataDisabledOrNotDefault) + } + + @Test + fun testHasActivityIn_noData_noActivity() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityIn = false + assertFalse(state.hasActivityIn()) + } + + @Test + fun testHasActivityIn_noData_activityIn() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityIn = true + assertFalse(state.hasActivityIn()) + } + + @Test + fun testHasActivityIn_dataConnected_activityIn() { + state.dataConnected = true + state.carrierNetworkChangeMode = false + state.activityIn = true + assertTrue(state.hasActivityIn()) + } + + @Test + fun testHasActivityIn_carrierNetworkChange() { + state.dataConnected = true + state.carrierNetworkChangeMode = true + state.activityIn = true + assertFalse(state.hasActivityIn()) + } + + @Test + fun testHasActivityOut_noData_noActivity() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityOut = false + assertFalse(state.hasActivityOut()) + } + + @Test + fun testHasActivityOut_noData_activityOut() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityOut = true + assertFalse(state.hasActivityOut()) + } + + @Test + fun testHasActivityOut_dataConnected_activityOut() { + state.dataConnected = true + state.carrierNetworkChangeMode = false + state.activityOut = true + assertTrue(state.hasActivityOut()) + } + + @Test + fun testHasActivityOut_carrierNetworkChange() { + state.dataConnected = true + state.carrierNetworkChangeMode = true + state.activityOut = true + assertFalse(state.hasActivityOut()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index 9c65fac1af45..9c870b5aa363 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -71,6 +71,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.telephony.TelephonyListenerManager; @@ -125,6 +126,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected CarrierConfigTracker mCarrierConfigTracker; protected FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); protected Handler mMainHandler; + // Use a real mobile mappings object since lots of tests rely on it + protected FakeMobileMappingsProxy mMobileMappingsProxy = new FakeMobileMappingsProxy(); protected WifiStatusTrackerFactory mWifiStatusTrackerFactory; protected MobileSignalControllerFactory mMobileFactory; @@ -219,10 +222,13 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mWifiStatusTrackerFactory = new WifiStatusTrackerFactory( mContext, mMockWm, mMockNsm, mMockCm, mMainHandler); + // Most of these tests rely on the actual MobileMappings behavior + mMobileMappingsProxy.setUseRealImpl(true); mMobileFactory = new MobileSignalControllerFactory( mContext, mCallbackHandler, - mCarrierConfigTracker + mCarrierConfigTracker, + mMobileMappingsProxy ); mNetworkController = new NetworkControllerImpl(mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java index 4bed4a19b3d9..1d112262765e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java @@ -18,12 +18,21 @@ package com.android.systemui.statusbar.connectivity; import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; +import static android.telephony.TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED; +import static android.telephony.TelephonyManager.EXTRA_CARRIER_ID; +import static android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID; +import static com.android.settingslib.mobile.TelephonyIcons.NR_5G_PLUS; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.content.Intent; import android.net.NetworkCapabilities; import android.os.Handler; import android.os.Looper; @@ -35,6 +44,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import com.android.settingslib.SignalIcon.MobileIconGroup; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.DataUsageController; import com.android.systemui.dump.DumpManager; @@ -45,6 +55,8 @@ import com.android.systemui.util.CarrierConfigTracker; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.HashMap; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -329,6 +341,57 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { assertFalse(mNetworkController.isMobileDataNetworkInService()); } + @Test + public void mobileSignalController_getsCarrierId() { + when(mMockTm.getSimCarrierId()).thenReturn(1); + setupDefaultSignal(); + + assertEquals(1, mMobileSignalController.getState().getCarrierId()); + } + + @Test + public void mobileSignalController_updatesCarrierId_onChange() { + when(mMockTm.getSimCarrierId()).thenReturn(1); + setupDefaultSignal(); + + // Updates are sent down through this broadcast, we can send the intent directly + Intent intent = new Intent(ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED); + intent.putExtra(EXTRA_SUBSCRIPTION_ID, mSubId); + intent.putExtra(EXTRA_CARRIER_ID, 2); + + mMobileSignalController.handleBroadcast(intent); + + assertEquals(2, mMobileSignalController.getState().getCarrierId()); + } + + @Test + public void networkTypeIcon_hasCarrierIdOverride() { + int fakeCarrier = 1; + int fakeIconOverride = 12345; + int testDataNetType = 100; + String testDataString = "100"; + HashMap<String, MobileIconGroup> testMap = new HashMap<>(); + testMap.put(testDataString, NR_5G_PLUS); + + // Pretend that there is an override for this icon, and this carrier ID + NetworkTypeResIdCache mockCache = mock(NetworkTypeResIdCache.class); + when(mockCache.get(eq(NR_5G_PLUS), eq(fakeCarrier), any())).thenReturn(fakeIconOverride); + + // Turn off the default mobile mapping, so we can override + mMobileMappingsProxy.setUseRealImpl(false); + mMobileMappingsProxy.setIconMap(testMap); + // Use the mocked cache + mMobileSignalController.mCurrentState.setNetworkTypeResIdCache(mockCache); + // Rebuild the network map + mMobileSignalController.setConfiguration(mConfig); + when(mMockTm.getSimCarrierId()).thenReturn(fakeCarrier); + + setupDefaultSignal(); + updateDataConnectionState(TelephonyManager.DATA_CONNECTED, testDataNetType); + + verifyDataIndicators(fakeIconOverride); + } + private void testDataActivity(int direction, boolean in, boolean out) { updateDataActivity(direction); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt new file mode 100644 index 000000000000..9e73487972e8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt @@ -0,0 +1,83 @@ +/* + * 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.connectivity + +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import com.android.settingslib.SignalIcon.MobileIconGroup +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 + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class NetworkTypeResIdCacheTest : SysuiTestCase() { + private lateinit var cache: NetworkTypeResIdCache + private var overrides = MobileIconCarrierIdOverridesFake() + + @Before + fun setUp() { + cache = NetworkTypeResIdCache(overrides) + } + + @Test + fun carrier1_noOverride_usesDefault() { + assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconDefault1) + } + + @Test + fun carrier1_overridden_usesOverride() { + overrides.overriddenIds.add(CARRIER_1) + overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1) + + assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconOverride1) + } + + @Test + fun carrier1_override_carrier2UsesDefault() { + overrides.overriddenIds.add(CARRIER_1) + overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1) + + assertThat(cache.get(group1, CARRIER_2, context)).isEqualTo(iconDefault1) + } + + @Test + fun carrier1_overrideType1_type2UsesDefault() { + overrides.overriddenIds.add(CARRIER_1) + overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1) + + assertThat(cache.get(group2, CARRIER_1, context)).isEqualTo(iconDefault2) + } + + companion object { + // Simplified icon overrides here + const val CARRIER_1 = 1 + const val CARRIER_2 = 2 + + const val NET_TYPE_1 = "one" + const val iconDefault1 = 123 + const val iconOverride1 = 321 + val group1 = MobileIconGroup(NET_TYPE_1, /* dataContentDesc */ 0, iconDefault1) + + const val NET_TYPE_2 = "two" + const val iconDefault2 = 234 + + val group2 = MobileIconGroup(NET_TYPE_2, /* dataContentDesc*/ 0, iconDefault2) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index 57fb976c03ec..bf31eb287579 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -41,6 +41,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.CommandQueue; @@ -60,6 +61,8 @@ import org.mockito.stubbing.Answer; import java.util.Optional; +import dagger.Lazy; + @SmallTest @RunWith(AndroidTestingRunner.class) public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @@ -84,6 +87,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private Vibrator mVibrator; @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; @Mock private SystemBarAttributesListener mSystemBarAttributesListener; + @Mock private Lazy<CameraLauncher> mCameraLauncherLazy; CentralSurfacesCommandQueueCallbacks mSbcqCallbacks; @@ -115,7 +119,8 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { Optional.of(mVibrator), new DisableFlagsLogger(), DEFAULT_DISPLAY, - mSystemBarAttributesListener); + mSystemBarAttributesListener, + mCameraLauncherLazy); when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true); when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt())) 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 5ad1431cc8d8..41912f51db56 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 @@ -112,6 +112,7 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; @@ -288,6 +289,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private InteractionJankMonitor mJankMonitor; @Mock private DeviceStateManager mDeviceStateManager; @Mock private WiredChargingRippleController mWiredChargingRippleController; + @Mock private Lazy<CameraLauncher> mCameraLauncherLazy; + @Mock private CameraLauncher mCameraLauncher; /** * The process of registering/unregistering a predictive back callback requires a * ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test. @@ -380,6 +383,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper); when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController); + when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher); when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent); when(mCentralSurfacesComponent.getNotificationShadeWindowViewController()).thenReturn( @@ -481,7 +485,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mActivityLaunchAnimator, mJankMonitor, mDeviceStateManager, - mWiredChargingRippleController, mDreamManager) { + mWiredChargingRippleController, + mDreamManager, + mCameraLauncherLazy) { @Override protected ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -893,7 +899,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCentralSurfaces.showKeyguardImpl(); // Starting a pulse should change the scrim controller to the pulsing state - when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true); + when(mCameraLauncher.isLaunchingAffordance()).thenReturn(true); mCentralSurfaces.updateScrimController(); verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any()); } @@ -929,7 +935,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCentralSurfaces.showKeyguardImpl(); // Starting a pulse should change the scrim controller to the pulsing state - when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false); + when(mCameraLauncher.isLaunchingAffordance()).thenReturn(false); mCentralSurfaces.updateScrimController(); verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index df48e1d43584..808abc8e9de5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -1437,6 +1437,17 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void behindTint_inKeyguardState_bouncerNotActive_usesKeyguardBehindTint() { + when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false); + mScrimController.setClipsQsScrim(false); + + mScrimController.transitionTo(ScrimState.KEYGUARD); + finishAnimationsImmediately(); + assertThat(mScrimBehind.getTint()) + .isEqualTo(ScrimState.KEYGUARD.getBehindTint()); + } + + @Test public void testNotificationTransparency_followsTransitionToFullShade() { mScrimController.transitionTo(SHADE_LOCKED); mScrimController.setRawPanelExpansionFraction(1.0f); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 49c3a2128bd7..9f70565749df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -222,9 +222,16 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - public void onPanelExpansionChanged_neverHidesScrimmedBouncer() { + public void onPanelExpansionChanged_neverHidesFullscreenBouncer() { when(mPrimaryBouncer.isShowing()).thenReturn(true); - when(mPrimaryBouncer.isScrimmed()).thenReturn(true); + when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( + KeyguardSecurityModel.SecurityMode.SimPuk); + mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); + verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE)); + + reset(mPrimaryBouncer); + when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( + KeyguardSecurityModel.SecurityMode.SimPin); mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE)); } @@ -271,13 +278,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() { - mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animate */); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); - verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f)); - } - - @Test public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() { when(mBiometricUnlockController.getMode()) .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt index 6d8d902615de..a052008d4832 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt @@ -16,31 +16,59 @@ package com.android.systemui.statusbar.pipeline.mobile.util +import android.telephony.TelephonyDisplayInfo import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.MobileMappings.Config import com.android.settingslib.mobile.TelephonyIcons class FakeMobileMappingsProxy : MobileMappingsProxy { + // The old [NetworkControllerDataTest] infra requires us to be able to use the real + // impl sometimes + var useRealImpl = false + + private var realImpl = MobileMappingsProxyImpl() private var iconMap = mapOf<String, MobileIconGroup>() private var defaultIcons = TelephonyIcons.THREE_G fun setIconMap(map: Map<String, MobileIconGroup>) { iconMap = map } - override fun mapIconSets(config: Config): Map<String, MobileIconGroup> = iconMap + override fun mapIconSets(config: Config): Map<String, MobileIconGroup> { + if (useRealImpl) { + return realImpl.mapIconSets(config) + } + return iconMap + } fun getIconMap() = iconMap fun setDefaultIcons(group: MobileIconGroup) { defaultIcons = group } - override fun getDefaultIcons(config: Config): MobileIconGroup = defaultIcons + override fun getDefaultIcons(config: Config): MobileIconGroup { + if (useRealImpl) { + return realImpl.getDefaultIcons(config) + } + return defaultIcons + } + + /** This is only used in the old pipeline, use the real impl always */ + override fun getIconKey(displayInfo: TelephonyDisplayInfo): String { + return realImpl.getIconKey(displayInfo) + } + fun getDefaultIcons(): MobileIconGroup = defaultIcons override fun toIconKey(networkType: Int): String { + if (useRealImpl) { + return realImpl.toIconKeyOverride(networkType) + } return networkType.toString() } override fun toIconKeyOverride(networkType: Int): String { + if (useRealImpl) { + return realImpl.toIconKeyOverride(networkType) + } return toIconKey(networkType) + "_override" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java index 43d0fe9559b6..1eee08c22187 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java @@ -221,4 +221,33 @@ public class BatteryControllerTest extends SysuiTestCase { Assert.assertFalse(mBatteryController.isChargingSourceDock()); } + + @Test + public void batteryStateChanged_healthNotOverheated_outputsFalse() { + Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); + intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD); + + mBatteryController.onReceive(getContext(), intent); + + Assert.assertFalse(mBatteryController.isOverheated()); + } + + @Test + public void batteryStateChanged_healthOverheated_outputsTrue() { + Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); + intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT); + + mBatteryController.onReceive(getContext(), intent); + + Assert.assertTrue(mBatteryController.isOverheated()); + } + + @Test + public void batteryStateChanged_noHealthGiven_outputsFalse() { + Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); + + mBatteryController.onReceive(getContext(), intent); + + Assert.assertFalse(mBatteryController.isOverheated()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt index 8572478589fd..09f0d4a10410 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -119,7 +119,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { ) ) - verify(logger).logViewAddition("Fake Window Title") + verify(logger).logViewAddition("id", "Fake Window Title") } @Test @@ -153,7 +153,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { underTest.displayView(getState()) assertThat(fakeWakeLock.isHeld).isTrue() - underTest.removeView("test reason") + underTest.removeView("id", "test reason") assertThat(fakeWakeLock.isHeld).isFalse() } @@ -263,21 +263,143 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { } @Test + fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() { + underTest.displayView(ViewInfo("First name", id = "id1")) + + verify(windowManager).addView(any(), any()) + + reset(windowManager) + underTest.displayView(ViewInfo("Second name", id = "id2")) + underTest.removeView("id2", "test reason") + + verify(windowManager).removeView(any()) + + fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1) + + assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1") + assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name") + + reset(windowManager) + fakeClock.advanceTime(TIMEOUT_MS + 1) + + verify(windowManager).removeView(any()) + assertThat(underTest.activeViews.size).isEqualTo(0) + } + + @Test + fun multipleViewsWithDifferentIds_oldViewRemoved_recentViewIsDisplayed() { + underTest.displayView(ViewInfo("First name", id = "id1")) + + verify(windowManager).addView(any(), any()) + + reset(windowManager) + underTest.displayView(ViewInfo("Second name", id = "id2")) + underTest.removeView("id1", "test reason") + + verify(windowManager, never()).removeView(any()) + assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2") + assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name") + + fakeClock.advanceTime(TIMEOUT_MS + 1) + + verify(windowManager).removeView(any()) + assertThat(underTest.activeViews.size).isEqualTo(0) + } + + @Test + fun multipleViewsWithDifferentIds_threeDifferentViews_recentActiveViewIsDisplayed() { + underTest.displayView(ViewInfo("First name", id = "id1")) + underTest.displayView(ViewInfo("Second name", id = "id2")) + underTest.displayView(ViewInfo("Third name", id = "id3")) + + verify(windowManager).addView(any(), any()) + + reset(windowManager) + underTest.removeView("id3", "test reason") + + verify(windowManager).removeView(any()) + + fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1) + + assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2") + assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name") + + reset(windowManager) + underTest.removeView("id2", "test reason") + + verify(windowManager).removeView(any()) + + fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1) + + assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1") + assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name") + + reset(windowManager) + fakeClock.advanceTime(TIMEOUT_MS + 1) + + verify(windowManager).removeView(any()) + assertThat(underTest.activeViews.size).isEqualTo(0) + } + + @Test + fun multipleViewsWithDifferentIds_oneViewStateChanged_stackHasRecentState() { + underTest.displayView(ViewInfo("First name", id = "id1")) + underTest.displayView(ViewInfo("New name", id = "id1")) + + verify(windowManager).addView(any(), any()) + + reset(windowManager) + underTest.displayView(ViewInfo("Second name", id = "id2")) + underTest.removeView("id2", "test reason") + + verify(windowManager).removeView(any()) + + fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1) + + assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1") + assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("New name") + assertThat(underTest.activeViews[0].second.name).isEqualTo("New name") + + reset(windowManager) + fakeClock.advanceTime(TIMEOUT_MS + 1) + + verify(windowManager).removeView(any()) + assertThat(underTest.activeViews.size).isEqualTo(0) + } + + @Test + fun multipleViewsWithDifferentIds_viewsTimeouts_noViewLeftToDisplay() { + underTest.displayView(ViewInfo("First name", id = "id1")) + fakeClock.advanceTime(TIMEOUT_MS / 3) + underTest.displayView(ViewInfo("Second name", id = "id2")) + fakeClock.advanceTime(TIMEOUT_MS / 3) + underTest.displayView(ViewInfo("Third name", id = "id3")) + + reset(windowManager) + fakeClock.advanceTime(TIMEOUT_MS + 1) + + verify(windowManager).removeView(any()) + verify(windowManager, never()).addView(any(), any()) + assertThat(underTest.activeViews.size).isEqualTo(0) + } + + @Test fun removeView_viewRemovedAndRemovalLogged() { // First, add the view underTest.displayView(getState()) // Then, remove it val reason = "test reason" - underTest.removeView(reason) + val deviceId = "id" + underTest.removeView(deviceId, reason) verify(windowManager).removeView(any()) - verify(logger).logViewRemoval(reason) + verify(logger).logViewRemoval(deviceId, reason) } @Test fun removeView_noAdd_viewNotRemoved() { - underTest.removeView("reason") + underTest.removeView("id", "reason") verify(windowManager, never()).removeView(any()) } @@ -329,7 +451,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { val name: String, override val windowTitle: String = "Window Title", override val wakeReason: String = "WAKE_REASON", - override val timeoutMs: Int = 1 + override val timeoutMs: Int = 1, + override val id: String = "id", ) : TemporaryViewInfo() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt index d155050ce932..116b8fe62b37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt @@ -44,7 +44,7 @@ class TemporaryViewLoggerTest : SysuiTestCase() { @Test fun logViewAddition_bufferHasLog() { - logger.logViewAddition("Test Window Title") + logger.logViewAddition("test id", "Test Window Title") val stringWriter = StringWriter() buffer.dump(PrintWriter(stringWriter), tailLength = 0) @@ -57,7 +57,8 @@ class TemporaryViewLoggerTest : SysuiTestCase() { @Test fun logViewRemoval_bufferHasTagAndReason() { val reason = "test reason" - logger.logViewRemoval(reason) + val deviceId = "test id" + logger.logViewRemoval(deviceId, reason) val stringWriter = StringWriter() buffer.dump(PrintWriter(stringWriter), tailLength = 0) @@ -65,6 +66,7 @@ class TemporaryViewLoggerTest : SysuiTestCase() { assertThat(actualString).contains(TAG) assertThat(actualString).contains(reason) + assertThat(actualString).contains(deviceId) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt index 8e37aa292240..47c84ab48093 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt @@ -377,6 +377,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() { windowTitle = WINDOW_TITLE, wakeReason = WAKE_REASON, timeoutMs = TIMEOUT, + id = DEVICE_ID, ) } @@ -401,3 +402,4 @@ class ChipbarCoordinatorTest : SysuiTestCase() { private const val TIMEOUT = 10000 private const val WINDOW_TITLE = "Test Chipbar Window Title" private const val WAKE_REASON = "TEST_CHIPBAR_WAKE_REASON" +private const val DEVICE_ID = "id" diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 6f70f0ee0f2b..a798f403c73a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -44,6 +44,9 @@ class FakeKeyguardRepository : KeyguardRepository { private val _isDozing = MutableStateFlow(false) override val isDozing: Flow<Boolean> = _isDozing + private val _isDreaming = MutableStateFlow(false) + override val isDreaming: Flow<Boolean> = _isDreaming + private val _dozeAmount = MutableStateFlow(0f) override val dozeAmount: Flow<Float> = _dozeAmount @@ -54,10 +57,13 @@ class FakeKeyguardRepository : KeyguardRepository { override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState private val _isUdfpsSupported = MutableStateFlow(false) - + private val _isBouncerShowing = MutableStateFlow(false) override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing + private val _isKeyguardGoingAway = MutableStateFlow(false) + override val isKeyguardGoingAway: Flow<Boolean> = _isKeyguardGoingAway + private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE) override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState diff --git a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java index d74c7025ae33..7c9a48431a24 100644 --- a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java +++ b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java @@ -27,6 +27,7 @@ import android.util.TypedXmlSerializer; import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -287,6 +288,14 @@ public class AmbientBrightnessStatsTracker { localDate)) { return lastBrightnessStats; } else { + // It is a new day, and we have available data, so log data. The daily boundary + // might not be right if the user changes timezones but that is fine, since it + // won't be that frequent. + if (lastBrightnessStats != null) { + FrameworkStatsLog.write(FrameworkStatsLog.AMBIENT_BRIGHTNESS_STATS_REPORTED, + lastBrightnessStats.getStats(), + lastBrightnessStats.getBucketBoundaries()); + } AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(localDate, BUCKET_BOUNDARIES_FOR_NEW_STATS); if (userStats.size() == MAX_DAYS_TO_TRACK) { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index e51976b766fd..ef1baf62eecf 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -123,6 +123,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.server.AnimationThread; import com.android.server.DisplayThread; @@ -146,6 +147,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; + /** * Manages attached displays. * <p> @@ -1880,6 +1882,14 @@ public final class DisplayManagerService extends SystemService { if (displayDevice == null) { return; } + if (mLogicalDisplayMapper.getDisplayLocked(displayDevice) + .getDisplayInfoLocked().type == Display.TYPE_INTERNAL) { + FrameworkStatsLog.write(FrameworkStatsLog.BRIGHTNESS_CONFIGURATION_UPDATED, + c.getCurve().first, + c.getCurve().second, + // should not be logged for virtual displays + uniqueId); + } mPersistentDataStore.setBrightnessConfigurationForDisplayLocked(c, displayDevice, userSerial, packageName); } finally { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 69c890d90d03..36bff20e0d54 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -2810,18 +2810,22 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call float appliedPowerFactor = event.isLowPowerModeSet() ? event.powerFactor : -1f; - FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED, - convertToNits(event.initialBrightness), - convertToNits(event.brightness), - event.slowAmbientLux, - event.physicalDisplayId, - event.isShortTermModelActive(), - appliedPowerFactor, - appliedRbcStrength, - appliedHbmMaxNits, - appliedThermalCapNits, - event.automaticBrightnessEnabled, - FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL); + if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null + && mLogicalDisplay.getPrimaryDisplayDeviceLocked() + .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) { + FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED, + convertToNits(event.initialBrightness), + convertToNits(event.brightness), + event.slowAmbientLux, + event.physicalDisplayId, + event.isShortTermModelActive(), + appliedPowerFactor, + appliedRbcStrength, + appliedHbmMaxNits, + appliedThermalCapNits, + event.automaticBrightnessEnabled, + FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL); + } } class BrightnessEvent { diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index f64006c3b7c4..70c9e23c6af8 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -167,12 +167,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, @NonNull Handler handler) { - this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap()); - } - - LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, - @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, - @NonNull Handler handler, DeviceStateToLayoutMap deviceStateToLayoutMap) { mSyncRoot = syncRoot; mPowerManager = context.getSystemService(PowerManager.class); mInteractive = mPowerManager.isInteractive(); @@ -187,7 +181,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mDeviceStatesOnWhichToSleep = toSparseBooleanArray(context.getResources().getIntArray( com.android.internal.R.array.config_deviceStatesOnWhichToSleep)); mDisplayDeviceRepo.addListener(this); - mDeviceStateToLayoutMap = deviceStateToLayoutMap; + mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(); } @Override @@ -375,7 +369,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // the transition is smooth. Plus, on some devices, only one internal displays can be // on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be // temporarily turned off. - resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION); + if (mDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) { + resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION); + } mPendingDeviceState = state; final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState, mInteractive, mBootCompleted); @@ -895,8 +891,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { newDisplay.swapDisplaysLocked(oldDisplay); } - if (displayLayout.isEnabled()) { - setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_ENABLED); + if (!displayLayout.isEnabled()) { + setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED); } } @@ -916,7 +912,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); display.updateLocked(mDisplayDeviceRepo); mLogicalDisplays.put(displayId, display); - setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_DISABLED); + setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED); return display; } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 0ae92b4ee846..32ee21cf1658 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -616,6 +616,10 @@ final class DefaultPermissionGrantPolicy { grantPermissionsToSystemPackage(pm, getDefaultCaptivePortalLoginPackage(), userId, NOTIFICATION_PERMISSIONS); + // Dock Manager + grantPermissionsToSystemPackage(pm, getDefaultDockManagerPackage(), userId, + NOTIFICATION_PERMISSIONS); + // Camera grantPermissionsToSystemPackage(pm, getDefaultSystemHandlerActivityPackage(pm, MediaStore.ACTION_IMAGE_CAPTURE, userId), @@ -932,6 +936,10 @@ final class DefaultPermissionGrantPolicy { return mContext.getString(R.string.config_defaultCaptivePortalLoginPackageName); } + private String getDefaultDockManagerPackage() { + return mContext.getString(R.string.config_defaultDockManagerPackageName); + } + @SafeVarargs private final void grantPermissionToEachSystemPackage(PackageManagerWrapper pm, ArrayList<String> packages, int userId, Set<String>... permissions) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ccab96888e8f..27f8c5c2aa11 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1750,6 +1750,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } prevDc.mClosingApps.remove(this); + prevDc.getDisplayPolicy().removeRelaunchingApp(this); if (prevDc.mFocusedApp == this) { prevDc.setFocusedApp(null); @@ -3969,6 +3970,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void startRelaunching() { if (mPendingRelaunchCount == 0) { mRelaunchStartTime = SystemClock.elapsedRealtime(); + if (mVisibleRequested) { + mDisplayContent.getDisplayPolicy().addRelaunchingApp(this); + } } clearAllDrawn(); @@ -3982,7 +3986,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mPendingRelaunchCount--; if (mPendingRelaunchCount == 0 && !isClientVisible()) { // Don't count if the client won't report drawn. - mRelaunchStartTime = 0; + finishOrAbortReplacingWindow(); } } else { // Update keyguard flags upon finishing relaunch. @@ -4003,7 +4007,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } mPendingRelaunchCount = 0; + finishOrAbortReplacingWindow(); + } + + void finishOrAbortReplacingWindow() { mRelaunchStartTime = 0; + mDisplayContent.getDisplayPolicy().removeRelaunchingApp(this); } /** @@ -5112,6 +5121,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */); } logAppCompatState(); + if (!visible) { + finishOrAbortReplacingWindow(); + } } /** diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index fe691c61a96b..54664f5fbe3d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -27,6 +27,7 @@ import static android.app.ActivityManager.START_FLAG_DEBUG; import static android.app.ActivityManager.START_FLAG_NATIVE_DEBUGGING; import static android.app.ActivityManager.START_FLAG_TRACK_ALLOCATION; import static android.app.ActivityManager.START_TASK_TO_FRONT; +import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.WaitResult.INVALID_DELAY; @@ -2592,6 +2593,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Apply options to prevent pendingOptions be taken when scheduling // activity lifecycle transaction to make sure the override pending app // transition will be applied immediately. + if (activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) { + targetActivity.mPendingRemoteAnimation = + activityOptions.getRemoteAnimationAdapter(); + } targetActivity.applyOptionsAnimation(); if (activityOptions != null && activityOptions.getLaunchCookie() != null) { targetActivity.mLaunchCookie = activityOptions.getLaunchCookie(); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 2688ff757f64..2c289c99d3da 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -282,6 +282,12 @@ public class DisplayPolicy { private final ArraySet<WindowState> mInsetsSourceWindowsExceptIme = new ArraySet<>(); + /** Apps which are controlling the appearance of system bars */ + private final ArraySet<ActivityRecord> mSystemBarColorApps = new ArraySet<>(); + + /** Apps which are relaunching and were controlling the appearance of system bars */ + private final ArraySet<ActivityRecord> mRelaunchingSystemBarColorApps = new ArraySet<>(); + private boolean mIsFreeformWindowOverlappingWithNavBar; private boolean mLastImmersiveMode; @@ -1550,6 +1556,7 @@ public class DisplayPolicy { mStatusBarBackgroundWindows.clear(); mStatusBarColorCheckedBounds.setEmpty(); mStatusBarBackgroundCheckedBounds.setEmpty(); + mSystemBarColorApps.clear(); mAllowLockscreenWhenOn = false; mShowingDream = false; @@ -1626,6 +1633,7 @@ public class DisplayPolicy { win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS, new Rect(win.getFrame()))); mStatusBarColorCheckedBounds.union(sTmpRect); + addSystemBarColorApp(win); } } @@ -1638,6 +1646,7 @@ public class DisplayPolicy { if (isOverlappingWithNavBar) { if (mNavBarColorWindowCandidate == null) { mNavBarColorWindowCandidate = win; + addSystemBarColorApp(win); } if (mNavBarBackgroundWindow == null) { mNavBarBackgroundWindow = win; @@ -1656,9 +1665,11 @@ public class DisplayPolicy { } } else if (win.isDimming()) { if (mStatusBar != null) { - addStatusBarAppearanceRegionsForDimmingWindow( + if (addStatusBarAppearanceRegionsForDimmingWindow( win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS, - mStatusBar.getFrame(), win.getBounds(), win.getFrame()); + mStatusBar.getFrame(), win.getBounds(), win.getFrame())) { + addSystemBarColorApp(win); + } } if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) { mNavBarColorWindowCandidate = win; @@ -1666,18 +1677,21 @@ public class DisplayPolicy { } } - private void addStatusBarAppearanceRegionsForDimmingWindow(int appearance, Rect statusBarFrame, - Rect winBounds, Rect winFrame) { + /** + * Returns true if mStatusBarAppearanceRegionList is changed. + */ + private boolean addStatusBarAppearanceRegionsForDimmingWindow( + int appearance, Rect statusBarFrame, Rect winBounds, Rect winFrame) { if (!sTmpRect.setIntersect(winBounds, statusBarFrame)) { - return; + return false; } if (mStatusBarColorCheckedBounds.contains(sTmpRect)) { - return; + return false; } if (appearance == 0 || !sTmpRect2.setIntersect(winFrame, statusBarFrame)) { mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(winBounds))); mStatusBarColorCheckedBounds.union(sTmpRect); - return; + return true; } // A dimming window can divide status bar into different appearance regions (up to 3). // +---------+-------------+---------+ @@ -1706,6 +1720,14 @@ public class DisplayPolicy { // We don't have vertical status bar yet, so we don't handle the other orientation. } mStatusBarColorCheckedBounds.union(sTmpRect); + return true; + } + + private void addSystemBarColorApp(WindowState win) { + final ActivityRecord app = win.mActivityRecord; + if (app != null) { + mSystemBarColorApps.add(app); + } } /** @@ -2202,6 +2224,25 @@ public class DisplayPolicy { return mDisplayContent.getInsetsPolicy(); } + /** + * Called when an app has started replacing its main window. + */ + void addRelaunchingApp(ActivityRecord app) { + if (mSystemBarColorApps.contains(app)) { + mRelaunchingSystemBarColorApps.add(app); + } + } + + /** + * Called when an app has finished replacing its main window or aborted. + */ + void removeRelaunchingApp(ActivityRecord app) { + final boolean removed = mRelaunchingSystemBarColorApps.remove(app); + if (removed & mRelaunchingSystemBarColorApps.isEmpty()) { + updateSystemBarAttributes(); + } + } + void resetSystemBarAttributes() { mLastDisableFlags = 0; updateSystemBarAttributes(); @@ -2244,6 +2285,11 @@ public class DisplayPolicy { final int displayId = getDisplayId(); final int disableFlags = win.getDisableFlags(); final int opaqueAppearance = updateSystemBarsLw(win, disableFlags); + if (!mRelaunchingSystemBarColorApps.isEmpty()) { + // The appearance of system bars might change while relaunching apps. We don't report + // the intermediate state to system UI. Otherwise, it might trigger redundant effects. + return; + } final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate, mDisplayContent.mInputMethodWindow, mNavigationBarPosition); final boolean isNavbarColorManagedByIme = @@ -2707,6 +2753,14 @@ public class DisplayPolicy { pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState="); pw.println(mTopFullscreenOpaqueWindowState); } + if (!mSystemBarColorApps.isEmpty()) { + pw.print(prefix); pw.print("mSystemBarColorApps="); + pw.println(mSystemBarColorApps); + } + if (!mRelaunchingSystemBarColorApps.isEmpty()) { + pw.print(prefix); pw.print("mRelaunchingSystemBarColorApps="); + pw.println(mRelaunchingSystemBarColorApps); + } if (mNavBarColorWindowCandidate != null) { pw.print(prefix); pw.print("mNavBarColorWindowCandidate="); pw.println(mNavBarColorWindowCandidate); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index ea82417a2389..74a236bd862c 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -501,12 +501,16 @@ final class LetterboxUiController { if (hasVisibleTaskbar(mainWindow)) { cropBounds = new Rect(mActivityRecord.getBounds()); + + // Rounded corners should be displayed above the taskbar. + // It is important to call adjustBoundsForTaskbarUnchecked before offsetTo + // because taskbar bounds are in screen coordinates + adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds); + // Activity bounds are in screen coordinates while (0,0) for activity's surface // control is at the top left corner of an app window so offsetting bounds // accordingly. cropBounds.offsetTo(0, 0); - // Rounded corners should be displayed above the taskbar. - adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds); } transaction diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9c9d751244e6..9db5170897a8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1883,7 +1883,7 @@ public class WindowManagerService extends IWindowManager.Stub // Make this invalid which indicates a null attached frame. outAttachedFrame.set(0, 0, -1, -1); } - outSizeCompatScale[0] = win.getSizeCompatScale(); + outSizeCompatScale[0] = win.getSizeCompatScaleForClient(); } Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index a42cec9c06fb..bf751646da37 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1265,8 +1265,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mGlobalScale = mInvGlobalScale = mSizeCompatScale = 1f; } - float getSizeCompatScale() { - return mSizeCompatScale; + float getSizeCompatScaleForClient() { + // If the size compat scale is because of the size compat bounds, we only scale down its + // coordinates at the server side without letting the client know. + return mToken.hasSizeCompatBounds() ? 1f : mSizeCompatScale; } /** @@ -3863,7 +3865,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP outFrames.attachedFrame.scale(mInvGlobalScale); } } - outFrames.sizeCompatScale = mSizeCompatScale; + + outFrames.sizeCompatScale = getSizeCompatScaleForClient(); // Note: in the cases where the window is tied to an activity, we should not send a // configuration update when the window has requested to be hidden. Doing so can lead to @@ -6021,7 +6024,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final long duration = SystemClock.elapsedRealtime() - mActivityRecord.mRelaunchStartTime; Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms"); - mActivityRecord.mRelaunchStartTime = 0; + mActivityRecord.finishOrAbortReplacingWindow(); } if (mActivityRecord != null && mAttrs.type == TYPE_APPLICATION_STARTING) { mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger() diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index 2094c9345fa2..cc68ba88f76e 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -22,8 +22,6 @@ import static android.view.Display.DEFAULT_DISPLAY_GROUP; import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED; import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED; import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED; -import static com.android.server.display.LogicalDisplay.DISPLAY_PHASE_DISABLED; -import static com.android.server.display.LogicalDisplay.DISPLAY_PHASE_ENABLED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED; @@ -55,8 +53,6 @@ import android.view.DisplayInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.server.display.layout.Layout; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -89,7 +85,6 @@ public class LogicalDisplayMapperTest { @Mock Resources mResourcesMock; @Mock IPowerManager mIPowerManagerMock; @Mock IThermalService mIThermalServiceMock; - @Mock DeviceStateToLayoutMap mDeviceStateToLayoutMapMock; @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor; @@ -135,13 +130,11 @@ public class LogicalDisplayMapperTest { when(mResourcesMock.getIntArray( com.android.internal.R.array.config_deviceStatesOnWhichToSleep)) .thenReturn(new int[]{0}); - when(mDeviceStateToLayoutMapMock.get(-1)).thenReturn(new Layout()); mLooper = new TestLooper(); mHandler = new Handler(mLooper.getLooper()); mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo, - mListenerMock, new DisplayManagerService.SyncRoot(), mHandler, - mDeviceStateToLayoutMapMock); + mListenerMock, new DisplayManagerService.SyncRoot(), mHandler); } @@ -420,58 +413,6 @@ public class LogicalDisplayMapperTest { /* isBootCompleted= */true)); } - @Test - public void testDeviceStateLocked() { - DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, - DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); - DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, - DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); - - Layout layout = new Layout(); - layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true); - layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false); - when(mDeviceStateToLayoutMapMock.get(0)).thenReturn(layout); - - layout = new Layout(); - layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, false, false); - layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, true, true); - when(mDeviceStateToLayoutMapMock.get(1)).thenReturn(layout); - when(mDeviceStateToLayoutMapMock.get(2)).thenReturn(layout); - - LogicalDisplay display1 = add(device1); - assertEquals(info(display1).address, info(device1).address); - assertEquals(DEFAULT_DISPLAY, id(display1)); - - LogicalDisplay display2 = add(device2); - assertEquals(info(display2).address, info(device2).address); - // We can only have one default display - assertEquals(DEFAULT_DISPLAY, id(display1)); - - mLogicalDisplayMapper.setDeviceStateLocked(0, false); - mLooper.moveTimeForward(1000); - mLooper.dispatchAll(); - assertEquals(DISPLAY_PHASE_ENABLED, - mLogicalDisplayMapper.getDisplayLocked(device1).getPhase()); - assertEquals(DISPLAY_PHASE_DISABLED, - mLogicalDisplayMapper.getDisplayLocked(device2).getPhase()); - - mLogicalDisplayMapper.setDeviceStateLocked(1, false); - mLooper.moveTimeForward(1000); - mLooper.dispatchAll(); - assertEquals(DISPLAY_PHASE_DISABLED, - mLogicalDisplayMapper.getDisplayLocked(device1).getPhase()); - assertEquals(DISPLAY_PHASE_ENABLED, - mLogicalDisplayMapper.getDisplayLocked(device2).getPhase()); - - mLogicalDisplayMapper.setDeviceStateLocked(2, false); - mLooper.moveTimeForward(1000); - mLooper.dispatchAll(); - assertEquals(DISPLAY_PHASE_DISABLED, - mLogicalDisplayMapper.getDisplayLocked(device1).getPhase()); - assertEquals(DISPLAY_PHASE_ENABLED, - mLogicalDisplayMapper.getDisplayLocked(device2).getPhase()); - } - ///////////////// // Helper Methods ///////////////// diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 181e81d70dfa..06eea298600c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; @@ -69,6 +70,7 @@ import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.times; import android.annotation.Nullable; import android.app.ActivityManager; @@ -84,6 +86,7 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; +import android.view.InsetsSource; import android.view.InsetsVisibilities; import android.view.WindowManager; @@ -103,6 +106,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.List; /** * Tests for Size Compatibility mode. @@ -2369,6 +2375,48 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testLetterboxDetailsForTaskBar_letterboxNotOverlappingTaskBar() { + mAtm.mDevEnableNonResizableMultiWindow = true; + final int screenHeight = 2200; + final int screenWidth = 1400; + final int taskbarHeight = 200; + setUpDisplaySizeWithApp(screenWidth, screenHeight); + + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); + + // Move first activity to split screen which takes half of the screen. + organizer.mPrimary.setBounds(0, screenHeight / 2, screenWidth, screenHeight); + organizer.putTaskToPrimary(mTask, true); + + final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); + navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight)); + + mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15); + + final WindowState w1 = addWindowToActivity(mActivity); + w1.mAboveInsetsState.addSource(navSource); + + // Prepare unresizable activity with max aspect ratio + prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); + + // Refresh the letterboxes + mActivity.mRootWindowContainer.performSurfacePlacement(); + + final ArgumentCaptor<Rect> cropCapturer = ArgumentCaptor.forClass(Rect.class); + verify(mTransaction, times(2)).setWindowCrop( + eq(w1.getSurfaceControl()), + cropCapturer.capture() + ); + final List<Rect> capturedCrops = cropCapturer.getAllValues(); + + final int expectedHeight = screenHeight / 2 - taskbarHeight; + assertEquals(2, capturedCrops.size()); + assertEquals(expectedHeight, capturedCrops.get(0).bottom); + assertEquals(expectedHeight, capturedCrops.get(1).bottom); + } + + @Test public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() { mAtm.mDevEnableNonResizableMultiWindow = true; setUpDisplaySizeWithApp(2800, 1000); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index b6f86527b747..105fc4c38c13 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -15380,11 +15380,28 @@ public class TelephonyManager { public static final int MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED = 2; /** + * Allow switching mobile data to the non-default SIM if the non-default SIM has better + * availability. + * + * This is used for temporarily allowing data on the non-default data SIM when on-default SIM + * has better availability on DSDS devices, where better availability means strong + * signal/connectivity. + * If this policy is enabled, data will be temporarily enabled on the non-default data SIM, + * including during any voice calls(equivalent to enabling + * {@link #MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL}). + * + * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabled}. + * @hide + */ + public static final int MOBILE_DATA_POLICY_AUTO_DATA_SWITCH = 3; + + /** * @hide */ @IntDef(prefix = { "MOBILE_DATA_POLICY_" }, value = { MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL, MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED, + MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, }) @Retention(RetentionPolicy.SOURCE) public @interface MobileDataPolicy { } diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index 73aff4351785..a834e2bbd0d1 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -468,14 +468,14 @@ public final class DataCallResponse implements Parcelable { final boolean isQosBearerSessionsSame = (mQosBearerSessions == null || other.mQosBearerSessions == null) ? mQosBearerSessions == other.mQosBearerSessions - : mQosBearerSessions.size() == other.mQosBearerSessions.size() - && mQosBearerSessions.containsAll(other.mQosBearerSessions); + : (mQosBearerSessions.size() == other.mQosBearerSessions.size() + && mQosBearerSessions.containsAll(other.mQosBearerSessions)); final boolean isTrafficDescriptorsSame = (mTrafficDescriptors == null || other.mTrafficDescriptors == null) ? mTrafficDescriptors == other.mTrafficDescriptors - : mTrafficDescriptors.size() == other.mTrafficDescriptors.size() - && mTrafficDescriptors.containsAll(other.mTrafficDescriptors); + : (mTrafficDescriptors.size() == other.mTrafficDescriptors.size() + && mTrafficDescriptors.containsAll(other.mTrafficDescriptors)); return mCause == other.mCause && mSuggestedRetryTime == other.mSuggestedRetryTime @@ -504,10 +504,35 @@ public final class DataCallResponse implements Parcelable { @Override public int hashCode() { + // Generate order-independent hashes for lists + int addressesHash = mAddresses.stream() + .map(LinkAddress::hashCode) + .mapToInt(Integer::intValue) + .sum(); + int dnsAddressesHash = mDnsAddresses.stream() + .map(InetAddress::hashCode) + .mapToInt(Integer::intValue) + .sum(); + int gatewayAddressesHash = mGatewayAddresses.stream() + .map(InetAddress::hashCode) + .mapToInt(Integer::intValue) + .sum(); + int pcscfAddressesHash = mPcscfAddresses.stream() + .map(InetAddress::hashCode) + .mapToInt(Integer::intValue) + .sum(); + int qosBearerSessionsHash = mQosBearerSessions.stream() + .map(QosBearerSession::hashCode) + .mapToInt(Integer::intValue) + .sum(); + int trafficDescriptorsHash = mTrafficDescriptors.stream() + .map(TrafficDescriptor::hashCode) + .mapToInt(Integer::intValue) + .sum(); return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType, - mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses, - mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, mDefaultQos, - mQosBearerSessions, mSliceInfo, mTrafficDescriptors); + mInterfaceName, addressesHash, dnsAddressesHash, gatewayAddressesHash, + pcscfAddressesHash, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, + mDefaultQos, qosBearerSessionsHash, mSliceInfo, trafficDescriptorsHash); } @Override @@ -816,8 +841,8 @@ public final class DataCallResponse implements Parcelable { /** * Set pdu session id. * <p/> - * The id must be between 1 and 15 when linked to a pdu session. If no pdu session - * exists for the current data call, the id must be set to {@link PDU_SESSION_ID_NOT_SET}. + * The id must be between 1 and 15 when linked to a pdu session. If no pdu session + * exists for the current data call, the id must be set to {@link #PDU_SESSION_ID_NOT_SET}. * * @param pduSessionId Pdu Session Id of the data call. * @return The same instance of the builder. @@ -858,6 +883,7 @@ public final class DataCallResponse implements Parcelable { */ public @NonNull Builder setQosBearerSessions( @NonNull List<QosBearerSession> qosBearerSessions) { + Objects.requireNonNull(qosBearerSessions); mQosBearerSessions = qosBearerSessions; return this; } @@ -891,6 +917,7 @@ public final class DataCallResponse implements Parcelable { */ public @NonNull Builder setTrafficDescriptors( @NonNull List<TrafficDescriptor> trafficDescriptors) { + Objects.requireNonNull(trafficDescriptors); mTrafficDescriptors = trafficDescriptors; return this; } |