summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Notification.java4
-rw-r--r--core/java/android/hardware/camera2/params/OutputConfiguration.java8
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java165
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java220
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java10
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp14
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h3
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp13
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.h1
-rw-r--r--libs/hwui/tests/unit/SkiaPipelineTests.cpp4
-rw-r--r--native/android/surface_control.cpp4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java23
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java76
-rw-r--r--packages/SystemUI/TEST_MAPPING19
-rw-r--r--packages/SystemUI/res/values/ids.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt80
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt104
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt177
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt171
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt198
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt201
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt326
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt447
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java5
-rw-r--r--services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java36
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java14
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java5
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java12
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java12
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java39
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java8
93 files changed, 2711 insertions, 896 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e82073380394..f320b742a430 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4453,6 +4453,10 @@ public class Notification implements Parcelable
* <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
* a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
* use full screen intents.</p>
+ * <p>
+ * To be launched as a full screen intent, the notification must also be posted to a
+ * channel with importance level set to IMPORTANCE_HIGH or higher.
+ * </p>
*
* @param intent The pending intent to launch.
* @param highPriority Passing true will cause this notification to be sent
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 9e8703779863..90e92dbe2ab0 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -159,8 +159,9 @@ public final class OutputConfiguration implements Parcelable {
*
* <li> For a SurfaceView output surface, the timestamp base is {@link
* #TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED}. The timestamp is overridden with choreographer
- * pulses from the display subsystem for smoother display of camera frames. The timestamp
- * is roughly in the same time base as {@link android.os.SystemClock#uptimeMillis}.</li>
+ * pulses from the display subsystem for smoother display of camera frames when the camera
+ * device runs in fixed frame rate. The timestamp is roughly in the same time base as
+ * {@link android.os.SystemClock#uptimeMillis}.</li>
* <li> For an output surface of MediaRecorder, MediaCodec, or ImageReader with {@link
* android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE} usge flag, the timestamp base is
* {@link #TIMESTAMP_BASE_MONOTONIC}, which is roughly the same time base as
@@ -231,7 +232,8 @@ public final class OutputConfiguration implements Parcelable {
*
* <p>The timestamp of the output images are overridden with choreographer pulses from the
* display subsystem for smoother display of camera frames. An output target of SurfaceView
- * uses this time base by default.</p>
+ * uses this time base by default. Note that the timestamp override is done for fixed camera
+ * frame rate only.</p>
*
* <p>This timestamp base isn't applicable to SurfaceTexture targets. SurfaceTexture's
* {@link android.graphics.SurfaceTexture#updateTexImage updateTexImage} function always
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index f03b7f66cdc8..30c3d50ed8ad 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -19,6 +19,10 @@
by the resources of the app using the Shell library. -->
<bool name="config_enableShellMainThread">false</bool>
+ <!-- Determines whether to register the shell task organizer on init.
+ TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
+ <bool name="config_registerShellTaskOrganizerOnInit">true</bool>
+
<!-- Animation duration for PIP when entering. -->
<integer name="config_pipEnterAnimationDuration">425</integer>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 1c0e6f726fbf..756d80204833 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -20,6 +20,9 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
+
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -42,7 +45,6 @@ import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
-import java.util.function.BiFunction;
/** To run the ActivityEmbedding animations. */
class ActivityEmbeddingAnimationRunner {
@@ -85,7 +87,7 @@ class ActivityEmbeddingAnimationRunner {
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Runnable animationFinishCallback) {
final List<ActivityEmbeddingAnimationAdapter> adapters =
- createAnimationAdapters(info, startTransaction);
+ createAnimationAdapters(info, startTransaction, finishTransaction);
long duration = 0;
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
duration = Math.max(duration, adapter.getDurationHint());
@@ -129,7 +131,8 @@ class ActivityEmbeddingAnimationRunner {
*/
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
boolean isChangeTransition = false;
for (TransitionInfo.Change change : info.getChanges()) {
if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
@@ -145,23 +148,25 @@ class ActivityEmbeddingAnimationRunner {
return createChangeAnimationAdapters(info, startTransaction);
}
if (Transitions.isClosingType(info.getType())) {
- return createCloseAnimationAdapters(info);
+ return createCloseAnimationAdapters(info, startTransaction, finishTransaction);
}
- return createOpenAnimationAdapters(info);
+ return createOpenAnimationAdapters(info, startTransaction, finishTransaction);
}
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
- @NonNull TransitionInfo info) {
- return createOpenCloseAnimationAdapters(info, true /* isOpening */,
- mAnimationSpec::loadOpenAnimation);
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
+ true /* isOpening */, mAnimationSpec::loadOpenAnimation);
}
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
- @NonNull TransitionInfo info) {
- return createOpenCloseAnimationAdapters(info, false /* isOpening */,
- mAnimationSpec::loadCloseAnimation);
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
+ false /* isOpening */, mAnimationSpec::loadCloseAnimation);
}
/**
@@ -170,8 +175,9 @@ class ActivityEmbeddingAnimationRunner {
*/
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
- @NonNull TransitionInfo info, boolean isOpening,
- @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider) {
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction, boolean isOpening,
+ @NonNull AnimationProvider animationProvider) {
// We need to know if the change window is only a partial of the whole animation screen.
// If so, we will need to adjust it to make the whole animation screen looks like one.
final List<TransitionInfo.Change> openingChanges = new ArrayList<>();
@@ -194,7 +200,8 @@ class ActivityEmbeddingAnimationRunner {
final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
for (TransitionInfo.Change change : openingChanges) {
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
- change, animationProvider, openingWholeScreenBounds);
+ info, change, startTransaction, finishTransaction, animationProvider,
+ openingWholeScreenBounds);
if (isOpening) {
adapter.overrideLayer(offsetLayer++);
}
@@ -202,7 +209,8 @@ class ActivityEmbeddingAnimationRunner {
}
for (TransitionInfo.Change change : closingChanges) {
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
- change, animationProvider, closingWholeScreenBounds);
+ info, change, startTransaction, finishTransaction, animationProvider,
+ closingWholeScreenBounds);
if (!isOpening) {
adapter.overrideLayer(offsetLayer++);
}
@@ -213,10 +221,18 @@ class ActivityEmbeddingAnimationRunner {
@NonNull
private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter(
- @NonNull TransitionInfo.Change change,
- @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider,
- @NonNull Rect wholeAnimationBounds) {
- final Animation animation = animationProvider.apply(change, wholeAnimationBounds);
+ @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) {
+ final Animation animation = animationProvider.get(info, change, wholeAnimationBounds);
+ // We may want to show a background color for open/close transition.
+ final int backgroundColor = getTransitionBackgroundColorIfSet(info, change, animation,
+ 0 /* defaultColor */);
+ if (backgroundColor != 0) {
+ addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
+ finishTransaction);
+ }
return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(),
wholeAnimationBounds);
}
@@ -322,4 +338,10 @@ class ActivityEmbeddingAnimationRunner {
return ScreenshotUtils.takeScreenshot(t, screenshotChange.getLeash(),
animationChange.getLeash(), cropBounds, Integer.MAX_VALUE);
}
+
+ /** To provide an {@link Animation} based on the transition infos. */
+ private interface AnimationProvider {
+ Animation get(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
+ @NonNull Rect animationBounds);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index ad0dddf77002..eb6ac7615266 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -17,6 +17,9 @@
package com.android.wm.shell.activityembedding;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
+
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
@@ -33,7 +36,6 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
-import com.android.internal.R;
import com.android.internal.policy.TransitionAnimation;
import com.android.wm.shell.transition.Transitions;
@@ -175,16 +177,20 @@ class ActivityEmbeddingAnimationSpec {
}
@NonNull
- Animation loadOpenAnimation(@NonNull TransitionInfo.Change change,
- @NonNull Rect wholeAnimationBounds) {
+ Animation loadOpenAnimation(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = Transitions.isOpeningType(change.getMode());
final Animation animation;
- // TODO(b/207070762):
- // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit
- // 2. Implement edgeExtension version
- animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? R.anim.task_fragment_open_enter
- : R.anim.task_fragment_open_exit);
+ // TODO(b/207070762): Implement edgeExtension version
+ if (shouldShowBackdrop(info, change)) {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_clear_top_open_enter
+ : com.android.internal.R.anim.task_fragment_clear_top_open_exit);
+ } else {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_open_enter
+ : com.android.internal.R.anim.task_fragment_open_exit);
+ }
// Use the whole animation bounds instead of the change bounds, so that when multiple change
// targets are opening at the same time, the animation applied to each will be the same.
// Otherwise, we may see gap between the activities that are launching together.
@@ -195,16 +201,20 @@ class ActivityEmbeddingAnimationSpec {
}
@NonNull
- Animation loadCloseAnimation(@NonNull TransitionInfo.Change change,
- @NonNull Rect wholeAnimationBounds) {
+ Animation loadCloseAnimation(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = Transitions.isOpeningType(change.getMode());
final Animation animation;
- // TODO(b/207070762):
- // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit
- // 2. Implement edgeExtension version
- animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? R.anim.task_fragment_close_enter
- : R.anim.task_fragment_close_exit);
+ // TODO(b/207070762): Implement edgeExtension version
+ if (shouldShowBackdrop(info, change)) {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_clear_top_close_enter
+ : com.android.internal.R.anim.task_fragment_clear_top_close_exit);
+ } else {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_close_enter
+ : com.android.internal.R.anim.task_fragment_close_exit);
+ }
// Use the whole animation bounds instead of the change bounds, so that when multiple change
// targets are closing at the same time, the animation applied to each will be the same.
// Otherwise, we may see gap between the activities that are finishing together.
@@ -213,4 +223,11 @@ class ActivityEmbeddingAnimationSpec {
animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
return animation;
}
+
+ private boolean shouldShowBackdrop(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change change) {
+ final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE,
+ mTransitionAnimation);
+ return a != null && a.getShowBackdrop();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 80cdd1f79cb5..c25bbbf06dda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -29,6 +29,7 @@ import android.view.WindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ProtoLogController;
+import com.android.wm.shell.R;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -173,6 +174,7 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static ShellTaskOrganizer provideShellTaskOrganizer(
+ Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
CompatUIController compatUI,
@@ -180,6 +182,10 @@ public abstract class WMShellBaseModule {
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor
) {
+ if (!context.getResources().getBoolean(R.bool.config_registerShellTaskOrganizerOnInit)) {
+ // TODO(b/238217847): Force override shell init if registration is disabled
+ shellInit = new ShellInit(mainExecutor);
+ }
return new ShellTaskOrganizer(shellInit, shellCommandHandler, compatUI,
unfoldAnimationController, recentTasksOptional, mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index e784261daa7e..37a50b611039 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -188,14 +188,16 @@ public abstract class WMShellModule {
@ShellMainThread Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ @DynamicOverride DesktopModeController desktopModeController) {
return new CaptionWindowDecorViewModel(
context,
mainHandler,
mainChoreographer,
taskOrganizer,
displayController,
- syncQueue);
+ syncQueue,
+ desktopModeController);
}
//
@@ -318,6 +320,7 @@ public abstract class WMShellModule {
ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
+ PipAnimationController pipAnimationController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -337,11 +340,12 @@ public abstract class WMShellModule {
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(
context, shellInit, shellCommandHandler, shellController,
- displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
- pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
- pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
- windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, oneHandedController, mainExecutor));
+ displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
+ pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
+ phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler,
+ pipTransitionController, windowManagerShellWrapper, taskStackListener,
+ pipParamsChangedForwarder, displayInsetsController, oneHandedController,
+ mainExecutor));
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 9474cfe916f0..99739c457aa6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -177,6 +177,25 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
}
/**
+ * Turn desktop mode on or off
+ * @param active the desired state for desktop mode setting
+ */
+ public void setDesktopModeActive(boolean active) {
+ int value = active ? 1 : 0;
+ Settings.System.putInt(mContext.getContentResolver(), Settings.System.DESKTOP_MODE, value);
+ }
+
+ /**
+ * Returns the windowing mode of the display area with the specified displayId.
+ * @param displayId
+ * @return
+ */
+ public int getDisplayAreaWindowingMode(int displayId) {
+ return mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
+ .configuration.windowConfiguration.getWindowingMode();
+ }
+
+ /**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
private final class SettingsObserver extends ContentObserver {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index b32c3eed2fb4..6728c00af51b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -195,6 +195,17 @@ public class PipAnimationController {
}
/**
+ * Returns true if the PiP window is currently being animated.
+ */
+ public boolean isAnimating() {
+ PipAnimationController.PipTransitionAnimator animator = getCurrentAnimator();
+ if (animator != null && animator.isRunning()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Quietly cancel the animator by removing the listeners first.
*/
static void quietCancel(@NonNull ValueAnimator animator) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 1a52d8c395ba..f170e774739f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -324,19 +324,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return mPipTransitionController;
}
- /**
- * Returns true if the PiP window is currently being animated.
- */
- public boolean isAnimating() {
- // TODO(b/183746978) move this to PipAnimationController, and inject that in PipController
- PipAnimationController.PipTransitionAnimator animator =
- mPipAnimationController.getCurrentAnimator();
- if (animator != null && animator.isRunning()) {
- return true;
- }
- return false;
- }
-
public Rect getCurrentOrAnimatingBounds() {
PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index bc8191d2af46..af47666efa5a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -130,6 +130,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private DisplayController mDisplayController;
private PipInputConsumer mPipInputConsumer;
private WindowManagerShellWrapper mWindowManagerShellWrapper;
+ private PipAnimationController mPipAnimationController;
private PipAppOpsListener mAppOpsListener;
private PipMediaController mMediaController;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
@@ -158,7 +159,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return;
}
// if there is another animation ongoing, wait for it to finish and try again
- if (mPipTaskOrganizer.isAnimating()) {
+ if (mPipAnimationController.isAnimating()) {
mMainExecutor.removeCallbacks(
mMovePipInResponseToKeepClearAreasChangeCallback);
mMainExecutor.executeDelayed(
@@ -368,6 +369,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
+ PipAnimationController pipAnimationController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -392,11 +394,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
return new PipController(context, shellInit, shellCommandHandler, shellController,
- displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
- pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
- pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
- windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, oneHandedController, mainExecutor)
+ displayController, pipAnimationController, pipAppOpsListener,
+ pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper,
+ pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
+ pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
+ taskStackListener, pipParamsChangedForwarder, displayInsetsController,
+ oneHandedController, mainExecutor)
.mImpl;
}
@@ -405,6 +408,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
+ PipAnimationController pipAnimationController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -445,6 +449,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mMediaController = pipMediaController;
mMenuController = phonePipMenuController;
mTouchHandler = pipTouchHandler;
+ mPipAnimationController = pipAnimationController;
mAppOpsListener = pipAppOpsListener;
mOneHandedController = oneHandedController;
mPipTransitionController = pipTransitionController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 4c927b6e84b8..dbb2948de5db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -18,13 +18,11 @@ package com.android.wm.shell.transition;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
-import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_NONE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -43,10 +41,11 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+import static android.window.TransitionInfo.FLAG_FILLS_TASK;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -59,6 +58,10 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -74,7 +77,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.PixelFormat;
@@ -84,7 +86,6 @@ import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.os.Handler;
import android.os.IBinder;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.view.Choreographer;
@@ -122,21 +123,6 @@ import java.util.function.Consumer;
public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private static final int MAX_ANIMATION_DURATION = 3000;
- /**
- * Restrict ability of activities overriding transition animation in a way such that
- * an activity can do it only when the transition happens within a same task.
- *
- * @see android.app.Activity#overridePendingTransition(int, int)
- */
- private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
- "persist.wm.disable_custom_task_animation";
-
- /**
- * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
- */
- static boolean sDisableCustomTaskAnimationProperty =
- SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
-
private final TransactionPool mTransactionPool;
private final DisplayController mDisplayController;
private final Context mContext;
@@ -384,8 +370,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
change.getEndAbsBounds().top - info.getRootOffset().y);
// Seamless display transition doesn't need to animate.
if (isSeamlessDisplayChange) continue;
- if (isTask) {
- // Skip non-tasks since those usually have null bounds.
+ if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
+ && !change.hasFlags(FLAG_FILLS_TASK))) {
+ // Update Task and embedded split window crop bounds, otherwise we may see crop
+ // on previous bounds during the rotation animation.
startTransaction.setWindowCrop(change.getLeash(),
change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
}
@@ -431,22 +419,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
cornerRadius = 0;
}
- if (a.getShowBackdrop()) {
- if (info.getAnimationOptions().getBackgroundColor() != 0) {
- // If available use the background color provided through AnimationOptions
- backgroundColorForTransition =
- info.getAnimationOptions().getBackgroundColor();
- } else if (a.getBackdropColor() != 0) {
- // Otherwise fallback on the background color provided through the animation
- // definition.
- backgroundColorForTransition = a.getBackdropColor();
- } else if (change.getBackgroundColor() != 0) {
- // Otherwise default to the window's background color if provided through
- // the theme as the background color for the animation - the top most window
- // with a valid background color and showBackground set takes precedence.
- backgroundColorForTransition = change.getBackgroundColor();
- }
- }
+ backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
+ backgroundColorForTransition);
boolean delayedEdgeExtension = false;
if (!isTask && a.hasExtension()) {
@@ -668,29 +642,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return edgeExtensionLayer;
}
- private void addBackgroundToTransition(
- @NonNull SurfaceControl rootLeash,
- @ColorInt int color,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction
- ) {
- final Color bgColor = Color.valueOf(color);
- final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
-
- final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder()
- .setName("Animation Background")
- .setParent(rootLeash)
- .setColorLayer()
- .setOpaque(true)
- .build();
-
- startTransaction
- .setLayer(animationBackgroundSurface, Integer.MIN_VALUE)
- .setColor(animationBackgroundSurface, colorArray)
- .show(animationBackgroundSurface);
- finishTransaction.remove(animationBackgroundSurface);
- }
-
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@@ -704,9 +655,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
@Nullable
- private Animation loadAnimation(TransitionInfo info, TransitionInfo.Change change,
- int wallpaperTransit) {
- Animation a = null;
+ private Animation loadAnimation(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change change, int wallpaperTransit) {
+ Animation a;
final int type = info.getType();
final int flags = info.getFlags();
@@ -717,12 +668,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final boolean isTask = change.getTaskInfo() != null;
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final int overrideType = options != null ? options.getType() : ANIM_NONE;
- final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true;
+ final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
final Rect endBounds = Transitions.isClosingType(changeMode)
? mRotator.getEndBoundsInStartRotation(change)
: change.getEndAbsBounds();
- final boolean isDream =
- isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
if (info.isKeyguardGoingAway()) {
a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
@@ -763,87 +712,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// This received a transferred starting window, so don't animate
return null;
} else {
- int animAttr = 0;
- boolean translucent = false;
- if (isDream) {
- if (type == TRANSIT_OPEN) {
- animAttr = enter
- ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
- : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
- } else if (type == TRANSIT_CLOSE) {
- animAttr = enter
- ? 0
- : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
- }
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
- animAttr = enter
- ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
- : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
- animAttr = enter
- ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
- : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
- animAttr = enter
- ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
- : R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
- animAttr = enter
- ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
- : R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
- } else if (type == TRANSIT_OPEN) {
- // We will translucent open animation for translucent activities and tasks. Choose
- // WindowAnimation_activityOpenEnterAnimation and set translucent here, then
- // TransitionAnimation loads appropriate animation later.
- if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
- translucent = true;
- }
- if (isTask && !translucent) {
- animAttr = enter
- ? R.styleable.WindowAnimation_taskOpenEnterAnimation
- : R.styleable.WindowAnimation_taskOpenExitAnimation;
- } else {
- animAttr = enter
- ? R.styleable.WindowAnimation_activityOpenEnterAnimation
- : R.styleable.WindowAnimation_activityOpenExitAnimation;
- }
- } else if (type == TRANSIT_TO_FRONT) {
- animAttr = enter
- ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
- : R.styleable.WindowAnimation_taskToFrontExitAnimation;
- } else if (type == TRANSIT_CLOSE) {
- if (isTask) {
- animAttr = enter
- ? R.styleable.WindowAnimation_taskCloseEnterAnimation
- : R.styleable.WindowAnimation_taskCloseExitAnimation;
- } else {
- if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
- translucent = true;
- }
- animAttr = enter
- ? R.styleable.WindowAnimation_activityCloseEnterAnimation
- : R.styleable.WindowAnimation_activityCloseExitAnimation;
- }
- } else if (type == TRANSIT_TO_BACK) {
- animAttr = enter
- ? R.styleable.WindowAnimation_taskToBackEnterAnimation
- : R.styleable.WindowAnimation_taskToBackExitAnimation;
- }
-
- if (animAttr != 0) {
- if (overrideType == ANIM_FROM_STYLE && canCustomContainer) {
- a = mTransitionAnimation
- .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
- animAttr, translucent);
- } else {
- a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
- }
- }
-
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr,
- transitTypeToString(type),
- enter);
+ a = loadAttributeAnimation(info, change, wallpaperTransit, mTransitionAnimation);
}
if (a != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
new file mode 100644
index 000000000000..efee6f40b53e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -0,0 +1,220 @@
+/*
+ * 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.wm.shell.transition;
+
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
+import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Color;
+import android.os.SystemProperties;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.window.TransitionInfo;
+
+import com.android.internal.R;
+import com.android.internal.policy.TransitionAnimation;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+/** The helper class that provides methods for adding styles to transition animations. */
+public class TransitionAnimationHelper {
+
+ /**
+ * Restrict ability of activities overriding transition animation in a way such that
+ * an activity can do it only when the transition happens within a same task.
+ *
+ * @see android.app.Activity#overridePendingTransition(int, int)
+ */
+ private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
+ "persist.wm.disable_custom_task_animation";
+
+ /**
+ * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
+ */
+ static final boolean sDisableCustomTaskAnimationProperty =
+ SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
+
+ /** Loads the animation that is defined through attribute id for the given transition. */
+ @Nullable
+ public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change change, int wallpaperTransit,
+ @NonNull TransitionAnimation transitionAnimation) {
+ final int type = info.getType();
+ final int changeMode = change.getMode();
+ final int changeFlags = change.getFlags();
+ final boolean enter = Transitions.isOpeningType(changeMode);
+ final boolean isTask = change.getTaskInfo() != null;
+ final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
+ final int overrideType = options != null ? options.getType() : ANIM_NONE;
+ final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
+ final boolean isDream =
+ isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
+ int animAttr = 0;
+ boolean translucent = false;
+ if (isDream) {
+ if (type == TRANSIT_OPEN) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
+ : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
+ } else if (type == TRANSIT_CLOSE) {
+ animAttr = enter
+ ? 0
+ : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
+ }
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+ } else if (type == TRANSIT_OPEN) {
+ // We will translucent open animation for translucent activities and tasks. Choose
+ // WindowAnimation_activityOpenEnterAnimation and set translucent here, then
+ // TransitionAnimation loads appropriate animation later.
+ if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
+ translucent = true;
+ }
+ if (isTask && !translucent) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskOpenEnterAnimation
+ : R.styleable.WindowAnimation_taskOpenExitAnimation;
+ } else {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_activityOpenEnterAnimation
+ : R.styleable.WindowAnimation_activityOpenExitAnimation;
+ }
+ } else if (type == TRANSIT_TO_FRONT) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
+ : R.styleable.WindowAnimation_taskToFrontExitAnimation;
+ } else if (type == TRANSIT_CLOSE) {
+ if (isTask) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskCloseEnterAnimation
+ : R.styleable.WindowAnimation_taskCloseExitAnimation;
+ } else {
+ if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
+ translucent = true;
+ }
+ animAttr = enter
+ ? R.styleable.WindowAnimation_activityCloseEnterAnimation
+ : R.styleable.WindowAnimation_activityCloseExitAnimation;
+ }
+ } else if (type == TRANSIT_TO_BACK) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskToBackEnterAnimation
+ : R.styleable.WindowAnimation_taskToBackExitAnimation;
+ }
+
+ Animation a = null;
+ if (animAttr != 0) {
+ if (overrideType == ANIM_FROM_STYLE && canCustomContainer) {
+ a = transitionAnimation
+ .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
+ animAttr, translucent);
+ } else {
+ a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
+ }
+ }
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr,
+ transitTypeToString(type),
+ enter);
+ return a;
+ }
+
+ /**
+ * Gets the background {@link ColorInt} for the given transition animation if it is set.
+ *
+ * @param defaultColor {@link ColorInt} to return if there is no background color specified by
+ * the given transition animation.
+ */
+ @ColorInt
+ public static int getTransitionBackgroundColorIfSet(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change change, @NonNull Animation a,
+ @ColorInt int defaultColor) {
+ if (!a.getShowBackdrop()) {
+ return defaultColor;
+ }
+ if (info.getAnimationOptions() != null
+ && info.getAnimationOptions().getBackgroundColor() != 0) {
+ // If available use the background color provided through AnimationOptions
+ return info.getAnimationOptions().getBackgroundColor();
+ } else if (a.getBackdropColor() != 0) {
+ // Otherwise fallback on the background color provided through the animation
+ // definition.
+ return a.getBackdropColor();
+ } else if (change.getBackgroundColor() != 0) {
+ // Otherwise default to the window's background color if provided through
+ // the theme as the background color for the animation - the top most window
+ // with a valid background color and showBackground set takes precedence.
+ return change.getBackgroundColor();
+ }
+ return defaultColor;
+ }
+
+ /**
+ * Adds the given {@code backgroundColor} as the background color to the transition animation.
+ */
+ public static void addBackgroundToTransition(@NonNull SurfaceControl rootLeash,
+ @ColorInt int backgroundColor, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ if (backgroundColor == 0) {
+ // No background color.
+ return;
+ }
+ final Color bgColor = Color.valueOf(backgroundColor);
+ final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
+ final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder()
+ .setName("Animation Background")
+ .setParent(rootLeash)
+ .setColorLayer()
+ .setOpaque(true)
+ .build();
+ startTransaction
+ .setLayer(animationBackgroundSurface, Integer.MIN_VALUE)
+ .setColor(animationBackgroundSurface, colorArray)
+ .show(animationBackgroundSurface);
+ finishTransaction.remove(animationBackgroundSurface);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 8b36db9204ac..9e49b51e1504 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -37,6 +37,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;
@@ -54,6 +55,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
private FreeformTaskTransitionStarter mTransitionStarter;
+ private DesktopModeController mDesktopModeController;
public CaptionWindowDecorViewModel(
Context context,
@@ -61,7 +63,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ DesktopModeController desktopModeController) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
@@ -69,6 +72,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mSyncQueue = syncQueue;
+ mDesktopModeController = desktopModeController;
}
@Override
@@ -211,8 +215,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
}
private void handleEventForMove(MotionEvent e) {
- if (mTaskOrganizer.getRunningTaskInfo(mTaskId).getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN) {
+ RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ int windowingMode = mDesktopModeController
+ .getDisplayAreaWindowingMode(taskInfo.displayId);
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
return;
}
switch (e.getActionMasked()) {
@@ -230,8 +236,14 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
+ .stableInsets().top;
mDragResizeCallback.onDragResizeEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ if (e.getRawY(dragPointerIdx) <= statusBarHeight
+ && windowingMode == WINDOWING_MODE_FREEFORM) {
+ mDesktopModeController.setDesktopModeActive(false);
+ }
break;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index a8d3bdcb7c96..1e08f1e55797 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -48,6 +48,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
@@ -85,6 +86,7 @@ public class PipControllerTest extends ShellTestCase {
@Mock private ShellCommandHandler mMockShellCommandHandler;
@Mock private DisplayController mMockDisplayController;
@Mock private PhonePipMenuController mMockPhonePipMenuController;
+ @Mock private PipAnimationController mMockPipAnimationController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
@Mock private PhonePipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
@@ -117,8 +119,8 @@ public class PipControllerTest extends ShellTestCase {
mShellController = spy(new ShellController(mShellInit, mMockShellCommandHandler,
mMockExecutor));
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
- mShellController, mMockDisplayController, mMockPipAppOpsListener,
- mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
+ mShellController, mMockDisplayController, mMockPipAnimationController,
+ mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
@@ -183,8 +185,8 @@ public class PipControllerTest extends ShellTestCase {
ShellInit shellInit = new ShellInit(mMockExecutor);
assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
- mShellController, mMockDisplayController, mMockPipAppOpsListener,
- mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
+ mShellController, mMockDisplayController, mMockPipAnimationController,
+ mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 2aca41e41905..8e350d5012a5 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -53,8 +53,12 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() {
}
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
- // TODO: Figure out why this workaround is needed, see b/13913604
- // In the meantime this matches the behavior of GLRenderer, so it is not a regression
+ // In case the surface was destroyed (e.g. a previous trimMemory call) we
+ // need to recreate it here.
+ if (!isSurfaceReady() && mNativeWindow) {
+ setSurface(mNativeWindow.get(), mSwapBehavior);
+ }
+
EGLint error = 0;
if (!mEglManager.makeCurrent(mEglSurface, &error)) {
return MakeCurrentResult::AlreadyCurrent;
@@ -166,6 +170,9 @@ void SkiaOpenGLPipeline::onStop() {
}
bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) {
+ mNativeWindow = surface;
+ mSwapBehavior = swapBehavior;
+
if (mEglSurface != EGL_NO_SURFACE) {
mEglManager.destroySurface(mEglSurface);
mEglSurface = EGL_NO_SURFACE;
@@ -182,7 +189,8 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh
if (mEglSurface != EGL_NO_SURFACE) {
const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
- mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
+ const bool isPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
+ ALOGE_IF(preserveBuffer != isPreserved, "Unable to match the desired swap behavior.");
return true;
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 186998a01745..a80c613697f2 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -61,7 +61,8 @@ protected:
private:
renderthread::EglManager& mEglManager;
EGLSurface mEglSurface = EGL_NO_SURFACE;
- bool mBufferPreserved = false;
+ sp<ANativeWindow> mNativeWindow;
+ renderthread::SwapBehavior mSwapBehavior = renderthread::SwapBehavior::kSwap_discardBuffer;
};
} /* namespace skiapipeline */
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 905d46e58014..cc2565d88d5e 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -55,7 +55,12 @@ VulkanManager& SkiaVulkanPipeline::vulkanManager() {
}
MakeCurrentResult SkiaVulkanPipeline::makeCurrent() {
- return MakeCurrentResult::AlreadyCurrent;
+ // In case the surface was destroyed (e.g. a previous trimMemory call) we
+ // need to recreate it here.
+ if (!isSurfaceReady() && mNativeWindow) {
+ setSurface(mNativeWindow.get(), SwapBehavior::kSwap_default);
+ }
+ return isContextReady() ? MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed;
}
Frame SkiaVulkanPipeline::getFrame() {
@@ -130,7 +135,11 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() {
void SkiaVulkanPipeline::onStop() {}
-bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) {
+// We can safely ignore the swap behavior because VkManager will always operate
+// in a mode equivalent to EGLManager::SwapBehavior::kBufferAge
+bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapBehavior*/) {
+ mNativeWindow = surface;
+
if (mVkSurface) {
vulkanManager().destroySurface(mVkSurface);
mVkSurface = nullptr;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index ada6af67d4a0..a6e685d08aeb 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -61,6 +61,7 @@ private:
renderthread::VulkanManager& vulkanManager();
renderthread::VulkanSurface* mVkSurface = nullptr;
+ sp<ANativeWindow> mNativeWindow;
};
} /* namespace skiapipeline */
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 60ae6044cd5b..7419f8fd89f1 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -404,7 +404,9 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) {
EXPECT_TRUE(pipeline->isSurfaceReady());
renderThread.destroyRenderingContext();
EXPECT_FALSE(pipeline->isSurfaceReady());
- LOG_ALWAYS_FATAL_IF(pipeline->isSurfaceReady());
+
+ pipeline->makeCurrent();
+ EXPECT_TRUE(pipeline->isSurfaceReady());
}
RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) {
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 1ebdc273931b..795af8a58351 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -300,7 +300,7 @@ void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* aSurfaceTransaction,
auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
for (const auto& [surfaceControl, latchTime, acquireTimeOrFence, presentFence,
- previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) {
+ previousReleaseFence, transformHint, frameEvents, ignore] : surfaceControlStats) {
ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
aSurfaceControlStats[aSurfaceControl].acquireTimeOrFence = acquireTimeOrFence;
aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
@@ -650,7 +650,7 @@ void ASurfaceTransaction_setOnCommit(ASurfaceTransaction* aSurfaceTransaction, v
auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
for (const auto& [surfaceControl, latchTime, acquireTimeOrFence, presentFence,
- previousReleaseFence, transformHint, frameEvents] :
+ previousReleaseFence, transformHint, frameEvents, ignore] :
surfaceControlStats) {
ASurfaceControl* aSurfaceControl =
reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 7927c5d460a4..eb53ea1d44f7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -758,16 +758,23 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
public boolean isBusy() {
- synchronized (mProfileLock) {
- for (LocalBluetoothProfile profile : mProfiles) {
- int status = getProfileConnectionState(profile);
- if (status == BluetoothProfile.STATE_CONNECTING
- || status == BluetoothProfile.STATE_DISCONNECTING) {
- return true;
- }
+ for (CachedBluetoothDevice memberDevice : getMemberDevice()) {
+ if (isBusyState(memberDevice)) {
+ return true;
+ }
+ }
+ return isBusyState(this);
+ }
+
+ private boolean isBusyState(CachedBluetoothDevice device){
+ for (LocalBluetoothProfile profile : device.getProfiles()) {
+ int status = device.getProfileConnectionState(profile);
+ if (status == BluetoothProfile.STATE_CONNECTING
+ || status == BluetoothProfile.STATE_DISCONNECTING) {
+ return true;
}
- return getBondState() == BluetoothDevice.BOND_BONDING;
}
+ return device.getBondState() == BluetoothDevice.BOND_BONDING;
}
private boolean updateProfiles() {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 79e99387b2fa..315ab0aac878 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1069,4 +1069,80 @@ public class CachedBluetoothDeviceTest {
assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
}
+
+ @Test
+ public void isBusy_mainDeviceIsConnecting_returnsBusy() {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
+
+ assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+ assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mCachedDevice.isBusy()).isTrue();
+ }
+
+ @Test
+ public void isBusy_mainDeviceIsBonding_returnsBusy() {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+
+ assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+ assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mCachedDevice.isBusy()).isTrue();
+ }
+
+ @Test
+ public void isBusy_memberDeviceIsConnecting_returnsBusy() {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
+
+ assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+ assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mCachedDevice.isBusy()).isTrue();
+ }
+
+ @Test
+ public void isBusy_memberDeviceIsBonding_returnsBusy() {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+ when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+
+ assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+ assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mCachedDevice.isBusy()).isTrue();
+ }
+
+ @Test
+ public void isBusy_allDevicesAreNotBusy_returnsNotBusy() {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+ assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+ assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mCachedDevice.isBusy()).isFalse();
+ }
}
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 26feaf979b20..cd45b8ea9f61 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -1,25 +1,6 @@
{
// Looking for unit test presubmit configuration?
// This currently lives in ATP config apct/system_ui/unit_test
- "presubmit-large": [
- {
- "name": "PlatformScenarioTests",
- "options": [
- {
- "include-filter": "android.platform.test.scenario.sysui"
- },
- {
- "include-annotation": "android.platform.test.scenario.annotation.Scenario"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- },
- {
- "exclude-annotation": "android.platform.test.annotations.Postsubmit"
- }
- ]
- }
- ],
"presubmit-sysui": [
{
"name": "PlatformScenarioTests",
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 808425435efa..f22e79722e78 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -131,6 +131,9 @@
<!-- For StatusIconContainer to tag its icon views -->
<item type="id" name="status_bar_view_state_tag" />
+ <!-- Status bar -->
+ <item type="id" name="status_bar_dot" />
+
<!-- Default display cutout on the physical top of screen -->
<item type="id" name="display_cutout" />
<item type="id" name="display_cutout_left" />
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 67b683ec643a..2e13903814a5 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -455,6 +455,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
}
+ boolean needToUpdateProviderViews = false;
final String newUniqueId = mDisplayInfo.uniqueId;
if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
mDisplayUniqueId = newUniqueId;
@@ -472,6 +473,37 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
setupDecorations();
return;
}
+
+ if (mScreenDecorHwcLayer != null) {
+ updateHwLayerRoundedCornerDrawable();
+ updateHwLayerRoundedCornerExistAndSize();
+ }
+ needToUpdateProviderViews = true;
+ }
+
+ final float newRatio = getPhysicalPixelDisplaySizeRatio();
+ if (mRoundedCornerResDelegate.getPhysicalPixelDisplaySizeRatio() != newRatio) {
+ mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(newRatio);
+ if (mScreenDecorHwcLayer != null) {
+ updateHwLayerRoundedCornerExistAndSize();
+ }
+ needToUpdateProviderViews = true;
+ }
+
+ if (needToUpdateProviderViews) {
+ updateOverlayProviderViews(null);
+ } else {
+ updateOverlayProviderViews(new Integer[] {
+ mFaceScanningViewId,
+ R.id.display_cutout,
+ R.id.display_cutout_left,
+ R.id.display_cutout_right,
+ R.id.display_cutout_bottom,
+ });
+ }
+
+ if (mScreenDecorHwcLayer != null) {
+ mScreenDecorHwcLayer.onDisplayChanged(newUniqueId);
}
}
};
@@ -1037,8 +1069,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
&& (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {
mRotation = newRotation;
mDisplayMode = newMod;
- mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
- getPhysicalPixelDisplaySizeRatio());
if (mScreenDecorHwcLayer != null) {
mScreenDecorHwcLayer.pendingConfigChange = false;
mScreenDecorHwcLayer.updateRotation(mRotation);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
index 3d5e601f18f5..e342ac2f320d 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
@@ -47,7 +47,8 @@ class IntentCreator {
shareIntent.putExtra(Intent.EXTRA_STREAM, clipData.getItemAt(0).getUri());
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
- shareIntent.putExtra(Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context));
+ shareIntent.putExtra(
+ Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context).toString());
shareIntent.setType("text/plain");
}
Intent chooserIntent = Intent.createChooser(shareIntent, null)
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index 8b4aeefb6ed4..a25286438387 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -78,18 +78,23 @@ class RoundedCornerResDelegate(
reloadMeasures()
}
+ private fun reloadAll(newReloadToken: Int) {
+ if (reloadToken == newReloadToken) {
+ return
+ }
+ reloadToken = newReloadToken
+ reloadRes()
+ reloadMeasures()
+ }
+
fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
if (displayUniqueId != newDisplayUniqueId) {
displayUniqueId = newDisplayUniqueId
newReloadToken ?.let { reloadToken = it }
reloadRes()
reloadMeasures()
- } else if (newReloadToken != null) {
- if (reloadToken == newReloadToken) {
- return
- }
- reloadToken = newReloadToken
- reloadMeasures()
+ } else {
+ newReloadToken?.let { reloadAll(it) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 6f38f4f53b7c..5f96a3b56e27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -285,6 +285,14 @@ class KeyguardUnlockAnimationController @Inject constructor(
var willUnlockWithInWindowLauncherAnimations: Boolean = false
/**
+ * Whether we called [ILauncherUnlockAnimationController.prepareForUnlock], but have not yet
+ * called [ILauncherUnlockAnimationController.playUnlockAnimation]. This is used exclusively for
+ * logging purposes to help track down bugs where the Launcher surface is prepared for unlock
+ * but then never animated.
+ */
+ private var launcherPreparedForUnlock = false
+
+ /**
* Whether we decided in [prepareForInWindowLauncherAnimations] that we are able to and want to
* play the smartspace shared element animation. If true,
* [willUnlockWithInWindowLauncherAnimations] will also always be true since in-window
@@ -376,6 +384,20 @@ class KeyguardUnlockAnimationController @Inject constructor(
}
/**
+ * Logging helper to log the conditions under which we decide to perform the in-window
+ * animations. This is used if we prepare to unlock but then somehow decide later to not play
+ * the animation, which would leave Launcher in a bad state.
+ */
+ private fun logInWindowAnimationConditions() {
+ Log.wtf(TAG, "canPerformInWindowLauncherAnimations expected all of these to be true: ")
+ Log.wtf(TAG, " isNexusLauncherUnderneath: ${isNexusLauncherUnderneath()}")
+ Log.wtf(TAG, " !notificationShadeWindowController.isLaunchingActivity: " +
+ "${!notificationShadeWindowController.isLaunchingActivity}")
+ Log.wtf(TAG, " launcherUnlockController != null: ${launcherUnlockController != null}")
+ Log.wtf(TAG, " !isFoldable(context): ${!isFoldable(context)}")
+ }
+
+ /**
* Called from [KeyguardStateController] to let us know that the keyguard going away state has
* changed.
*/
@@ -384,6 +406,15 @@ class KeyguardUnlockAnimationController @Inject constructor(
!statusBarStateController.leaveOpenOnKeyguardHide()) {
prepareForInWindowLauncherAnimations()
}
+
+ // If the keyguard is no longer going away and we were unlocking with in-window animations,
+ // make sure that we've left the launcher at 100% unlocked. This is a fail-safe to prevent
+ // against "tiny launcher" and similar states where the launcher is left in the prepared to
+ // animate state.
+ if (!keyguardStateController.isKeyguardGoingAway &&
+ willUnlockWithInWindowLauncherAnimations) {
+ launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */)
+ }
}
/**
@@ -437,6 +468,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
lockscreenSmartspaceBounds, /* lockscreenSmartspaceBounds */
selectedPage /* selectedPage */
)
+
+ launcherPreparedForUnlock = true
} catch (e: RemoteException) {
Log.e(TAG, "Remote exception in prepareForInWindowUnlockAnimations.", e)
}
@@ -495,6 +528,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
true,
UNLOCK_ANIMATION_DURATION_MS + CANNED_UNLOCK_START_DELAY,
0 /* startDelay */)
+
+ launcherPreparedForUnlock = false
} else {
// Otherwise, we're swiping in an app and should just fade it in. The swipe gesture
// will translate it until the end of the swipe gesture.
@@ -554,6 +589,12 @@ class KeyguardUnlockAnimationController @Inject constructor(
surfaceBehindEntryAnimator.start()
}
}
+
+ if (launcherPreparedForUnlock && !willUnlockWithInWindowLauncherAnimations) {
+ Log.wtf(TAG, "Launcher is prepared for unlock, so we should have started the " +
+ "in-window animation, however we apparently did not.")
+ logInWindowAnimationConditions()
+ }
}
/**
@@ -569,6 +610,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
CANNED_UNLOCK_START_DELAY /* startDelay */)
+ launcherPreparedForUnlock = false
+
// Now that the Launcher surface (with its smartspace positioned identically to ours) is
// visible, hide our smartspace.
lockscreenSmartspace?.visibility = View.INVISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index f8c6a5791839..8368792b8ae3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -149,32 +149,6 @@ class MediaCarouselController @Inject constructor(
}
}
}
-
- companion object {
- private const val SQUISHINESS_SCALE_START = 0.5
- private const val SQUISHINESS_SCALE_FACTOR = 0.5
- private fun getSquishinessScale(squishinessFraction: Float): Double {
- return SQUISHINESS_SCALE_START + SQUISHINESS_SCALE_FACTOR * squishinessFraction
- }
- }
-
- var squishinessFraction: Float = 1f
- set(value) {
- if (field == value) {
- return
- }
- field = value
-
- val scale = getSquishinessScale(field)
- for (mediaPlayer in MediaPlayerData.players()) {
- mediaPlayer.mediaViewHolder?.let {
- it.player.bottom = it.player.top + (scale * it.player.measuredHeight).toInt()
- } ?: mediaPlayer.recommendationViewHolder?.let {
- it.recommendations.bottom = it.recommendations.top +
- (scale * it.recommendations.measuredHeight).toInt()
- }
- }
- }
private val configListener = object : ConfigurationController.ConfigurationListener {
override fun onDensityOrFontScaleChanged() {
// System font changes should only happen when UMO is offscreen or a flicker may occur
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 30947e839f0a..50a10bc0b15a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -541,10 +541,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
if (!mImeVisible) {
// IME not showing, take all touches
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ return;
}
if (!mView.isImeRenderingNavButtons()) {
// IME showing but not drawing any buttons, take all touches
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ return;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f41b905775e4..18bd6b7b3c32 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -27,7 +27,6 @@ import android.view.View;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostState;
@@ -76,14 +75,13 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QS_PANEL) MediaHost mediaHost,
QSTileRevealController.Factory qsTileRevealControllerFactory,
- DumpManager dumpManager, MediaCarouselController mediaCarouselController,
- MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
+ DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSliderController.Factory brightnessSliderFactory,
FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
- metricsLogger, uiEventLogger, qsLogger, dumpManager, mediaCarouselController);
+ metricsLogger, uiEventLogger, qsLogger, dumpManager);
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index a5c60a417a05..ded466a0cb25 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -32,7 +32,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
@@ -71,7 +70,6 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
private final UiEventLogger mUiEventLogger;
private final QSLogger mQSLogger;
private final DumpManager mDumpManager;
- private final MediaCarouselController mMediaCarouselController;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
protected boolean mShouldUseSplitNotificationShade;
@@ -133,8 +131,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
MetricsLogger metricsLogger,
UiEventLogger uiEventLogger,
QSLogger qsLogger,
- DumpManager dumpManager,
- MediaCarouselController mediaCarouselController
+ DumpManager dumpManager
) {
super(view);
mHost = host;
@@ -147,7 +144,6 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
mDumpManager = dumpManager;
mShouldUseSplitNotificationShade =
LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
- mMediaCarouselController = mediaCarouselController;
}
@Override
@@ -165,7 +161,6 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
public void setSquishinessFraction(float squishinessFraction) {
mView.setSquishinessFraction(squishinessFraction);
- mMediaCarouselController.setSquishinessFraction(squishinessFraction);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index bd75c75faa00..ae6ed2008a77 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -444,7 +444,7 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
mShouldUseSettingsButton.set(false);
mBgHandler.post(() -> {
String settingsButtonText = getSettingsButton();
- final View dialogView = createDialogView();
+ final View dialogView = createDialogView(quickSettingsContext);
mMainHandler.post(() -> {
mDialog = new SystemUIDialog(quickSettingsContext, 0);
mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -469,14 +469,14 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
}
@VisibleForTesting
- View createDialogView() {
+ View createDialogView(Context quickSettingsContext) {
if (mSecurityController.isParentalControlsEnabled()) {
return createParentalControlsDialogView();
}
- return createOrganizationDialogView();
+ return createOrganizationDialogView(quickSettingsContext);
}
- private View createOrganizationDialogView() {
+ private View createOrganizationDialogView(Context quickSettingsContext) {
final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
final CharSequence deviceOwnerOrganization =
@@ -487,7 +487,7 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
final String vpnName = mSecurityController.getPrimaryVpnName();
final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName();
- View dialogView = LayoutInflater.from(mContext)
+ View dialogView = LayoutInflater.from(quickSettingsContext)
.inflate(R.layout.quick_settings_footer_dialog, null, false);
// device management section
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 7ce0ad04bb75..9739974256f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -26,7 +26,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
@@ -56,10 +55,10 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
@Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
Provider<Boolean> usingCollapsedLandscapeMediaProvider,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, MediaCarouselController mediaCarouselController
+ DumpManager dumpManager
) {
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
- uiEventLogger, qsLogger, dumpManager, mediaCarouselController);
+ uiEventLogger, qsLogger, dumpManager);
mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index b5859616f392..ccaab1adaf26 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -30,6 +30,7 @@ import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.VariableDateViewController;
@@ -104,7 +105,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mView.requireViewById(R.id.date_clock)
);
- mIconManager = tintedIconManagerFactory.create(mIconContainer);
+ mIconManager = tintedIconManagerFactory.create(mIconContainer, StatusBarLocation.QS);
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
mColorExtractor = colorExtractor;
mOnColorsChangedListener = (extractor, which) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index fe40d4cbe23a..d3ed47407b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -48,6 +48,7 @@ import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
@@ -261,7 +262,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
batteryMeterViewController.ignoreTunerUpdates()
batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
- iconManager = tintedIconManagerFactory.create(iconContainer)
+ iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
iconManager.setTint(
Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 827d0d0f8444..c35c5c522798 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -22,6 +22,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.Notification;
import android.content.Context;
@@ -59,6 +60,8 @@ import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.util.drawable.DrawableSize;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -86,6 +89,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
public static final int STATE_DOT = 1;
public static final int STATE_HIDDEN = 2;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATE_ICON, STATE_DOT, STATE_HIDDEN})
+ public @interface VisibleState { }
+
private static final String TAG = "StatusBarIconView";
private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
= new FloatProperty<StatusBarIconView>("iconAppearAmount") {
@@ -133,6 +140,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float mDotRadius;
private int mStaticDotRadius;
+ @StatusBarIconView.VisibleState
private int mVisibleState = STATE_ICON;
private float mIconAppearAmount = 1.0f;
private ObjectAnimator mIconAppearAnimator;
@@ -746,11 +754,12 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
}
@Override
- public void setVisibleState(int state) {
+ public void setVisibleState(@StatusBarIconView.VisibleState int state) {
setVisibleState(state, true /* animate */, null /* endRunnable */);
}
- public void setVisibleState(int state, boolean animate) {
+ @Override
+ public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
setVisibleState(state, animate, null);
}
@@ -862,6 +871,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
return mIconAppearAmount;
}
+ @StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 25c6dce96b5c..48c6e273bbb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -59,7 +59,8 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
private ImageView mOut;
private ImageView mMobile, mMobileType, mMobileRoaming;
private View mMobileRoamingSpace;
- private int mVisibleState = -1;
+ @StatusBarIconView.VisibleState
+ private int mVisibleState = STATE_HIDDEN;
private DualToneHandler mDualToneHandler;
private boolean mForceHidden;
@@ -271,7 +272,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
}
@Override
- public void setVisibleState(int state, boolean animate) {
+ public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
if (state == mVisibleState) {
return;
}
@@ -312,6 +313,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
}
@Override
+ @StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index 5aee62e3e89f..f3e74d92fc8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -55,7 +55,8 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
private View mAirplaneSpacer;
private WifiIconState mState;
private String mSlot;
- private int mVisibleState = -1;
+ @StatusBarIconView.VisibleState
+ private int mVisibleState = STATE_HIDDEN;
public static StatusBarWifiView fromContext(Context context, String slot) {
LayoutInflater inflater = LayoutInflater.from(context);
@@ -107,7 +108,7 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
}
@Override
- public void setVisibleState(int state, boolean animate) {
+ public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
if (state == mVisibleState) {
return;
}
@@ -131,6 +132,7 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
}
@Override
+ @StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index d541fae4ed33..1196211bd671 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -22,14 +22,32 @@ public interface StatusIconDisplayable extends DarkReceiver {
String getSlot();
void setStaticDrawableColor(int color);
void setDecorColor(int color);
- default void setVisibleState(int state) {
+
+ /** Sets the visible state that this displayable should be. */
+ default void setVisibleState(@StatusBarIconView.VisibleState int state) {
setVisibleState(state, false);
}
- void setVisibleState(int state, boolean animate);
+
+ /**
+ * Sets the visible state that this displayable should be, and whether the change should
+ * animate.
+ */
+ void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate);
+
+ /** Returns the current visible state of this displayable. */
+ @StatusBarIconView.VisibleState
int getVisibleState();
+
+ /**
+ * Returns true if this icon should be visible if there's space, and false otherwise.
+ *
+ * Note that this doesn't necessarily mean it *will* be visible. It's possible that there are
+ * more icons than space, in which case this icon might just show a dot or might be completely
+ * hidden. {@link #getVisibleState} will return the icon's actual visible status.
+ */
boolean isIconVisible();
+
default boolean isIconBlocked() {
return false;
}
}
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index ce2c9c244696..0026b71a5304 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -352,8 +352,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mDisableStateTracker.startTracking(mCommandQueue, mView.getDisplay().getDisplayId());
if (mTintedIconManager == null) {
- mTintedIconManager =
- mTintedIconManagerFactory.create(mView.findViewById(R.id.statusIcons));
+ mTintedIconManager = mTintedIconManagerFactory.create(
+ mView.findViewById(R.id.statusIcons), StatusBarLocation.KEYGUARD);
mTintedIconManager.setBlockList(getBlockedIcons());
mStatusBarIconController.addIconGroup(mTintedIconManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index bd99713e3a69..d6d021ff2819 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -56,7 +56,6 @@ import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
-import javax.inject.Provider;
public interface StatusBarIconController {
@@ -139,13 +138,15 @@ public interface StatusBarIconController {
public DarkIconManager(
LinearLayout linearLayout,
+ StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
+ location,
statusBarPipelineFlags,
- wifiViewModelProvider,
+ wifiViewModel,
mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
@@ -204,27 +205,28 @@ public interface StatusBarIconController {
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final Provider<WifiViewModel> mWifiViewModelProvider;
+ private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
private final DarkIconDispatcher mDarkIconDispatcher;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModelProvider = wifiViewModelProvider;
+ mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
mDarkIconDispatcher = darkIconDispatcher;
}
- public DarkIconManager create(LinearLayout group) {
+ public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
return new DarkIconManager(
group,
+ location,
mStatusBarPipelineFlags,
- mWifiViewModelProvider,
+ mWifiViewModel,
mMobileContextProvider,
mDarkIconDispatcher);
}
@@ -239,12 +241,14 @@ public interface StatusBarIconController {
public TintedIconManager(
ViewGroup group,
+ StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider) {
super(group,
+ location,
statusBarPipelineFlags,
- wifiViewModelProvider,
+ wifiViewModel,
mobileContextProvider);
}
@@ -278,24 +282,25 @@ public interface StatusBarIconController {
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final Provider<WifiViewModel> mWifiViewModelProvider;
+ private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModelProvider = wifiViewModelProvider;
+ mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
}
- public TintedIconManager create(ViewGroup group) {
+ public TintedIconManager create(ViewGroup group, StatusBarLocation location) {
return new TintedIconManager(
group,
+ location,
mStatusBarPipelineFlags,
- mWifiViewModelProvider,
+ mWifiViewModel,
mMobileContextProvider);
}
}
@@ -306,8 +311,9 @@ public interface StatusBarIconController {
*/
class IconManager implements DemoModeCommandReceiver {
protected final ViewGroup mGroup;
+ private final StatusBarLocation mLocation;
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final Provider<WifiViewModel> mWifiViewModelProvider;
+ private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
protected final Context mContext;
protected final int mIconSize;
@@ -324,12 +330,14 @@ public interface StatusBarIconController {
public IconManager(
ViewGroup group,
+ StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider) {
mGroup = group;
+ mLocation = location;
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModelProvider = wifiViewModelProvider;
+ mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
@@ -446,7 +454,7 @@ public interface StatusBarIconController {
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
return ModernStatusBarWifiView.constructAndBind(
- mContext, slot, mWifiViewModelProvider.get());
+ mContext, slot, mWifiViewModel, mLocation);
}
private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
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 e61794b0243e..9d5392af3127 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -730,7 +730,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private void setDozing(boolean dozing) {
if (mDozing != dozing) {
mDozing = dozing;
- reset(true /* hideBouncerWhenShowing */);
+ if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
+ reset(dozing /* hideBouncerWhenShowing */);
+ }
updateStates();
if (!dozing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt
new file mode 100644
index 000000000000..5ace22695ec3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.phone
+
+/** An enumeration of the different locations that host a status bar. */
+enum class StatusBarLocation {
+ /** Home screen or in-app. */
+ HOME,
+ /** Keyguard (aka lockscreen). */
+ KEYGUARD,
+ /** Quick settings (inside the shade). */
+ QS,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index ce04fb599963..e1215ee95238 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -68,6 +68,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
@@ -250,7 +251,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mStatusBar.restoreHierarchyState(
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
}
- mDarkIconManager = mDarkIconManagerFactory.create(view.findViewById(R.id.statusIcons));
+ mDarkIconManager = mDarkIconManagerFactory.create(
+ view.findViewById(R.id.statusIcons), StatusBarLocation.HOME);
mDarkIconManager.setShouldLog(true);
updateBlockedIcons();
mStatusBarIconController.addIconGroup(mDarkIconManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
new file mode 100644
index 000000000000..118b94c7aa83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared
+
+import android.telephony.TelephonyManager
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * An object storing constants that are used for calculating connectivity icons.
+ *
+ * Stored in a class for logging purposes.
+ */
+@SysUISingleton
+class ConnectivityConstants
+@Inject
+constructor(dumpManager: DumpManager, telephonyManager: TelephonyManager) : Dumpable {
+ init {
+ dumpManager.registerDumpable("$SB_LOGGING_TAG:ConnectivityConstants", this)
+ }
+
+ /** True if this device has the capability for data connections and false otherwise. */
+ val hasDataCapabilities = telephonyManager.isDataCapable
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.apply { println("hasDataCapabilities=$hasDataCapabilities") }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index 88d8a86d39f2..dbb1aa54d8ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -33,6 +33,20 @@ class ConnectivityPipelineLogger @Inject constructor(
) {
/**
* Logs a change in one of the **raw inputs** to the connectivity pipeline.
+ *
+ * Use this method for inputs that don't have any extra information besides their callback name.
+ */
+ fun logInputChange(callbackName: String) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { str1 = callbackName },
+ { "Input: $str1" }
+ )
+ }
+
+ /**
+ * Logs a change in one of the **raw inputs** to the connectivity pipeline.
*/
fun logInputChange(callbackName: String, changeInfo: String?) {
buffer.log(
@@ -128,12 +142,36 @@ class ConnectivityPipelineLogger @Inject constructor(
const val SB_LOGGING_TAG = "SbConnectivity"
/**
+ * Log a change in one of the **inputs** to the connectivity pipeline.
+ */
+ fun Flow<Unit>.logInputChange(
+ logger: ConnectivityPipelineLogger,
+ inputParamName: String,
+ ): Flow<Unit> {
+ return this.onEach { logger.logInputChange(inputParamName) }
+ }
+
+ /**
+ * Log a change in one of the **inputs** to the connectivity pipeline.
+ *
+ * @param prettyPrint an optional function to transform the value into a readable string.
+ * [toString] is used if no custom function is provided.
+ */
+ fun <T> Flow<T>.logInputChange(
+ logger: ConnectivityPipelineLogger,
+ inputParamName: String,
+ prettyPrint: (T) -> String = { it.toString() }
+ ): Flow<T> {
+ return this.onEach {logger.logInputChange(inputParamName, prettyPrint(it)) }
+ }
+
+ /**
* Log a change in one of the **outputs** to the connectivity pipeline.
*
* @param prettyPrint an optional function to transform the value into a readable string.
* [toString] is used if no custom function is provided.
*/
- fun <T : Any> Flow<T>.logOutputChange(
+ fun <T> Flow<T>.logOutputChange(
logger: ConnectivityPipelineLogger,
outputParamName: String,
prettyPrint: (T) -> String = { it.toString() }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 103f3fc21f91..681cf7254ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
import android.annotation.SuppressLint
+import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
@@ -30,51 +31,87 @@ import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
import android.util.Log
import com.android.settingslib.Utils
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
-/**
- * Provides data related to the wifi state.
- */
+/** Provides data related to the wifi state. */
interface WifiRepository {
- /**
- * Observable for the current wifi network.
- */
- val wifiNetwork: Flow<WifiNetworkModel>
-
- /**
- * Observable for the current wifi network activity.
- */
- val wifiActivity: Flow<WifiActivityModel>
+ /** Observable for the current wifi enabled status. */
+ val isWifiEnabled: StateFlow<Boolean>
+
+ /** Observable for the current wifi network. */
+ val wifiNetwork: StateFlow<WifiNetworkModel>
+
+ /** Observable for the current wifi network activity. */
+ val wifiActivity: StateFlow<WifiActivityModel>
}
/** Real implementation of [WifiRepository]. */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@SuppressLint("MissingPermission")
class WifiRepositoryImpl @Inject constructor(
+ broadcastDispatcher: BroadcastDispatcher,
connectivityManager: ConnectivityManager,
logger: ConnectivityPipelineLogger,
@Main mainExecutor: Executor,
@Application scope: CoroutineScope,
wifiManager: WifiManager?,
) : WifiRepository {
- override val wifiNetwork: Flow<WifiNetworkModel> = conflatedCallbackFlow {
+
+ private val wifiStateChangeEvents: Flow<Unit> = broadcastDispatcher.broadcastFlow(
+ IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)
+ )
+ .logInputChange(logger, "WIFI_STATE_CHANGED_ACTION intent")
+
+ private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
+ MutableSharedFlow(extraBufferCapacity = 1)
+
+ override val isWifiEnabled: StateFlow<Boolean> =
+ if (wifiManager == null) {
+ MutableStateFlow(false).asStateFlow()
+ } else {
+ // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
+ // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
+ // have changed.
+ merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
+ .mapLatest { wifiManager.isWifiEnabled }
+ .distinctUntilChanged()
+ .logOutputChange(logger, "enabled")
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = wifiManager.isWifiEnabled
+ )
+ }
+
+ override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
@@ -84,6 +121,8 @@ class WifiRepositoryImpl @Inject constructor(
) {
logger.logOnCapabilitiesChanged(network, networkCapabilities)
+ wifiNetworkChangeEvents.tryEmit(Unit)
+
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
if (wifiInfo?.isPrimary == true) {
val wifiNetworkModel = createWifiNetworkModel(
@@ -104,6 +143,9 @@ class WifiRepositoryImpl @Inject constructor(
override fun onLost(network: Network) {
logger.logOnLost(network)
+
+ wifiNetworkChangeEvents.tryEmit(Unit)
+
val wifi = currentWifi
if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
val newNetworkModel = WifiNetworkModel.Inactive
@@ -132,7 +174,7 @@ class WifiRepositoryImpl @Inject constructor(
initialValue = WIFI_NETWORK_DEFAULT
)
- override val wifiActivity: Flow<WifiActivityModel> =
+ override val wifiActivity: StateFlow<WifiActivityModel> =
if (wifiManager == null) {
Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
flowOf(ACTIVITY_DEFAULT)
@@ -142,13 +184,15 @@ class WifiRepositoryImpl @Inject constructor(
logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
trySend(trafficStateToWifiActivityModel(state))
}
-
- trySend(ACTIVITY_DEFAULT)
wifiManager.registerTrafficStateCallback(mainExecutor, callback)
-
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
}
}
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = ACTIVITY_DEFAULT
+ )
companion object {
val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 952525d243f9..04b17ed2924a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -22,9 +22,10 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlo
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
/**
@@ -38,7 +39,11 @@ class WifiInteractor @Inject constructor(
connectivityRepository: ConnectivityRepository,
wifiRepository: WifiRepository,
) {
- private val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
+ /**
+ * The SSID (service set identifier) of the wifi network. Null if we don't have a network, or
+ * have a network but no valid SSID.
+ */
+ val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
when (info) {
is WifiNetworkModel.Inactive -> null
is WifiNetworkModel.CarrierMerged -> null
@@ -51,17 +56,17 @@ class WifiInteractor @Inject constructor(
}
}
+ /** Our current enabled status. */
+ val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
+
/** Our current wifi network. See [WifiNetworkModel]. */
val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
+ /** Our current wifi activity. See [WifiActivityModel]. */
+ val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
+
/** True if we're configured to force-hide the wifi icon and false otherwise. */
val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
it.contains(ConnectivitySlot.WIFI)
}
-
- /** True if our wifi network has activity in (download), and false otherwise. */
- val hasActivityIn: Flow<Boolean> =
- combine(wifiRepository.wifiActivity, ssid) { activity, ssid ->
- activity.hasActivityIn && ssid != null
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
index a19d1bdd8e62..0eb4b0de9f6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
@@ -41,9 +41,14 @@ class WifiConstants @Inject constructor(
/** True if we should show the activityIn/activityOut icons and false otherwise. */
val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
+ /** True if we should always show the wifi icon when wifi is enabled and false otherwise. */
+ val alwaysShowIconIfEnabled =
+ context.resources.getBoolean(R.bool.config_showWifiIndicatorWhenEnabled)
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.apply {
println("shouldShowActivityConfig=$shouldShowActivityConfig")
+ println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
index 44c04968041e..574610605b4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package com.android.systemui.statusbar.pipeline.wifi.shared.model
-/**
- * Provides information on the current wifi activity.
- */
+/** Provides information on the current wifi activity. */
data class WifiActivityModel(
/** True if the wifi has activity in (download). */
val hasActivityIn: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 4fad3274d12f..273be63eb8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -26,8 +26,15 @@ import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -41,40 +48,111 @@ import kotlinx.coroutines.launch
*/
@OptIn(InternalCoroutinesApi::class)
object WifiViewBinder {
- /** Binds the view to the view-model, continuing to update the former based on the latter. */
+
+ /**
+ * Defines interface for an object that acts as the binding between the view and its view-model.
+ *
+ * Users of the [WifiViewBinder] class should use this to control the binder after it is bound.
+ */
+ interface Binding {
+ /** Returns true if the wifi icon should be visible and false otherwise. */
+ fun getShouldIconBeVisible(): Boolean
+
+ /** Notifies that the visibility state has changed. */
+ fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
+ }
+
+ /**
+ * Binds the view to the appropriate view-model based on the given location. The view will
+ * continue to be updated following updates from the view-model.
+ */
@JvmStatic
fun bind(
view: ViewGroup,
- viewModel: WifiViewModel,
- ) {
+ wifiViewModel: WifiViewModel,
+ location: StatusBarLocation,
+ ): Binding {
+ return when (location) {
+ StatusBarLocation.HOME -> bind(view, wifiViewModel.home)
+ StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard)
+ StatusBarLocation.QS -> bind(view, wifiViewModel.qs)
+ }
+ }
+
+ /** Binds the view to the view-model, continuing to update the former based on the latter. */
+ @JvmStatic
+ private fun bind(
+ view: ViewGroup,
+ viewModel: LocationBasedWifiViewModel,
+ ): Binding {
+ val groupView = view.requireViewById<ViewGroup>(R.id.wifi_group)
val iconView = view.requireViewById<ImageView>(R.id.wifi_signal)
+ val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
+ val activityInView = view.requireViewById<ImageView>(R.id.wifi_in)
+ val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
+ val activityContainerView = view.requireViewById<View>(R.id.inout_container)
view.isVisible = true
iconView.isVisible = true
+ // TODO(b/238425913): We should log this visibility state.
+ @StatusBarIconView.VisibleState
+ val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
+
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- viewModel.wifiIcon.distinctUntilChanged().collect { wifiIcon ->
- // TODO(b/238425913): Right now, if !isVisible, there's just an empty space
- // where the wifi icon would be. We need to pipe isVisible through to
- // [ModernStatusBarWifiView.isIconVisible], which is what actually makes
- // the view GONE.
+ visibilityState.collect { visibilityState ->
+ groupView.isVisible = visibilityState == STATE_ICON
+ dotView.isVisible = visibilityState == STATE_DOT
+ }
+ }
+
+ launch {
+ viewModel.wifiIcon.collect { wifiIcon ->
view.isVisible = wifiIcon != null
- wifiIcon?.let {
- IconViewBinder.bind(wifiIcon, iconView)
- }
+ wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
}
}
launch {
viewModel.tint.collect { tint ->
- iconView.imageTintList = ColorStateList.valueOf(tint)
+ val tintList = ColorStateList.valueOf(tint)
+ iconView.imageTintList = tintList
+ activityInView.imageTintList = tintList
+ activityOutView.imageTintList = tintList
+ dotView.setDecorColor(tint)
+ }
+ }
+
+ launch {
+ viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible ->
+ activityInView.isVisible = visible
+ }
+ }
+
+ launch {
+ viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible ->
+ activityOutView.isVisible = visible
+ }
+ }
+
+ launch {
+ viewModel.isActivityContainerVisible.distinctUntilChanged().collect { visible ->
+ activityContainerView.isVisible = visible
}
}
}
}
- // TODO(b/238425913): Hook up to [viewModel] to render actual changes to the wifi icon.
+ return object : Binding {
+ override fun getShouldIconBeVisible(): Boolean {
+ return viewModel.wifiIcon.value != null
+ }
+
+ override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
+ visibilityState.value = state
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index c14a897fffab..6c616ac7c3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -19,10 +19,14 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.view
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
+import android.view.Gravity
import android.view.LayoutInflater
import com.android.systemui.R
import com.android.systemui.statusbar.BaseStatusBarWifiView
-import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
@@ -36,6 +40,17 @@ class ModernStatusBarWifiView(
) : BaseStatusBarWifiView(context, attrs) {
private lateinit var slot: String
+ private lateinit var binding: WifiViewBinder.Binding
+
+ @StatusBarIconView.VisibleState
+ private var iconVisibleState: Int = STATE_HIDDEN
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ binding.onVisibilityStateChanged(value)
+ }
override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
// TODO(b/238425913)
@@ -51,42 +66,64 @@ class ModernStatusBarWifiView(
// TODO(b/238425913)
}
- override fun setVisibleState(state: Int, animate: Boolean) {
- // TODO(b/238425913)
+ override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
+ iconVisibleState = state
}
+ @StatusBarIconView.VisibleState
override fun getVisibleState(): Int {
- // TODO(b/238425913)
- return STATE_ICON
+ return iconVisibleState
}
override fun isIconVisible(): Boolean {
- // TODO(b/238425913)
- return true
+ return binding.getShouldIconBeVisible()
}
- /** Set the slot name for this view. */
- private fun setSlot(slotName: String) {
- this.slot = slotName
+ private fun initView(
+ slotName: String,
+ wifiViewModel: WifiViewModel,
+ location: StatusBarLocation,
+ ) {
+ slot = slotName
+ initDotView()
+ binding = WifiViewBinder.bind(this, wifiViewModel, location)
+ }
+
+ // Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
+ private fun initDotView() {
+ // TODO(b/238425913): Could we just have this dot view be part of
+ // R.layout.new_status_bar_wifi_group with a dot drawable so we don't need to inflate it
+ // manually? Would that not work with animations?
+ val dotView = StatusBarIconView(mContext, slot, null).also {
+ it.id = R.id.status_bar_dot
+ // Hard-code this view to always be in the DOT state so that whenever it's visible it
+ // will show a dot
+ it.visibleState = STATE_DOT
+ }
+
+ val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
+ val lp = LayoutParams(width, width)
+ lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
+ addView(dotView, lp)
}
companion object {
/**
- * Inflates a new instance of [ModernStatusBarWifiView], binds it to [viewModel], and
+ * Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and
* returns it.
*/
@JvmStatic
fun constructAndBind(
context: Context,
slot: String,
- viewModel: WifiViewModel,
+ wifiViewModel: WifiViewModel,
+ location: StatusBarLocation,
): ModernStatusBarWifiView {
return (
LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
as ModernStatusBarWifiView
).also {
- it.setSlot(slot)
- WifiViewBinder.bind(it, viewModel)
+ it.initView(slot, wifiViewModel, location)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
new file mode 100644
index 000000000000..40f948f9ee6c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * A view model for the wifi icon shown on the "home" page (aka, when the device is unlocked and not
+ * showing the shade, so the user is on the home-screen, or in an app).
+ */
+class HomeWifiViewModel(
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ wifiIcon: StateFlow<Icon.Resource?>,
+ isActivityInViewVisible: Flow<Boolean>,
+ isActivityOutViewVisible: Flow<Boolean>,
+ isActivityContainerVisible: Flow<Boolean>,
+) :
+ LocationBasedWifiViewModel(
+ statusBarPipelineFlags,
+ debugTint = Color.CYAN,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
new file mode 100644
index 000000000000..9642ac42972e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** A view model for the wifi icon shown on keyguard (lockscreen). */
+class KeyguardWifiViewModel(
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ wifiIcon: StateFlow<Icon.Resource?>,
+ isActivityInViewVisible: Flow<Boolean>,
+ isActivityOutViewVisible: Flow<Boolean>,
+ isActivityContainerVisible: Flow<Boolean>,
+) :
+ LocationBasedWifiViewModel(
+ statusBarPipelineFlags,
+ debugTint = Color.MAGENTA,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
new file mode 100644
index 000000000000..e23f8c7e97e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * A view model for a wifi icon in a specific location. This allows us to control parameters that
+ * are location-specific (for example, different tints of the icon in different locations).
+ *
+ * Must be subclassed for each distinct location.
+ */
+abstract class LocationBasedWifiViewModel(
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ debugTint: Int,
+
+ /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
+ val wifiIcon: StateFlow<Icon.Resource?>,
+
+ /** True if the activity in view should be visible. */
+ val isActivityInViewVisible: Flow<Boolean>,
+
+ /** True if the activity out view should be visible. */
+ val isActivityOutViewVisible: Flow<Boolean>,
+
+ /** True if the activity container view should be visible. */
+ val isActivityContainerVisible: Flow<Boolean>,
+) {
+ /** The color that should be used to tint the icon. */
+ val tint: Flow<Int> =
+ flowOf(
+ if (statusBarPipelineFlags.useNewPipelineDebugColoring()) {
+ debugTint
+ } else {
+ DEFAULT_TINT
+ }
+ )
+
+ companion object {
+ /**
+ * A default icon tint.
+ *
+ * TODO(b/238425913): The tint is actually controlled by
+ * [com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager]. We
+ * should use that logic instead of white as a default.
+ */
+ private const val DEFAULT_TINT = Color.WHITE
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
new file mode 100644
index 000000000000..0ddf90e21872
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
+class QsWifiViewModel(
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ wifiIcon: StateFlow<Icon.Resource?>,
+ isActivityInViewVisible: Flow<Boolean>,
+ isActivityOutViewVisible: Flow<Boolean>,
+ isActivityContainerVisible: Flow<Boolean>,
+) :
+ LocationBasedWifiViewModel(
+ statusBarPipelineFlags,
+ debugTint = Color.GREEN,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 3c243ac90831..ebbd77b72014 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.content.Context
-import android.graphics.Color
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
@@ -26,107 +25,185 @@ import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTI
import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
* Models the UI state for the status bar wifi icon.
+ *
+ * This class exposes three view models, one per status bar location:
+ * - [home]
+ * - [keyguard]
+ * - [qs]
+ * In order to get the UI state for the wifi icon, you must use one of those view models (whichever
+ * is correct for your location).
+ *
+ * Internally, this class maintains the current state of the wifi icon and notifies those three
+ * view models of any changes.
*/
-class WifiViewModel @Inject constructor(
- statusBarPipelineFlags: StatusBarPipelineFlags,
- private val constants: WifiConstants,
+@SysUISingleton
+class WifiViewModel
+@Inject
+constructor(
+ connectivityConstants: ConnectivityConstants,
private val context: Context,
- private val logger: ConnectivityPipelineLogger,
- private val interactor: WifiInteractor,
+ logger: ConnectivityPipelineLogger,
+ interactor: WifiInteractor,
+ @Application private val scope: CoroutineScope,
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ wifiConstants: WifiConstants,
) {
/**
- * The drawable resource ID to use for the wifi icon. Null if we shouldn't display any icon.
+ * Returns the drawable resource ID to use for the wifi icon based on the given network.
+ * Null if we can't compute the icon.
*/
@DrawableRes
- private val iconResId: Flow<Int?> = interactor.wifiNetwork.map {
- when (it) {
+ private fun WifiNetworkModel.iconResId(): Int? {
+ return when (this) {
is WifiNetworkModel.CarrierMerged -> null
is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
is WifiNetworkModel.Active ->
when {
- it.level == null -> null
- it.isValidated -> WIFI_FULL_ICONS[it.level]
- else -> WIFI_NO_INTERNET_ICONS[it.level]
+ this.level == null -> null
+ this.isValidated -> WIFI_FULL_ICONS[this.level]
+ else -> WIFI_NO_INTERNET_ICONS[this.level]
}
}
}
- /** The content description for the wifi icon. */
- private val contentDescription: Flow<ContentDescription?> = interactor.wifiNetwork.map {
- when (it) {
+ /**
+ * Returns the content description for the wifi icon based on the given network.
+ * Null if we can't compute the content description.
+ */
+ private fun WifiNetworkModel.contentDescription(): ContentDescription? {
+ return when (this) {
is WifiNetworkModel.CarrierMerged -> null
is WifiNetworkModel.Inactive ->
ContentDescription.Loaded(
"${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
)
is WifiNetworkModel.Active ->
- when (it.level) {
+ when (this.level) {
null -> null
else -> {
- val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[it.level])
+ val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
when {
- it.isValidated -> ContentDescription.Loaded(levelDesc)
- else -> ContentDescription.Loaded(
- "$levelDesc,${context.getString(NO_INTERNET)}"
- )
+ this.isValidated -> ContentDescription.Loaded(levelDesc)
+ else ->
+ ContentDescription.Loaded(
+ "$levelDesc,${context.getString(NO_INTERNET)}"
+ )
}
}
}
}
}
- /**
- * The wifi icon that should be displayed. Null if we shouldn't display any icon.
- */
- val wifiIcon: Flow<Icon?> = combine(
+ /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
+ private val wifiIcon: StateFlow<Icon.Resource?> =
+ combine(
+ interactor.isEnabled,
interactor.isForceHidden,
- iconResId,
- contentDescription,
- ) { isForceHidden, iconResId, contentDescription ->
- when {
- isForceHidden ||
- iconResId == null ||
- iconResId <= 0 -> null
- else -> Icon.Resource(iconResId, contentDescription)
+ interactor.wifiNetwork,
+ ) { isEnabled, isForceHidden, wifiNetwork ->
+ if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
+ return@combine null
+ }
+
+ val iconResId = wifiNetwork.iconResId() ?: return@combine null
+ val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
+
+ return@combine when {
+ wifiConstants.alwaysShowIconIfEnabled -> icon
+ !connectivityConstants.hasDataCapabilities -> icon
+ wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
+ else -> null
}
}
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
- /**
- * True if the activity in icon should be displayed and false otherwise.
- */
- val isActivityInVisible: Flow<Boolean>
- get() =
- if (!constants.shouldShowActivityConfig) {
- flowOf(false)
- } else {
- interactor.hasActivityIn
+ /** The wifi activity status. Null if we shouldn't display the activity status. */
+ private val activity: Flow<WifiActivityModel?> =
+ if (!wifiConstants.shouldShowActivityConfig) {
+ flowOf(null)
+ } else {
+ combine(interactor.activity, interactor.ssid) { activity, ssid ->
+ when (ssid) {
+ null -> null
+ else -> activity
+ }
}
- .logOutputChange(logger, "activityInVisible")
+ }
+ .distinctUntilChanged()
+ .logOutputChange(logger, "activity")
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
- /** The tint that should be applied to the icon. */
- val tint: Flow<Int> = if (!statusBarPipelineFlags.useNewPipelineDebugColoring()) {
- emptyFlow()
- } else {
- flowOf(Color.CYAN)
- }
+ private val isActivityInViewVisible: Flow<Boolean> =
+ activity
+ .map { it?.hasActivityIn == true }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+ private val isActivityOutViewVisible: Flow<Boolean> =
+ activity
+ .map { it?.hasActivityOut == true }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+ private val isActivityContainerVisible: Flow<Boolean> =
+ combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut ->
+ activityIn || activityOut
+ }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+ /** A view model for the status bar on the home screen. */
+ val home: HomeWifiViewModel =
+ HomeWifiViewModel(
+ statusBarPipelineFlags,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
+
+ /** A view model for the status bar on keyguard. */
+ val keyguard: KeyguardWifiViewModel =
+ KeyguardWifiViewModel(
+ statusBarPipelineFlags,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
+
+ /** A view model for the status bar in quick settings. */
+ val qs: QsWifiViewModel =
+ QsWifiViewModel(
+ statusBarPipelineFlags,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
companion object {
@StringRes
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 5a26d05d7b37..df10dfe9f160 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -1005,13 +1005,18 @@ public class ScreenDecorationsTest extends SysuiTestCase {
assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize());
assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize());
- doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
+ setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
+ /* roundedTopDrawable */,
+ getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px)
+ /* roundedBottomDrawable */,
+ 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/);
mDisplayInfo.rotation = Surface.ROTATION_270;
mScreenDecorations.onConfigurationChanged(null);
- assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
- assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
+ assertEquals(new Size(4, 4), resDelegate.getTopRoundedSize());
+ assertEquals(new Size(5, 5), resDelegate.getBottomRoundedSize());
}
@Test
@@ -1288,6 +1293,51 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@Test
+ public void testOnDisplayChanged_hwcLayer() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+ 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
+ final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
+ decorationSupport.format = PixelFormat.R_8;
+ doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
+
+ // top cutout
+ mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
+
+ mScreenDecorations.start();
+
+ final ScreenDecorHwcLayer hwcLayer = mScreenDecorations.mScreenDecorHwcLayer;
+ spyOn(hwcLayer);
+ doReturn(mDisplay).when(hwcLayer).getDisplay();
+
+ mScreenDecorations.mDisplayListener.onDisplayChanged(1);
+
+ verify(hwcLayer, times(1)).onDisplayChanged(any());
+ }
+
+ @Test
+ public void testOnDisplayChanged_nonHwcLayer() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+ 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
+
+ // top cutout
+ mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
+
+ mScreenDecorations.start();
+
+ final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView)
+ mScreenDecorations.getOverlayView(R.id.display_cutout);
+ assertNotNull(cutoutView);
+ spyOn(cutoutView);
+ doReturn(mDisplay).when(cutoutView).getDisplay();
+
+ mScreenDecorations.mDisplayListener.onDisplayChanged(1);
+
+ verify(cutoutView, times(1)).onDisplayChanged(any());
+ }
+
+ @Test
public void testHasSameProvidersWithNullOverlays() {
setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
index 08fe7c486529..2a4c0eb18d02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
@@ -16,13 +16,14 @@
package com.android.systemui.clipboardoverlay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import android.content.ClipData;
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import android.text.SpannableString;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -129,6 +130,18 @@ public class IntentCreatorTest extends SysuiTestCase {
assertEquals("image/png", target.getType());
}
+ @Test
+ public void test_getShareIntent_spannableText() {
+ ClipData clipData = ClipData.newPlainText("Test", new SpannableString("Test Item"));
+ Intent intent = IntentCreator.getShareIntent(clipData, getContext());
+
+ assertEquals(Intent.ACTION_CHOOSER, intent.getAction());
+ assertFlags(intent, EXTERNAL_INTENT_FLAGS);
+ Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
+ assertEquals("Test Item", target.getStringExtra(Intent.EXTRA_TEXT));
+ assertEquals("text/plain", target.getType());
+ }
+
// Assert that the given flags are set
private void assertFlags(Intent intent, int flags) {
assertTrue((intent.getFlags() & flags) == flags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
index 93a1868b72f5..f93336134900 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
@@ -24,11 +24,12 @@ import androidx.annotation.DrawableRes
import androidx.test.filters.SmallTest
import com.android.internal.R as InternalR
import com.android.systemui.R as SystemUIR
-import com.android.systemui.SysuiTestCase
import com.android.systemui.tests.R
+import com.android.systemui.SysuiTestCase
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
+
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@@ -101,11 +102,14 @@ class RoundedCornerResDelegateTest : SysuiTestCase() {
assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
- roundedCornerResDelegate.physicalPixelDisplaySizeRatio = 2f
+ setupResources(radius = 100,
+ roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px),
+ roundedBottomDrawable = getTestsDrawable(R.drawable.rounded5px))
+
roundedCornerResDelegate.updateDisplayUniqueId(null, 1)
- assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize)
- assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize)
+ assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize)
+ assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index b42b7695cedd..8b1554c1f66f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -48,6 +48,7 @@ import android.view.WindowManagerPolicyConstants;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
@@ -234,6 +235,11 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(any());
}
+ /**
+ * This specific test case appears to be flaky.
+ * b/249136797 tracks the task of root-causing and fixing it.
+ */
+ @FlakyTest
@Test
public void testPredictiveBackInvocationDismissesDialog() {
mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index e3e3b7413157..5ad354247a04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -30,7 +30,6 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
@@ -46,7 +45,6 @@ import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -73,10 +71,6 @@ class MediaCarouselControllerTest : SysuiTestCase() {
@Mock lateinit var dumpManager: DumpManager
@Mock lateinit var logger: MediaUiEventLogger
@Mock lateinit var debugLogger: MediaCarouselControllerLogger
- @Mock lateinit var mediaViewHolder: MediaViewHolder
- @Mock lateinit var player: TransitionLayout
- @Mock lateinit var recommendationViewHolder: RecommendationViewHolder
- @Mock lateinit var recommendations: TransitionLayout
@Mock lateinit var mediaPlayer: MediaControlPanel
@Mock lateinit var mediaViewController: MediaViewController
@Mock lateinit var smartspaceMediaData: SmartspaceMediaData
@@ -282,46 +276,6 @@ class MediaCarouselControllerTest : SysuiTestCase() {
verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
}
- @Test
- fun testSetSquishinessFractionForMedia_setPlayerBottom() {
- whenever(panel.mediaViewHolder).thenReturn(mediaViewHolder)
- whenever(mediaViewHolder.player).thenReturn(player)
- whenever(player.measuredHeight).thenReturn(100)
-
- val playingLocal = Triple("playing local",
- DATA.copy(active = true, isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false),
- 4500L)
- MediaPlayerData.addMediaPlayer(playingLocal.first, playingLocal.second, panel, clock,
- false, debugLogger)
-
- mediaCarouselController.squishinessFraction = 0.0f
- verify(player).bottom = 50
- verifyNoMoreInteractions(recommendationViewHolder)
-
- mediaCarouselController.squishinessFraction = 0.5f
- verify(player).bottom = 75
- verifyNoMoreInteractions(recommendationViewHolder)
- }
-
- @Test
- fun testSetSquishinessFractionForRecommendation_setPlayerBottom() {
- whenever(panel.recommendationViewHolder).thenReturn(recommendationViewHolder)
- whenever(recommendationViewHolder.recommendations).thenReturn(recommendations)
- whenever(recommendations.measuredHeight).thenReturn(100)
-
- MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
- false, clock)
-
- mediaCarouselController.squishinessFraction = 0.0f
- verifyNoMoreInteractions(mediaViewHolder)
- verify(recommendationViewHolder.recommendations).bottom = 50
-
- mediaCarouselController.squishinessFraction = 0.5f
- verifyNoMoreInteractions(mediaViewHolder)
- verify(recommendationViewHolder.recommendations).bottom = 75
- }
-
fun testMediaLoaded_ScrollToActivePlayer() {
listener.value.onMediaDataLoaded("playing local",
null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index cbe118635e95..3cad2a005882 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -44,7 +44,6 @@ import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
@@ -87,7 +86,6 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
@Mock
private QSLogger mQSLogger;
private DumpManager mDumpManager = new DumpManager();
- private MediaCarouselController mMediaCarouselController;
@Mock
QSTileImpl mQSTile;
@Mock
@@ -110,9 +108,9 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
QSCustomizerController qsCustomizerController, MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, MediaCarouselController mediaCarouselController) {
+ DumpManager dumpManager) {
super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
- qsLogger, dumpManager, mediaCarouselController);
+ qsLogger, dumpManager);
}
@Override
@@ -146,7 +144,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController);
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
reset(mQSTileRevealController);
@@ -158,7 +156,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
mQSTileHost, mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController) {
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
@Override
protected QSTileRevealController createTileRevealController() {
return mQSTileRevealController;
@@ -253,7 +251,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
when(mQSPanel.getDumpableTag()).thenReturn("QSPanelLandscape");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController);
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
@@ -262,7 +260,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
when(mQSPanel.getDumpableTag()).thenReturn("QSPanelPortrait");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController);
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
assertThat(mController.shouldUseHorizontalLayout()).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 98d499a70fa7..5eb9a9862340 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -6,7 +6,6 @@ import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaCarouselController
import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.FalsingManager
@@ -41,7 +40,6 @@ class QSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var qsCustomizerController: QSCustomizerController
@Mock private lateinit var qsTileRevealControllerFactory: QSTileRevealController.Factory
@Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var mediaCarouselController: MediaCarouselController
@Mock private lateinit var metricsLogger: MetricsLogger
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var qsLogger: QSLogger
@@ -78,7 +76,6 @@ class QSPanelControllerTest : SysuiTestCase() {
mediaHost,
qsTileRevealControllerFactory,
dumpManager,
- mediaCarouselController,
metricsLogger,
uiEventLogger,
qsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 233c267c3be0..1c686c66e31e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -726,7 +726,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isParentalControlsEnabled()).thenReturn(true);
when(mSecurityController.getLabel(any())).thenReturn(PARENTAL_CONTROLS_LABEL);
- View view = mFooterUtils.createDialogView();
+ View view = mFooterUtils.createDialogView(getContext());
TextView textView = (TextView) view.findViewById(R.id.parental_controls_title);
assertEquals(PARENTAL_CONTROLS_LABEL, textView.getText());
}
@@ -749,7 +749,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_FINANCED);
- View view = mFooterUtils.createDialogView();
+ View view = mFooterUtils.createDialogView(getContext());
TextView managementSubtitle = view.findViewById(R.id.device_management_subtitle);
assertEquals(View.VISIBLE, managementSubtitle.getVisibility());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 4af5b9018d5a..6af8e4904a1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -23,7 +23,6 @@ import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaCarouselController
import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.qs.QSTile
@@ -60,7 +59,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var tileLayout: TileLayout
@Mock private lateinit var tileView: QSTileView
@Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
- @Mock private lateinit var mediaCarouselController: MediaCarouselController
private val uiEventLogger = UiEventLoggerFake()
private val dumpManager = DumpManager()
@@ -90,8 +88,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager,
- mediaCarouselController)
+ dumpManager)
controller.init()
}
@@ -158,8 +155,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
metricsLogger: MetricsLogger,
uiEventLogger: UiEventLoggerFake,
qsLogger: QSLogger,
- dumpManager: DumpManager,
- mediaCarouselController: MediaCarouselController
+ dumpManager: DumpManager
) :
QuickQSPanelController(
view,
@@ -171,8 +167,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager,
- mediaCarouselController) {
+ dumpManager) {
private var rotation = RotationUtils.ROTATION_NONE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index eb907bd92471..39d89bf99af2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -110,7 +110,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
`when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
`when`(variableDateViewControllerFactory.create(any()))
.thenReturn(variableDateViewController)
- `when`(iconManagerFactory.create(any())).thenReturn(iconManager)
+ `when`(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
`when`(view.resources).thenReturn(mContext.resources)
`when`(view.isAttachedToWindow).thenReturn(true)
`when`(view.context).thenReturn(context)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index c4485389d646..c76d9e7a2b20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -176,7 +176,7 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
}
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
- whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
+ whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 5ecfc8eb3649..90ae693db955 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -97,7 +97,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
whenever(variableDateViewControllerFactory.create(any()))
.thenReturn(variableDateViewController)
- whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
+ whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
view,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index ba5f5038c1d9..cfaa4707ef76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -135,7 +135,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
- when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
+ when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
allowTestableLooperAsMainThread();
TestableLooper.get(this).runWithLooper(() -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index de7db74495af..34399b80c9f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -51,8 +51,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import javax.inject.Provider;
-
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
@@ -79,8 +77,9 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
LinearLayout layout = new LinearLayout(mContext);
TestDarkIconManager manager = new TestDarkIconManager(
layout,
+ StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
- () -> mock(WifiViewModel.class),
+ mock(WifiViewModel.class),
mMobileContextProvider,
mock(DarkIconDispatcher.class));
testCallOnAdd_forManager(manager);
@@ -121,13 +120,15 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
TestDarkIconManager(
LinearLayout group,
+ StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider contextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(group,
+ location,
statusBarPipelineFlags,
- wifiViewModelProvider,
+ wifiViewModel,
contextProvider,
darkIconDispatcher);
}
@@ -165,8 +166,9 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
private static class TestIconManager extends IconManager implements TestableIconManager {
TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
super(group,
+ StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
- () -> mock(WifiViewModel.class),
+ mock(WifiViewModel.class),
contextProvider);
}
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 ee4b9d9c93f2..dcce61b86ced 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
@@ -26,7 +26,6 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -526,21 +525,4 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mBouncerExpansionCallback.onVisibilityChanged(false);
verify(mCentralSurfaces).setBouncerShowingOverDream(false);
}
-
- @Test
- public void testSetDozing_Dozing() {
- clearInvocations(mBouncer);
- mStatusBarKeyguardViewManager.onDozingChanged(true);
- // Once when shown and once with dozing changed.
- verify(mBouncer, times(1)).hide(false);
- }
-
- @Test
- public void testSetDozing_notDozing() {
- mStatusBarKeyguardViewManager.onDozingChanged(true);
- clearInvocations(mBouncer);
- mStatusBarKeyguardViewManager.onDozingChanged(false);
- // Once when shown and twice with dozing changed.
- verify(mBouncer, times(1)).hide(false);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 37c8f6285970..a3c6e9514191 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -431,7 +431,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class);
when(mOperatorNameViewControllerFactory.create(any()))
.thenReturn(mOperatorNameViewController);
- when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
+ when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
mSecureSettings = mock(SecureSettings.class);
setUpNotificationIconAreaController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index 36be1be309d6..0e75c74ef6f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -23,9 +23,16 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.mockito.Mockito
import org.mockito.Mockito.mock
@@ -64,12 +71,70 @@ class ConnectivityPipelineLoggerTest : SysuiTestCase() {
assertThat(actualString).contains(expectedNetId)
}
- private val NET_1_ID = 100
- private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
- Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
+ @Test
+ fun logOutputChange_printsValuesAndNulls() = runBlocking(IMMEDIATE) {
+ val flow: Flow<Int?> = flowOf(1, null, 3)
+
+ val job = flow
+ .logOutputChange(logger, "testInts")
+ .launchIn(this)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ assertThat(actualString).contains("1")
+ assertThat(actualString).contains("null")
+ assertThat(actualString).contains("3")
+
+ job.cancel()
+ }
+
+ @Test
+ fun logInputChange_unit_printsInputName() = runBlocking(IMMEDIATE) {
+ val flow: Flow<Unit> = flowOf(Unit, Unit)
+
+ val job = flow
+ .logInputChange(logger, "testInputs")
+ .launchIn(this)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ assertThat(actualString).contains("testInputs")
+
+ job.cancel()
+ }
+
+ @Test
+ fun logInputChange_any_printsValuesAndNulls() = runBlocking(IMMEDIATE) {
+ val flow: Flow<Any?> = flowOf(null, 2, "threeString")
+
+ val job = flow
+ .logInputChange(logger, "testInputs")
+ .launchIn(this)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ assertThat(actualString).contains("null")
+ assertThat(actualString).contains("2")
+ assertThat(actualString).contains("threeString")
+
+ job.cancel()
+ }
+
+ companion object {
+ private const val NET_1_ID = 100
+ private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
+ Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
+ }
+ private val NET_1_CAPS = NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build()
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
- private val NET_1_CAPS = NetworkCapabilities.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
- .build()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 6b8d4aa7c51f..f751afc195b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -16,20 +16,27 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
-import kotlinx.coroutines.flow.Flow
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
/** Fake implementation of [WifiRepository] exposing set methods for all the flows. */
class FakeWifiRepository : WifiRepository {
+ private val _isWifiEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
+
private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> =
MutableStateFlow(WifiNetworkModel.Inactive)
- override val wifiNetwork: Flow<WifiNetworkModel> = _wifiNetwork
+ override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
- override val wifiActivity: Flow<WifiActivityModel> = _wifiActivity
+ override val wifiActivity: StateFlow<WifiActivityModel> = _wifiActivity
+
+ fun setIsWifiEnabled(enabled: Boolean) {
+ _isWifiEnabled.value = enabled
+ }
fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) {
_wifiNetwork.value = wifiNetworkModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index d070ba0e47be..0ba0bd623c39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -28,15 +28,17 @@ import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
@@ -44,23 +46,28 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class WifiRepositoryImplTest : SysuiTestCase() {
private lateinit var underTest: WifiRepositoryImpl
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var wifiManager: WifiManager
@@ -70,16 +77,17 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ anyInt(),
+ nullable(),
+ )
+ ).thenReturn(flowOf(Unit))
executor = FakeExecutor(FakeSystemClock())
scope = CoroutineScope(IMMEDIATE)
-
- underTest = WifiRepositoryImpl(
- connectivityManager,
- logger,
- executor,
- scope,
- wifiManager,
- )
+ underTest = createRepo()
}
@After
@@ -88,6 +96,132 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isWifiEnabled_nullWifiManager_getsFalse() = runBlocking(IMMEDIATE) {
+ underTest = createRepo(wifiManagerToUse = null)
+
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+ }
+
+ @Test
+ fun isWifiEnabled_initiallyGetsWifiManagerValue() = runBlocking(IMMEDIATE) {
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+
+ underTest = createRepo()
+
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+ }
+
+ @Test
+ fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() = runBlocking(IMMEDIATE) {
+ // We need to call launch on the flows so that they start updating
+ val networkJob = underTest.wifiNetwork.launchIn(this)
+ val enabledJob = underTest.isWifiEnabled.launchIn(this)
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ getNetworkCallback().onCapabilitiesChanged(
+ NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
+ )
+
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ getNetworkCallback().onCapabilitiesChanged(
+ NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
+ )
+
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ networkJob.cancel()
+ enabledJob.cancel()
+ }
+
+ @Test
+ fun isWifiEnabled_networkLost_valueUpdated() = runBlocking(IMMEDIATE) {
+ // We need to call launch on the flows so that they start updating
+ val networkJob = underTest.wifiNetwork.launchIn(this)
+ val enabledJob = underTest.isWifiEnabled.launchIn(this)
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ getNetworkCallback().onLost(NETWORK)
+
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ getNetworkCallback().onLost(NETWORK)
+
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ networkJob.cancel()
+ enabledJob.cancel()
+ }
+
+ @Test
+ fun isWifiEnabled_intentsReceived_valueUpdated() = runBlocking(IMMEDIATE) {
+ val intentFlow = MutableSharedFlow<Unit>()
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ anyInt(),
+ nullable(),
+ )
+ ).thenReturn(intentFlow)
+ underTest = createRepo()
+
+ val job = underTest.isWifiEnabled.launchIn(this)
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ intentFlow.emit(Unit)
+
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ intentFlow.emit(Unit)
+
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() = runBlocking(IMMEDIATE) {
+ val intentFlow = MutableSharedFlow<Unit>()
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ anyInt(),
+ nullable(),
+ )
+ ).thenReturn(intentFlow)
+ underTest = createRepo()
+
+ val networkJob = underTest.wifiNetwork.launchIn(this)
+ val enabledJob = underTest.isWifiEnabled.launchIn(this)
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ intentFlow.emit(Unit)
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ getNetworkCallback().onLost(NETWORK)
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ getNetworkCallback().onCapabilitiesChanged(
+ NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
+ )
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ intentFlow.emit(Unit)
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ networkJob.cancel()
+ enabledJob.cancel()
+ }
+
+ @Test
fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
val job = underTest
@@ -509,13 +643,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
- underTest = WifiRepositoryImpl(
- connectivityManager,
- logger,
- executor,
- scope,
- wifiManager = null,
- )
+ underTest = createRepo(wifiManagerToUse = null)
var latest: WifiActivityModel? = null
val job = underTest
@@ -594,6 +722,17 @@ class WifiRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
+ private fun createRepo(wifiManagerToUse: WifiManager? = wifiManager): WifiRepositoryImpl {
+ return WifiRepositoryImpl(
+ broadcastDispatcher,
+ connectivityManager,
+ logger,
+ executor,
+ scope,
+ wifiManagerToUse,
+ )
+ }
+
private fun getTrafficStateCallback(): TrafficStateCallback {
val callbackCaptor = argumentCaptor<TrafficStateCallback>()
verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
index e896749d9a94..39b886af1cb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -16,13 +16,14 @@
package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
+import android.net.wifi.WifiManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,172 +51,129 @@ class WifiInteractorTest : SysuiTestCase() {
}
@Test
- fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
- )
+ fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
- var latest: Boolean? = null
+ var latest: String? = "default"
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
+ .ssid
+ .onEach { latest = it }
+ .launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isNull()
job.cancel()
}
@Test
- fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
- )
+ fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
- var latest: Boolean? = null
+ var latest: String? = "default"
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
+ .ssid
+ .onEach { latest = it }
+ .launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isNull()
job.cancel()
}
@Test
- fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
- )
-
- var latest: Boolean? = null
+ fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+ networkId = 1,
+ isPasspointAccessPoint = true,
+ passpointProviderFriendlyName = "friendly",
+ ))
+
+ var latest: String? = null
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
+ .ssid
+ .onEach { latest = it }
+ .launchIn(this)
- assertThat(latest).isTrue()
+ assertThat(latest).isEqualTo("friendly")
job.cancel()
}
@Test
- fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
- )
-
- var latest: Boolean? = null
+ fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+ networkId = 1,
+ isOnlineSignUpForPasspointAccessPoint = true,
+ passpointProviderFriendlyName = "friendly",
+ ))
+
+ var latest: String? = null
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
+ .ssid
+ .onEach { latest = it }
+ .launchIn(this)
- assertThat(latest).isTrue()
+ assertThat(latest).isEqualTo("friendly")
job.cancel()
}
@Test
- fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null))
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
- )
+ fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+ networkId = 1,
+ ssid = WifiManager.UNKNOWN_SSID,
+ ))
- var latest: Boolean? = null
+ var latest: String? = "default"
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
-
- assertThat(latest).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun hasActivityIn_inactiveNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
- )
-
- var latest: Boolean? = null
- val job = underTest
- .hasActivityIn
+ .ssid
.onEach { latest = it }
.launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isNull()
job.cancel()
}
@Test
- fun hasActivityIn_carrierMergedNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
- )
+ fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+ networkId = 1,
+ ssid = "MyAwesomeWifiNetwork",
+ ))
- var latest: Boolean? = null
+ var latest: String? = null
val job = underTest
- .hasActivityIn
+ .ssid
.onEach { latest = it }
.launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isEqualTo("MyAwesomeWifiNetwork")
job.cancel()
}
@Test
- fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
-
+ fun isEnabled_matchesRepoIsEnabled() = runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
+ .isEnabled
+ .onEach { latest = it }
+ .launchIn(this)
- // Conduct a series of changes and verify we catch each of them in succession
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
- )
+ wifiRepository.setIsWifiEnabled(true)
yield()
assertThat(latest).isTrue()
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
- )
+ wifiRepository.setIsWifiEnabled(false)
yield()
assertThat(latest).isFalse()
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
- )
+ wifiRepository.setIsWifiEnabled(true)
yield()
assertThat(latest).isTrue()
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
- )
- yield()
- assertThat(latest).isTrue()
-
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
- )
- yield()
- assertThat(latest).isFalse()
-
job.cancel()
}
@@ -242,6 +200,32 @@ class WifiInteractorTest : SysuiTestCase() {
}
@Test
+ fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) {
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .activity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val activity1 = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity1)
+ yield()
+ assertThat(latest).isEqualTo(activity1)
+
+ val activity2 = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity2)
+ yield()
+ assertThat(latest).isEqualTo(activity2)
+
+ val activity3 = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity3)
+ yield()
+ assertThat(latest).isEqualTo(activity3)
+
+ job.cancel()
+ }
+
+ @Test
fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
@@ -270,10 +254,6 @@ class WifiInteractorTest : SysuiTestCase() {
job.cancel()
}
-
- companion object {
- val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB")
- }
}
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 3c200a5da4fa..4efb13520ebf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -16,38 +16,225 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.view
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.View
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.lifecycle.InstantTaskExecutorRule
-import com.android.systemui.util.Assert
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(JUnit4::class)
-@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
class ModernStatusBarWifiViewTest : SysuiTestCase() {
+ private lateinit var testableLooper: TestableLooper
+
+ @Mock
+ private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
+ @Mock
+ private lateinit var logger: ConnectivityPipelineLogger
+ @Mock
+ private lateinit var connectivityConstants: ConnectivityConstants
+ @Mock
+ private lateinit var wifiConstants: WifiConstants
+ private lateinit var connectivityRepository: FakeConnectivityRepository
+ private lateinit var wifiRepository: FakeWifiRepository
+ private lateinit var interactor: WifiInteractor
+ private lateinit var viewModel: WifiViewModel
+ private lateinit var scope: CoroutineScope
+
@JvmField @Rule
val instantTaskExecutor = InstantTaskExecutorRule()
@Before
fun setUp() {
- Assert.setTestThread(Thread.currentThread())
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ connectivityRepository = FakeConnectivityRepository()
+ wifiRepository = FakeWifiRepository()
+ wifiRepository.setIsWifiEnabled(true)
+ interactor = WifiInteractor(connectivityRepository, wifiRepository)
+ scope = CoroutineScope(Dispatchers.Unconfined)
+ viewModel = WifiViewModel(
+ connectivityConstants,
+ context,
+ logger,
+ interactor,
+ scope,
+ statusBarPipelineFlags,
+ wifiConstants,
+ )
}
@Test
fun constructAndBind_hasCorrectSlot() {
val view = ModernStatusBarWifiView.constructAndBind(
- context, "slotName", mock()
+ context, "slotName", viewModel, StatusBarLocation.HOME
)
assertThat(view.slot).isEqualTo("slotName")
}
+
+ @Test
+ fun getVisibleState_icon_returnsIcon() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_ICON, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_ICON)
+ }
+
+ @Test
+ fun getVisibleState_dot_returnsDot() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_DOT, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_DOT)
+ }
+
+ @Test
+ fun getVisibleState_hidden_returnsHidden() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_HIDDEN)
+ }
+
+ // Note: The following tests are more like integration tests, since they stand up a full
+ // [WifiViewModel] and test the interactions between the view, view-binder, and view-model.
+
+ @Test
+ fun setVisibleState_icon_iconShownDotHidden() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_ICON, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconGroupView().visibility).isEqualTo(View.VISIBLE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.GONE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun setVisibleState_dot_iconHiddenDotShown() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_DOT, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.VISIBLE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun setVisibleState_hidden_iconAndDotHidden() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.GONE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun isIconVisible_notEnabled_outputsFalse() {
+ wifiRepository.setIsWifiEnabled(false)
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
+ )
+
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isFalse()
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun isIconVisible_enabled_outputsTrue() {
+ wifiRepository.setIsWifiEnabled(true)
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
+ )
+
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isTrue()
+
+ ViewUtils.detachView(view)
+ }
+
+ private fun View.getIconGroupView(): View {
+ return this.requireViewById(R.id.wifi_group)
+ }
+
+ private fun View.getDotView(): View {
+ return this.requireViewById(R.id.status_bar_dot)
+ }
}
+
+private const val SLOT_NAME = "TestSlotName"
+private const val NETWORK_ID = 200
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
new file mode 100644
index 000000000000..929e5294de3d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
+
+import android.content.Context
+import androidx.annotation.DrawableRes
+import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase) :
+ SysuiTestCase() {
+
+ private lateinit var underTest: WifiViewModel
+
+ @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var connectivityConstants: ConnectivityConstants
+ @Mock private lateinit var wifiConstants: WifiConstants
+ private lateinit var connectivityRepository: FakeConnectivityRepository
+ private lateinit var wifiRepository: FakeWifiRepository
+ private lateinit var interactor: WifiInteractor
+ private lateinit var scope: CoroutineScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ connectivityRepository = FakeConnectivityRepository()
+ wifiRepository = FakeWifiRepository()
+ wifiRepository.setIsWifiEnabled(true)
+ interactor = WifiInteractor(connectivityRepository, wifiRepository)
+ scope = CoroutineScope(IMMEDIATE)
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun wifiIcon() =
+ runBlocking(IMMEDIATE) {
+ wifiRepository.setIsWifiEnabled(testCase.enabled)
+ connectivityRepository.setForceHiddenIcons(
+ if (testCase.forceHidden) {
+ setOf(ConnectivitySlot.WIFI)
+ } else {
+ setOf()
+ }
+ )
+ whenever(wifiConstants.alwaysShowIconIfEnabled)
+ .thenReturn(testCase.alwaysShowIconWhenEnabled)
+ whenever(connectivityConstants.hasDataCapabilities)
+ .thenReturn(testCase.hasDataCapabilities)
+ underTest =
+ WifiViewModel(
+ connectivityConstants,
+ context,
+ logger,
+ interactor,
+ scope,
+ statusBarPipelineFlags,
+ wifiConstants,
+ )
+
+ val iconFlow = underTest.home.wifiIcon
+ val job = iconFlow.launchIn(this)
+
+ // WHEN we set a certain network
+ wifiRepository.setWifiNetwork(testCase.network)
+ yield()
+
+ // THEN we get the expected icon
+ assertThat(iconFlow.value?.res).isEqualTo(testCase.expected?.iconResource)
+ val expectedContentDescription =
+ if (testCase.expected == null) {
+ null
+ } else {
+ testCase.expected.contentDescription.invoke(context)
+ }
+ assertThat(iconFlow.value?.contentDescription?.getAsString())
+ .isEqualTo(expectedContentDescription)
+
+ job.cancel()
+ }
+
+ private fun ContentDescription.getAsString(): String? {
+ return when (this) {
+ is ContentDescription.Loaded -> this.description
+ is ContentDescription.Resource -> context.getString(this.res)
+ }
+ }
+
+ internal data class Expected(
+ /** The resource that should be used for the icon. */
+ @DrawableRes val iconResource: Int,
+
+ /** A function that, given a context, calculates the correct content description string. */
+ val contentDescription: (Context) -> String,
+ )
+
+ // Note: We use default values for the boolean parameters to reflect a "typical configuration"
+ // for wifi. This allows each TestCase to only define the parameter values that are critical
+ // for the test function.
+ internal data class TestCase(
+ val enabled: Boolean = true,
+ val forceHidden: Boolean = false,
+ val alwaysShowIconWhenEnabled: Boolean = false,
+ val hasDataCapabilities: Boolean = true,
+ val network: WifiNetworkModel,
+
+ /** The expected output. Null if we expect the output to be null. */
+ val expected: Expected?
+ )
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun data(): Collection<TestCase> =
+ listOf(
+ // Enabled = false => no networks shown
+ TestCase(
+ enabled = false,
+ network = WifiNetworkModel.CarrierMerged,
+ expected = null,
+ ),
+ TestCase(
+ enabled = false,
+ network = WifiNetworkModel.Inactive,
+ expected = null,
+ ),
+ TestCase(
+ enabled = false,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 1),
+ expected = null,
+ ),
+ TestCase(
+ enabled = false,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 3),
+ expected = null,
+ ),
+
+ // forceHidden = true => no networks shown
+ TestCase(
+ forceHidden = true,
+ network = WifiNetworkModel.CarrierMerged,
+ expected = null,
+ ),
+ TestCase(
+ forceHidden = true,
+ network = WifiNetworkModel.Inactive,
+ expected = null,
+ ),
+ TestCase(
+ enabled = false,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 2),
+ expected = null,
+ ),
+ TestCase(
+ forceHidden = true,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1),
+ expected = null,
+ ),
+
+ // alwaysShowIconWhenEnabled = true => all Inactive and Active networks shown
+ TestCase(
+ alwaysShowIconWhenEnabled = true,
+ network = WifiNetworkModel.Inactive,
+ expected =
+ Expected(
+ iconResource = WifiIcons.WIFI_NO_NETWORK,
+ contentDescription = { context ->
+ "${context.getString(WIFI_NO_CONNECTION)}," +
+ context.getString(NO_INTERNET)
+ }
+ ),
+ ),
+ TestCase(
+ alwaysShowIconWhenEnabled = true,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 4),
+ expected =
+ Expected(
+ iconResource = WIFI_NO_INTERNET_ICONS[4],
+ contentDescription = { context ->
+ "${context.getString(WIFI_CONNECTION_STRENGTH[4])}," +
+ context.getString(NO_INTERNET)
+ }
+ ),
+ ),
+ TestCase(
+ alwaysShowIconWhenEnabled = true,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2),
+ expected =
+ Expected(
+ iconResource = WIFI_FULL_ICONS[2],
+ contentDescription = { context ->
+ context.getString(WIFI_CONNECTION_STRENGTH[2])
+ }
+ ),
+ ),
+
+ // hasDataCapabilities = false => all Inactive and Active networks shown
+ TestCase(
+ hasDataCapabilities = false,
+ network = WifiNetworkModel.Inactive,
+ expected =
+ Expected(
+ iconResource = WifiIcons.WIFI_NO_NETWORK,
+ contentDescription = { context ->
+ "${context.getString(WIFI_NO_CONNECTION)}," +
+ context.getString(NO_INTERNET)
+ }
+ ),
+ ),
+ TestCase(
+ hasDataCapabilities = false,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 2),
+ expected =
+ Expected(
+ iconResource = WIFI_NO_INTERNET_ICONS[2],
+ contentDescription = { context ->
+ "${context.getString(WIFI_CONNECTION_STRENGTH[2])}," +
+ context.getString(NO_INTERNET)
+ }
+ ),
+ ),
+ TestCase(
+ hasDataCapabilities = false,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 0),
+ expected =
+ Expected(
+ iconResource = WIFI_FULL_ICONS[0],
+ contentDescription = { context ->
+ context.getString(WIFI_CONNECTION_STRENGTH[0])
+ }
+ ),
+ ),
+
+ // network = CarrierMerged => not shown
+ TestCase(
+ network = WifiNetworkModel.CarrierMerged,
+ expected = null,
+ ),
+
+ // network = Inactive => not shown
+ TestCase(
+ network = WifiNetworkModel.Inactive,
+ expected = null,
+ ),
+
+ // network = Active & validated = false => not shown
+ TestCase(
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3),
+ expected = null,
+ ),
+
+ // network = Active & validated = true => shown
+ TestCase(
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 4),
+ expected =
+ Expected(
+ iconResource = WIFI_FULL_ICONS[4],
+ contentDescription = { context ->
+ context.getString(WIFI_CONNECTION_STRENGTH[4])
+ }
+ ),
+ ),
+
+ // network has null level => not shown
+ TestCase(
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = null),
+ expected = null,
+ ),
+ )
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
+private const val NETWORK_ID = 789
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 43103a065e68..3169eef83f07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -17,37 +17,34 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import androidx.test.filters.SmallTest
-import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
-import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class WifiViewModelTest : SysuiTestCase() {
@@ -56,236 +53,426 @@ class WifiViewModelTest : SysuiTestCase() {
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
- @Mock private lateinit var constants: WifiConstants
+ @Mock private lateinit var connectivityConstants: ConnectivityConstants
+ @Mock private lateinit var wifiConstants: WifiConstants
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
+ private lateinit var scope: CoroutineScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
+ wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
+ scope = CoroutineScope(IMMEDIATE)
+ createAndSetViewModel()
+ }
- underTest = WifiViewModel(
- statusBarPipelineFlags,
- constants,
- context,
- logger,
- interactor
- )
+ @After
+ fun tearDown() {
+ scope.cancel()
}
+ // Note on testing: [WifiViewModel] exposes 3 different instances of
+ // [LocationBasedWifiViewModel]. In practice, these 3 different instances will get the exact
+ // same data for icon, activity, etc. flows. So, most of these tests will test just one of the
+ // instances. There are also some tests that verify all 3 instances received the same data.
+
@Test
- fun wifiIcon_forceHidden_outputsNull() = runBlocking(IMMEDIATE) {
- connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
+ fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
+ var latestHome: Icon? = null
+ val jobHome = underTest
+ .home
+ .wifiIcon
+ .onEach { latestHome = it }
+ .launchIn(this)
- var latest: Icon? = null
- val job = underTest
+ var latestKeyguard: Icon? = null
+ val jobKeyguard = underTest
+ .keyguard
.wifiIcon
- .onEach { latest = it }
+ .onEach { latestKeyguard = it }
.launchIn(this)
- assertThat(latest).isNull()
+ var latestQs: Icon? = null
+ val jobQs = underTest
+ .qs
+ .wifiIcon
+ .onEach { latestQs = it }
+ .launchIn(this)
- job.cancel()
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ isValidated = true,
+ level = 1
+ )
+ )
+ yield()
+
+ assertThat(latestHome).isInstanceOf(Icon.Resource::class.java)
+ assertThat(latestHome).isEqualTo(latestKeyguard)
+ assertThat(latestKeyguard).isEqualTo(latestQs)
+
+ jobHome.cancel()
+ jobKeyguard.cancel()
+ jobQs.cancel()
}
@Test
- fun wifiIcon_notForceHidden_outputsVisible() = runBlocking(IMMEDIATE) {
- connectivityRepository.setForceHiddenIcons(setOf())
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
+ fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Icon? = null
- val job = underTest
- .wifiIcon
- .onEach { latest = it }
+ var activityIn: Boolean? = null
+ val activityInJob = underTest
+ .home
+ .isActivityInViewVisible
+ .onEach { activityIn = it }
.launchIn(this)
- assertThat(latest).isInstanceOf(Icon.Resource::class.java)
+ var activityOut: Boolean? = null
+ val activityOutJob = underTest
+ .home
+ .isActivityOutViewVisible
+ .onEach { activityOut = it }
+ .launchIn(this)
- job.cancel()
+ var activityContainer: Boolean? = null
+ val activityContainerJob = underTest
+ .home
+ .isActivityContainerVisible
+ .onEach { activityContainer = it }
+ .launchIn(this)
+
+ // Verify that on launch, we receive false.
+ assertThat(activityIn).isFalse()
+ assertThat(activityOut).isFalse()
+ assertThat(activityContainer).isFalse()
+
+ activityInJob.cancel()
+ activityOutJob.cancel()
+ activityContainerJob.cancel()
}
@Test
- fun wifiIcon_inactiveNetwork_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+ fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ var activityIn: Boolean? = null
+ val activityInJob = underTest
+ .home
+ .isActivityInViewVisible
+ .onEach { activityIn = it }
+ .launchIn(this)
+
+ var activityOut: Boolean? = null
+ val activityOutJob = underTest
+ .home
+ .isActivityOutViewVisible
+ .onEach { activityOut = it }
+ .launchIn(this)
+
+ var activityContainer: Boolean? = null
+ val activityContainerJob = underTest
+ .home
+ .isActivityContainerVisible
+ .onEach { activityContainer = it }
+ .launchIn(this)
+
+ // WHEN we update the repo to have activity
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ // THEN we didn't update to the new activity (because our config is false)
+ assertThat(activityIn).isFalse()
+ assertThat(activityOut).isFalse()
+ assertThat(activityContainer).isFalse()
+
+ activityInJob.cancel()
+ activityOutJob.cancel()
+ activityContainerJob.cancel()
+ }
+
+ @Test
+ fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null))
+
+ var activityIn: Boolean? = null
+ val activityInJob = underTest
+ .home
+ .isActivityInViewVisible
+ .onEach { activityIn = it }
+ .launchIn(this)
+
+ var activityOut: Boolean? = null
+ val activityOutJob = underTest
+ .home
+ .isActivityOutViewVisible
+ .onEach { activityOut = it }
+ .launchIn(this)
+
+ var activityContainer: Boolean? = null
+ val activityContainerJob = underTest
+ .home
+ .isActivityContainerVisible
+ .onEach { activityContainer = it }
+ .launchIn(this)
+
+ // WHEN we update the repo to have activity
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ // THEN we still output false because our network's SSID is null
+ assertThat(activityIn).isFalse()
+ assertThat(activityOut).isFalse()
+ assertThat(activityContainer).isFalse()
+
+ activityInJob.cancel()
+ activityOutJob.cancel()
+ activityContainerJob.cancel()
+ }
+
+ @Test
+ fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ var latestHome: Boolean? = null
+ val jobHome = underTest
+ .home
+ .isActivityInViewVisible
+ .onEach { latestHome = it }
+ .launchIn(this)
+
+ var latestKeyguard: Boolean? = null
+ val jobKeyguard = underTest
+ .keyguard
+ .isActivityInViewVisible
+ .onEach { latestKeyguard = it }
+ .launchIn(this)
+
+ var latestQs: Boolean? = null
+ val jobQs = underTest
+ .qs
+ .isActivityInViewVisible
+ .onEach { latestQs = it }
+ .launchIn(this)
+
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- var latest: Icon? = null
+ assertThat(latestHome).isTrue()
+ assertThat(latestKeyguard).isTrue()
+ assertThat(latestQs).isTrue()
+
+ jobHome.cancel()
+ jobKeyguard.cancel()
+ jobQs.cancel()
+ }
+
+ @Test
+ fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ var latest: Boolean? = null
val job = underTest
- .wifiIcon
- .onEach { latest = it }
- .launchIn(this)
-
- assertThat(latest).isInstanceOf(Icon.Resource::class.java)
- val icon = latest as Icon.Resource
- assertThat(icon.res).isEqualTo(WIFI_NO_NETWORK)
- assertThat(icon.contentDescription?.getAsString())
- .contains(context.getString(WIFI_NO_CONNECTION))
- assertThat(icon.contentDescription?.getAsString())
- .contains(context.getString(NO_INTERNET))
+ .home
+ .isActivityInViewVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latest).isTrue()
job.cancel()
}
@Test
- fun wifiIcon_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
+ fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Icon? = null
+ var latest: Boolean? = null
val job = underTest
- .wifiIcon
+ .home
+ .isActivityInViewVisible
.onEach { latest = it }
.launchIn(this)
- assertThat(latest).isNull()
+ val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latest).isFalse()
job.cancel()
}
@Test
- fun wifiIcon_isActiveNullLevel_outputsNull() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = null))
+ fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Icon? = null
+ var latest: Boolean? = null
val job = underTest
- .wifiIcon
+ .home
+ .isActivityOutViewVisible
.onEach { latest = it }
.launchIn(this)
- assertThat(latest).isNull()
+ val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latest).isTrue()
job.cancel()
}
@Test
- fun wifiIcon_isActiveAndValidated_level1_outputsFull1Icon() = runBlocking(IMMEDIATE) {
- val level = 1
-
- wifiRepository.setWifiNetwork(
- WifiNetworkModel.Active(
- NETWORK_ID,
- isValidated = true,
- level = level
- )
- )
+ fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Icon? = null
+ var latest: Boolean? = null
val job = underTest
- .wifiIcon
+ .home
+ .isActivityOutViewVisible
.onEach { latest = it }
.launchIn(this)
- assertThat(latest).isInstanceOf(Icon.Resource::class.java)
- val icon = latest as Icon.Resource
- assertThat(icon.res).isEqualTo(WIFI_FULL_ICONS[level])
- assertThat(icon.contentDescription?.getAsString())
- .contains(context.getString(WIFI_CONNECTION_STRENGTH[level]))
- assertThat(icon.contentDescription?.getAsString())
- .doesNotContain(context.getString(NO_INTERNET))
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latest).isFalse()
job.cancel()
}
@Test
- fun wifiIcon_isActiveAndNotValidated_level4_outputsEmpty4Icon() = runBlocking(IMMEDIATE) {
- val level = 4
-
- wifiRepository.setWifiNetwork(
- WifiNetworkModel.Active(
- NETWORK_ID,
- isValidated = false,
- level = level
- )
- )
+ fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Icon? = null
+ var latest: Boolean? = null
val job = underTest
- .wifiIcon
+ .home
+ .isActivityContainerVisible
.onEach { latest = it }
.launchIn(this)
- assertThat(latest).isInstanceOf(Icon.Resource::class.java)
- val icon = latest as Icon.Resource
- assertThat(icon.res).isEqualTo(WIFI_NO_INTERNET_ICONS[level])
- assertThat(icon.contentDescription?.getAsString())
- .contains(context.getString(WIFI_CONNECTION_STRENGTH[level]))
- assertThat(icon.contentDescription?.getAsString())
- .contains(context.getString(NO_INTERNET))
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latest).isTrue()
job.cancel()
}
@Test
- fun activityInVisible_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(constants.shouldShowActivityConfig).thenReturn(false)
+ fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
- .isActivityInVisible
- .onEach { latest = it }
- .launchIn(this)
+ .home
+ .isActivityContainerVisible
+ .onEach { latest = it }
+ .launchIn(this)
- // Verify that on launch, we receive a false.
- assertThat(latest).isFalse()
+ val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latest).isTrue()
job.cancel()
}
@Test
- fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
- whenever(constants.shouldShowActivityConfig).thenReturn(false)
+ fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
- .isActivityInVisible
- .onEach { latest = it }
- .launchIn(this)
+ .home
+ .isActivityContainerVisible
+ .onEach { latest = it }
+ .launchIn(this)
- // Update the repo to have activityIn
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
- )
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
yield()
- // Verify that we didn't update to activityIn=true (because our config is false)
- assertThat(latest).isFalse()
+ assertThat(latest).isTrue()
job.cancel()
}
@Test
- fun activityInVisible_showActivityConfigTrue_outputsUpdate() = runBlocking(IMMEDIATE) {
- whenever(constants.shouldShowActivityConfig).thenReturn(true)
+ fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
- .isActivityInVisible
- .onEach { latest = it }
- .launchIn(this)
+ .home
+ .isActivityContainerVisible
+ .onEach { latest = it }
+ .launchIn(this)
- // Update the repo to have activityIn
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
- )
+ val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
yield()
- // Verify that we updated to activityIn=true
- assertThat(latest).isTrue()
+ assertThat(latest).isFalse()
job.cancel()
}
- private fun ContentDescription.getAsString(): String? {
- return when (this) {
- is ContentDescription.Loaded -> this.description
- is ContentDescription.Resource -> context.getString(this.res)
- }
+ private fun createAndSetViewModel() {
+ // [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
+ // creations rely on certain config values that we mock out in individual tests. This method
+ // allows tests to create the view model only after those configs are correctly set up.
+ underTest = WifiViewModel(
+ connectivityConstants,
+ context,
+ logger,
+ interactor,
+ scope,
+ statusBarPipelineFlags,
+ wifiConstants,
+ )
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index c47ea9cea75e..6ace4044b3f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -275,10 +275,9 @@ public class RemoteInputViewTest extends SysuiTestCase {
EditText editText = view.findViewById(R.id.remote_input_text);
editText.setText(TEST_REPLY);
ClipDescription description = new ClipDescription("", new String[] {"image/png"});
- // We need to use an (arbitrary) real resource here so that an actual image gets attached.
+ // We need to use an (arbitrary) real resource here so that an actual image gets attached
ClipData clip = new ClipData(description, new ClipData.Item(
- Uri.parse("android.resource://com.android.systemui/"
- + R.drawable.default_thumbnail)));
+ Uri.parse("android.resource://android/" + android.R.drawable.btn_default)));
ContentInfo payload =
new ContentInfo.Builder(clip, SOURCE_CLIPBOARD).build();
view.setAttachment(payload);
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index ad09f7cd3dde..33f1b4282e97 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -184,13 +184,21 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
@MainThread
private void startScan() {
enforceInitialized();
- // This method should not be called if scan is already in progress.
- if (mScanning) throw new IllegalStateException("Scan is already in progress.");
- // Neither should this method be called if the adapter is not available.
- if (mBleScanner == null) throw new IllegalStateException("BLE is not available.");
if (DEBUG) Log.i(TAG, "startScan()");
+ // This method should not be called if scan is already in progress.
+ if (mScanning) {
+ Slog.w(TAG, "Scan is already in progress.");
+ return;
+ }
+
+ // Neither should this method be called if the adapter is not available.
+ if (mBleScanner == null) {
+ Slog.w(TAG, "BLE is not available.");
+ return;
+ }
+
// Collect MAC addresses from all associations.
final Set<String> macAddresses = new HashSet<>();
for (AssociationInfo association : mAssociationStore.getAssociations()) {
@@ -221,8 +229,18 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
filters.add(filter);
}
- mBleScanner.startScan(filters, SCAN_SETTINGS, mScanCallback);
- mScanning = true;
+ // BluetoothLeScanner will throw an IllegalStateException if startScan() is called while LE
+ // is not enabled.
+ if (mBtAdapter.isLeEnabled()) {
+ try {
+ mBleScanner.startScan(filters, SCAN_SETTINGS, mScanCallback);
+ mScanning = true;
+ } catch (IllegalStateException e) {
+ Slog.w(TAG, "Exception while starting BLE scanning", e);
+ }
+ } else {
+ Slog.w(TAG, "BLE scanning is not turned on");
+ }
}
private void stopScanIfNeeded() {
@@ -240,11 +258,11 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
if (mBtAdapter.isLeEnabled()) {
try {
mBleScanner.stopScan(mScanCallback);
- } catch (RuntimeException e) {
- // Just to be sure not to crash system server here if BluetoothLeScanner throws
- // another RuntimeException.
+ } catch (IllegalStateException e) {
Slog.w(TAG, "Exception while stopping BLE scanning", e);
}
+ } else {
+ Slog.w(TAG, "BLE scanning is not turned on");
}
mScanning = false;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index efb2cb7a3283..2a219289cf10 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -411,8 +411,11 @@ final class LocalDisplayAdapter extends DisplayAdapter {
// For a new display, we need to initialize the default mode ID.
if (mDefaultModeId == INVALID_MODE_ID) {
- mDefaultModeId = activeRecord.mMode.getModeId();
- mDefaultModeGroup = mActiveSfDisplayMode.group;
+ mDefaultModeId = mSystemPreferredModeId != INVALID_MODE_ID
+ ? mSystemPreferredModeId : activeRecord.mMode.getModeId();
+ mDefaultModeGroup = mSystemPreferredModeId != INVALID_MODE_ID
+ ? preferredSfDisplayMode.group
+ : mActiveSfDisplayMode.group;
} else if (modesAdded && activeModeChanged) {
Slog.d(TAG, "New display modes are added and the active mode has changed, "
+ "use active mode as default mode.");
@@ -894,13 +897,6 @@ final class LocalDisplayAdapter extends DisplayAdapter {
public void setUserPreferredDisplayModeLocked(Display.Mode mode) {
final int oldModeId = getPreferredModeId();
mUserPreferredMode = mode;
- // When clearing the user preferred mode we need to also reset the default mode. This is
- // used by DisplayModeDirector to determine the default resolution, so if we don't clear
- // it then the resolution won't reset to what it would've been prior to setting a user
- // preferred display mode.
- if (mode == null && mSystemPreferredModeId != INVALID_MODE_ID) {
- mDefaultModeId = mSystemPreferredModeId;
- }
if (mode != null && (mode.isRefreshRateSet() || mode.isResolutionSet())) {
Display.Mode matchingSupportedMode;
matchingSupportedMode = findMode(mode.getPhysicalWidth(),
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 5599f2cfc374..9c95e31cc5f5 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1032,8 +1032,12 @@ public class AppTransitionController {
private void applyAnimations(ArraySet<ActivityRecord> openingApps,
ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,
LayoutParams animLp, boolean voiceInteraction) {
+ final RecentsAnimationController rac = mService.getRecentsAnimationController();
if (transit == WindowManager.TRANSIT_OLD_UNSET
|| (openingApps.isEmpty() && closingApps.isEmpty())) {
+ if (rac != null) {
+ rac.sendTasksAppeared();
+ }
return;
}
@@ -1071,7 +1075,6 @@ public class AppTransitionController {
voiceInteraction);
applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
voiceInteraction);
- final RecentsAnimationController rac = mService.getRecentsAnimationController();
if (rac != null) {
rac.sendTasksAppeared();
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index b26de07461d6..4c69f87106d1 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2168,10 +2168,7 @@ public class DisplayPolicy {
* If the decor insets changes, the display configuration may be affected. The caller should
* call {@link DisplayContent#sendNewConfiguration()} if this method returns {@code true}.
*/
- boolean updateDecorInsetsInfoIfNeeded(WindowState win) {
- if (!win.providesNonDecorInsets()) {
- return false;
- }
+ boolean updateDecorInsetsInfo() {
final DisplayFrames displayFrames = mDisplayContent.mDisplayFrames;
final int rotation = displayFrames.mRotation;
final int dw = displayFrames.mWidth;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 383fcb9303d7..07ae167f5e66 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -477,7 +477,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
mLocalInsetsSourceProviders.remove(insetsTypes[i]);
}
- mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
+ // Update insets if this window is attached.
+ if (mDisplayContent != null) {
+ mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c96253ccf70a..4d37e0816639 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1858,8 +1858,12 @@ public class WindowManagerService extends IWindowManager.Stub
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
+ ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
- if ((win.isVisibleRequestedOrAdding() && displayContent.updateOrientation())
- || displayPolicy.updateDecorInsetsInfoIfNeeded(win)) {
+ boolean needToSendNewConfiguration =
+ win.isVisibleRequestedOrAdding() && displayContent.updateOrientation();
+ if (win.providesNonDecorInsets()) {
+ needToSendNewConfiguration |= displayPolicy.updateDecorInsetsInfo();
+ }
+ if (needToSendNewConfiguration) {
displayContent.sendNewConfiguration();
}
@@ -2329,8 +2333,8 @@ public class WindowManagerService extends IWindowManager.Stub
& WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED) != 0) {
win.mLayoutNeeded = true;
}
- if (layoutChanged) {
- configChanged = displayPolicy.updateDecorInsetsInfoIfNeeded(win);
+ if (layoutChanged && win.providesNonDecorInsets()) {
+ configChanged = displayPolicy.updateDecorInsetsInfo();
}
if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
|| (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7c9d27704442..42d28612c83f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2627,11 +2627,19 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
+ // Check if window provides non decor insets before clearing its provided insets.
+ final boolean windowProvidesNonDecorInsets = providesNonDecorInsets();
+
removeImmediately();
// Removing a visible window may affect the display orientation so just update it if
// needed. Also recompute configuration if it provides screen decor insets.
- if ((wasVisible && displayContent.updateOrientation())
- || displayContent.getDisplayPolicy().updateDecorInsetsInfoIfNeeded(this)) {
+ boolean needToSendNewConfiguration = wasVisible && displayContent.updateOrientation();
+ if (windowProvidesNonDecorInsets) {
+ needToSendNewConfiguration |=
+ displayContent.getDisplayPolicy().updateDecorInsetsInfo();
+ }
+
+ if (needToSendNewConfiguration) {
displayContent.sendNewConfiguration();
}
mWmService.updateFocusedWindowLocked(isFocused()
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index ed369c016770..9c615d140e85 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -764,13 +764,11 @@ public class LocalDisplayAdapterTest {
@Test
public void testGetSystemPreferredDisplayMode() throws Exception {
SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f);
- // system preferred mode
+ // preferred mode
SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 3840, 2160, 60f);
- // user preferred mode
- SurfaceControl.DisplayMode displayMode3 = createFakeDisplayMode(2, 1920, 1080, 30f);
SurfaceControl.DisplayMode[] modes =
- new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, displayMode3};
+ new SurfaceControl.DisplayMode[]{displayMode1, displayMode2};
FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 1);
setUpDisplay(display);
updateAvailableDisplays();
@@ -782,43 +780,24 @@ public class LocalDisplayAdapterTest {
DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
0).getDisplayDeviceInfoLocked();
- assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
- Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
- assertThat(matches(defaultMode, displayMode1)).isTrue();
- // Set the user preferred display mode
- mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
- new Display.Mode(
- displayMode3.width, displayMode3.height, displayMode3.refreshRate));
- updateAvailableDisplays();
- waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
- displayDeviceInfo = mListener.addedDisplays.get(
- 0).getDisplayDeviceInfoLocked();
- defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
- assertThat(matches(defaultMode, displayMode3)).isTrue();
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
- // clear the user preferred mode
- mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(null);
- updateAvailableDisplays();
- waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
- displayDeviceInfo = mListener.addedDisplays.get(
- 0).getDisplayDeviceInfoLocked();
- defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
assertThat(matches(defaultMode, displayMode2)).isTrue();
- // Change the display and add new system preferred mode
- SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(3, 2340, 1080, 20f);
- modes = new SurfaceControl.DisplayMode[]{
- displayMode1, displayMode2, displayMode3, addedDisplayInfo};
+ // Change the display and add new preferred mode
+ SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(2, 2340, 1080, 60f);
+ modes = new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, addedDisplayInfo};
display.dynamicInfo.supportedDisplayModes = modes;
- display.dynamicInfo.preferredBootDisplayMode = 3;
+ display.dynamicInfo.preferredBootDisplayMode = 2;
setUpDisplay(display);
mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
assertTrue(mListener.traversalRequested);
assertThat(mListener.addedDisplays.size()).isEqualTo(1);
- assertThat(mListener.changedDisplays.size()).isEqualTo(3);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
DisplayDevice displayDevice = mListener.changedDisplays.get(0);
displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 95e9f20011d0..d5447447a7b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2992,7 +2992,8 @@ public class ActivityRecordTests extends WindowTestsBase {
.setSystemDecorations(true).build();
// Add a decor insets provider window.
final WindowState navbar = createNavBarWithProvidedInsets(squareDisplay);
- squareDisplay.getDisplayPolicy().updateDecorInsetsInfoIfNeeded(navbar);
+ assertTrue(navbar.providesNonDecorInsets()
+ && squareDisplay.getDisplayPolicy().updateDecorInsetsInfo());
squareDisplay.sendNewConfiguration();
final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index e00296f4d801..73eb1273efa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -292,12 +292,16 @@ public class DisplayPolicyTests extends WindowTestsBase {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
final DisplayInfo di = mDisplayContent.getDisplayInfo();
final int prevScreenHeightDp = mDisplayContent.getConfiguration().screenHeightDp;
- assertTrue(displayPolicy.updateDecorInsetsInfoIfNeeded(navbar));
+ assertTrue(navbar.providesNonDecorInsets() && displayPolicy.updateDecorInsetsInfo());
assertEquals(NAV_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation,
di.logicalWidth, di.logicalHeight).mConfigInsets.bottom);
mDisplayContent.sendNewConfiguration();
assertNotEquals(prevScreenHeightDp, mDisplayContent.getConfiguration().screenHeightDp);
- assertFalse(displayPolicy.updateDecorInsetsInfoIfNeeded(navbar));
+ assertFalse(navbar.providesNonDecorInsets() && displayPolicy.updateDecorInsetsInfo());
+
+ navbar.removeIfPossible();
+ assertEquals(0, displayPolicy.getDecorInsetsInfo(di.rotation, di.logicalWidth,
+ di.logicalHeight).mNonDecorInsets.bottom);
}
@UseTestDisplay(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })