Merge changes Icbafc8bb,I8ae07863,I35823347,I9c50f3f0 into main
* changes:
[SB][Screen Chips] Disable the old icons if flag is enabled.
[SB][Screen Chips] Don't let the timers reset on theme change.
[SB][Screen Chips] Show 3-2-1 countdown for screen record chip.
[SB][Screen Chips] Use correct icon for share-to-app chip.
diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
index cd5c37d..beb16b3 100644
--- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
@@ -46,6 +46,8 @@
android:tint="?android:attr/colorPrimary"
/>
+ <!-- Only one of [ongoing_activity_chip_time, ongoing_activity_chip_text] will ever
+ be shown at one time. -->
<com.android.systemui.statusbar.chips.ui.view.ChipChronometer
android:id="@+id/ongoing_activity_chip_time"
android:layout_width="wrap_content"
@@ -58,5 +60,19 @@
android:textColor="?android:attr/colorPrimary"
/>
+ <!-- Used to show generic text in the chip instead of a timer. -->
+ <TextView
+ android:id="@+id/ongoing_activity_chip_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:gravity="center|start"
+ android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding"
+ android:textAppearance="@android:style/TextAppearance.Material.Small"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ android:textColor="?android:attr/colorPrimary"
+ android:visibility="gone"
+ />
+
</com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer>
</FrameLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt
index 108e22b..64dedea 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt
@@ -16,6 +16,7 @@
package com.android.systemui.common.ui.binder
+import android.view.View
import android.widget.ImageView
import com.android.systemui.common.shared.model.Icon
@@ -30,4 +31,13 @@
is Icon.Resource -> view.setImageResource(icon.res)
}
}
+
+ fun bindNullable(icon: Icon?, view: ImageView) {
+ if (icon != null) {
+ view.visibility = View.VISIBLE
+ bind(icon, view)
+ } else {
+ view.visibility = View.GONE
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 4715230..284239a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -47,6 +47,7 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.screenrecord.data.model.ScreenRecordModel;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -146,8 +147,9 @@
if (isRecording) {
state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_stop);
} else if (isStarting) {
- // round, since the timer isn't exact
- int countdown = (int) Math.floorDiv(mMillisUntilFinished + 500, 1000);
+ int countdown =
+ (int) ScreenRecordModel.Starting.Companion.toCountdownSeconds(
+ mMillisUntilFinished);
state.secondaryLabel = String.format("%d...", countdown);
} else {
state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
index 7446708..e74e77f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -61,7 +61,7 @@
contentDescription = null
)
icon = { loadedIcon }
- val countDown = Math.floorDiv(data.millisUntilStarted + 500, 1000)
+ val countDown = data.countdownSeconds
sideViewIcon = QSTileState.SideViewIcon.None
secondaryLabel = String.format("%d...", countDown)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt
index b225444..ada5d8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt
@@ -22,7 +22,17 @@
data object Recording : ScreenRecordModel
/** A screen recording will begin in [millisUntilStarted] ms. */
- data class Starting(val millisUntilStarted: Long) : ScreenRecordModel
+ data class Starting(val millisUntilStarted: Long) : ScreenRecordModel {
+ val countdownSeconds = millisUntilStarted.toCountdownSeconds()
+
+ companion object {
+ /**
+ * Returns the number of seconds until screen recording will start, used to show a 3-2-1
+ * countdown.
+ */
+ fun Long.toCountdownSeconds() = Math.floorDiv(this + 500, 1000)
+ }
+ }
/** There's nothing related to screen recording happening. */
data object DoingNothing : ScreenRecordModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index ba3fde6..79f1874 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.call.ui.viewmodel
+import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.shared.model.Icon
@@ -60,7 +61,7 @@
val startTimeInElapsedRealtime =
state.startTimeMs - systemClock.currentTimeMillis() +
systemClock.elapsedRealtime()
- OngoingActivityChipModel.Shown(
+ OngoingActivityChipModel.Shown.Timer(
icon =
Icon.Resource(
com.android.internal.R.drawable.ic_phone,
@@ -68,26 +69,30 @@
),
colors = ColorsModel.Themed,
startTimeMs = startTimeInElapsedRealtime,
- ) {
- if (state.intent != null) {
- val backgroundView =
- it.requireViewById<ChipBackgroundContainer>(
- R.id.ongoing_activity_chip_background
- )
- // TODO(b/332662551): Log the click event.
- // This mimics OngoingCallController#updateChipClickListener.
- activityStarter.postStartActivityDismissingKeyguard(
- state.intent,
- ActivityTransitionAnimator.Controller.fromView(
- backgroundView,
- InteractionJankMonitor
- .CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
- )
- )
- }
- }
+ getOnClickListener(state),
+ )
}
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+
+ private fun getOnClickListener(state: OngoingCallModel.InCall): View.OnClickListener? {
+ if (state.intent == null) {
+ return null
+ }
+
+ return View.OnClickListener { view ->
+ val backgroundView =
+ view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
+ // TODO(b/332662551): Log the click event.
+ // This mimics OngoingCallController#updateChipClickListener.
+ activityStarter.postStartActivityDismissingKeyguard(
+ state.intent,
+ ActivityTransitionAnimator.Controller.fromView(
+ backgroundView,
+ InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+ )
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index 53b1e75..42e921e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -70,7 +70,8 @@
}
}
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+ // See b/347726238.
+ .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
/** Stops the currently active projection. */
private fun stopProjecting() {
@@ -80,7 +81,7 @@
private fun createCastToOtherDeviceChip(
state: ProjectionChipModel.Projecting,
): OngoingActivityChipModel.Shown {
- return OngoingActivityChipModel.Shown(
+ return OngoingActivityChipModel.Shown.Timer(
icon =
Icon.Resource(
CAST_TO_OTHER_DEVICE_ICON,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
index 1e9f0a1..43b1d16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
@@ -50,7 +50,6 @@
) { screenRecordState, mediaProjectionState ->
when (screenRecordState) {
is ScreenRecordModel.DoingNothing -> ScreenRecordChipModel.DoingNothing
- // TODO(b/332662551): Implement the 3-2-1 countdown chip.
is ScreenRecordModel.Starting ->
ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted)
is ScreenRecordModel.Recording -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 9d54c75..af6d7f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -23,6 +23,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
+import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
@@ -55,10 +56,14 @@
.map { state ->
when (state) {
is ScreenRecordChipModel.DoingNothing -> OngoingActivityChipModel.Hidden
- // TODO(b/332662551): Implement the 3-2-1 countdown chip.
- is ScreenRecordChipModel.Starting -> OngoingActivityChipModel.Hidden
+ is ScreenRecordChipModel.Starting -> {
+ OngoingActivityChipModel.Shown.Countdown(
+ colors = ColorsModel.Red,
+ secondsUntilStarted = state.millisUntilStarted.toCountdownSeconds(),
+ )
+ }
is ScreenRecordChipModel.Recording -> {
- OngoingActivityChipModel.Shown(
+ OngoingActivityChipModel.Shown.Timer(
// TODO(b/332662551): Also provide a content description.
icon = Icon.Resource(ICON, contentDescription = null),
colors = ColorsModel.Red,
@@ -71,7 +76,8 @@
}
}
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+ // See b/347726238.
+ .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
private fun createDelegate(
recordedTask: ActivityManager.RunningTaskInfo?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 0c24a70..c3b1456 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -66,7 +66,8 @@
}
}
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+ // See b/347726238.
+ .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
/** Stops the currently active projection. */
private fun stopProjecting() {
@@ -76,7 +77,7 @@
private fun createShareToAppChip(
state: ProjectionChipModel.Projecting,
): OngoingActivityChipModel.Shown {
- return OngoingActivityChipModel.Shown(
+ return OngoingActivityChipModel.Shown.Timer(
// TODO(b/332662551): Use the right content description.
icon = Icon.Resource(SHARE_TO_APP_ICON, contentDescription = null),
colors = ColorsModel.Red,
@@ -97,7 +98,6 @@
)
companion object {
- // TODO(b/332662551): Use the right icon.
- @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_screenshot_share
+ @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 4ea674a..57f609b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -25,22 +25,42 @@
data object Hidden : OngoingActivityChipModel()
/** This chip should be shown with the given information. */
- data class Shown(
- /** The icon to show on the chip. */
- val icon: Icon,
+ abstract class Shown(
+ /** The icon to show on the chip. If null, no icon will be shown. */
+ open val icon: Icon?,
/** What colors to use for the chip. */
- val colors: ColorsModel,
+ open val colors: ColorsModel,
/**
- * The time this event started, used to show the timer.
- *
- * This time should be relative to
- * [com.android.systemui.util.time.SystemClock.elapsedRealtime], *not*
- * [com.android.systemui.util.time.SystemClock.currentTimeMillis] because the
- * [ChipChronometer] is based off of elapsed realtime. See
- * [android.widget.Chronometer.setBase].
+ * Listener method to invoke when this chip is clicked. If null, the chip won't be
+ * clickable.
*/
- val startTimeMs: Long,
- /** Listener method to invoke when this chip is clicked. */
- val onClickListener: View.OnClickListener,
- ) : OngoingActivityChipModel()
+ open val onClickListener: View.OnClickListener?,
+ ) : OngoingActivityChipModel() {
+ /** The chip shows a timer, counting up from [startTimeMs]. */
+ data class Timer(
+ override val icon: Icon,
+ override val colors: ColorsModel,
+ /**
+ * The time this event started, used to show the timer.
+ *
+ * This time should be relative to
+ * [com.android.systemui.util.time.SystemClock.elapsedRealtime], *not*
+ * [com.android.systemui.util.time.SystemClock.currentTimeMillis] because the
+ * [ChipChronometer] is based off of elapsed realtime. See
+ * [android.widget.Chronometer.setBase].
+ */
+ val startTimeMs: Long,
+ override val onClickListener: View.OnClickListener?,
+ ) : Shown(icon, colors, onClickListener)
+
+ /**
+ * This chip shows a countdown using [secondsUntilStarted]. Used to inform users that an
+ * event is about to start. Typically, a [Countdown] chip will turn into a [Timer] chip.
+ */
+ data class Countdown(
+ override val colors: ColorsModel,
+ /** The number of seconds until an event is started. */
+ val secondsUntilStarted: Long,
+ ) : Shown(icon = null, colors, onClickListener = null)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 1b79ce4..9c8086f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -72,5 +72,8 @@
else -> call
}
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+ // Some of the chips could have timers in them and we don't want the start time
+ // for those timers to get reset for any reason. So, as soon as any subscriber has
+ // requested the chip information, we need to maintain it forever. See b/347726238.
+ .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 2371eed..3ba62b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -44,6 +44,7 @@
import androidx.lifecycle.Observer;
+import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
@@ -57,6 +58,7 @@
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.screenrecord.data.model.ScreenRecordModel;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
@@ -354,7 +356,11 @@
mProvisionedController.addCallback(this);
mCurrentUserSetup = mProvisionedController.isCurrentUserSetup();
mZenController.addCallback(this);
- mCast.addCallback(mCastCallback);
+ if (!Flags.statusBarScreenSharingChips()) {
+ // If the flag is enabled, the cast icon is handled in the new screen sharing chips
+ // instead of here so we don't need to listen for events here.
+ mCast.addCallback(mCastCallback);
+ }
mHotspot.addCallback(mHotspotCallback);
mNextAlarmController.addCallback(mNextAlarmCallback);
mDataSaver.addCallback(this);
@@ -362,7 +368,11 @@
mPrivacyItemController.addCallback(this);
mSensorPrivacyController.addCallback(mSensorPrivacyListener);
mLocationController.addCallback(this);
- mRecordingController.addCallback(this);
+ if (!Flags.statusBarScreenSharingChips()) {
+ // If the flag is enabled, the screen record icon is handled in the new screen sharing
+ // chips instead of here so we don't need to listen for events here.
+ mRecordingController.addCallback(this);
+ }
mJavaAdapter.alwaysCollectFlow(mConnectedDisplayInteractor.getConnectedDisplayState(),
this::onConnectedDisplayAvailabilityChanged);
@@ -519,6 +529,11 @@
}
private void updateCast() {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The cast icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
+
boolean isCasting = false;
for (CastDevice device : mCast.getCastDevices()) {
if (device.isCasting()) {
@@ -788,6 +803,10 @@
private Runnable mRemoveCastIconRunnable = new Runnable() {
@Override
public void run() {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The cast icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
if (DEBUG) Log.v(TAG, "updateCast: hiding icon NOW");
mIconController.setIconVisibility(mSlotCast, false);
}
@@ -796,8 +815,13 @@
// Screen Recording
@Override
public void onCountdown(long millisUntilFinished) {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The screen record icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
if (DEBUG) Log.d(TAG, "screenrecord: countdown " + millisUntilFinished);
- int countdown = (int) Math.floorDiv(millisUntilFinished + 500, 1000);
+ int countdown =
+ (int) ScreenRecordModel.Starting.Companion.toCountdownSeconds(millisUntilFinished);
int resourceId = R.drawable.stat_sys_screen_record;
String description = Integer.toString(countdown);
switch (countdown) {
@@ -820,6 +844,10 @@
@Override
public void onCountdownEnd() {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The screen record icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
if (DEBUG) Log.d(TAG, "screenrecord: hiding icon during countdown");
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
// Reset talkback priority
@@ -829,6 +857,10 @@
@Override
public void onRecordingStart() {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The screen record icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
if (DEBUG) Log.d(TAG, "screenrecord: showing icon");
mIconController.setIcon(mSlotScreenRecord,
R.drawable.stat_sys_screen_record,
@@ -838,6 +870,10 @@
@Override
public void onRecordingEnd() {
+ if (Flags.statusBarScreenSharingChips()) {
+ // The screen record icon is handled in the new screen sharing chips instead of here.
+ return;
+ }
// Ensure this is on the main thread
if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index d607ce0..68983a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -22,6 +22,7 @@
import android.graphics.drawable.GradientDrawable
import android.view.View
import android.widget.ImageView
+import android.widget.TextView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags
@@ -92,6 +93,8 @@
chipView.requireViewById(R.id.ongoing_activity_chip_icon)
val chipTimeView: ChipChronometer =
chipView.requireViewById(R.id.ongoing_activity_chip_time)
+ val chipTextView: TextView =
+ chipView.requireViewById(R.id.ongoing_activity_chip_text)
val chipBackgroundView =
chipView.requireViewById<ChipBackgroundContainer>(
R.id.ongoing_activity_chip_background
@@ -101,14 +104,15 @@
when (chipModel) {
is OngoingActivityChipModel.Shown -> {
// Data
- IconViewBinder.bind(chipModel.icon, chipIconView)
- ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView)
+ IconViewBinder.bindNullable(chipModel.icon, chipIconView)
+ setChipMainContent(chipModel, chipTextView, chipTimeView)
chipView.setOnClickListener(chipModel.onClickListener)
// Colors
val textColor = chipModel.colors.text(chipContext)
chipIconView.imageTintList = ColorStateList.valueOf(textColor)
chipTimeView.setTextColor(textColor)
+ chipTextView.setTextColor(textColor)
(chipBackgroundView.background as GradientDrawable).color =
chipModel.colors.background(chipContext)
@@ -117,6 +121,8 @@
)
}
is OngoingActivityChipModel.Hidden -> {
+ // The Chronometer should be stopped to prevent leaks -- see
+ // b/192243808 and [Chronometer.start].
chipTimeView.stop()
listener.onOngoingActivityStatusChanged(
hasOngoingActivity = false
@@ -130,6 +136,61 @@
}
}
+ private fun setChipMainContent(
+ chipModel: OngoingActivityChipModel.Shown,
+ chipTextView: TextView,
+ chipTimeView: ChipChronometer,
+ ) {
+ when (chipModel) {
+ is OngoingActivityChipModel.Shown.Countdown -> {
+ chipTextView.text = chipModel.secondsUntilStarted.toString()
+ chipTextView.visibility = View.VISIBLE
+
+ // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
+ // [Chronometer.start].
+ chipTimeView.stop()
+ chipTimeView.visibility = View.GONE
+ }
+ is OngoingActivityChipModel.Shown.Timer -> {
+ ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView)
+ chipTimeView.visibility = View.VISIBLE
+
+ chipTextView.visibility = View.GONE
+ }
+ }
+ updateChipTextPadding(chipModel, chipTextView, chipTimeView)
+ }
+
+ private fun updateChipTextPadding(
+ chipModel: OngoingActivityChipModel.Shown,
+ chipTextView: TextView,
+ chipTimeView: ChipChronometer,
+ ) {
+ val requiresPadding = chipModel.icon != null
+ if (requiresPadding) {
+ chipTextView.addChipTextPaddingStart()
+ chipTimeView.addChipTextPaddingStart()
+ } else {
+ chipTextView.removeChipTextPaddingStart()
+ chipTimeView.removeChipTextPaddingStart()
+ }
+ }
+
+ private fun View.addChipTextPaddingStart() {
+ this.setPaddingRelative(
+ this.context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_activity_chip_icon_text_padding
+ ),
+ paddingTop,
+ paddingEnd,
+ paddingBottom,
+ )
+ }
+
+ private fun View.removeChipTextPaddingStart() {
+ this.setPaddingRelative(/* start= */ 0, paddingTop, paddingEnd, paddingBottom)
+ }
+
private fun animateLightsOutView(view: View, visible: Boolean) {
view.animate().cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt
new file mode 100644
index 0000000..9331c8d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 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.screenrecord.data.model
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+@SmallTest
+class ScreenRecordModelTest : SysuiTestCase() {
+ @Test
+ fun countdownSeconds_millis0_is0() {
+ assertThat(0L.toCountdownSeconds()).isEqualTo(0)
+ assertThat(ScreenRecordModel.Starting(0L).countdownSeconds).isEqualTo(0)
+ }
+
+ @Test
+ fun countdownSeconds_millis500_isOne() {
+ assertThat(500L.toCountdownSeconds()).isEqualTo(1)
+ assertThat(ScreenRecordModel.Starting(500L).countdownSeconds).isEqualTo(1)
+ }
+
+ @Test
+ fun countdownSeconds_millis999_isOne() {
+ assertThat(999L.toCountdownSeconds()).isEqualTo(1)
+ assertThat(ScreenRecordModel.Starting(999L).countdownSeconds).isEqualTo(1)
+ }
+
+ @Test
+ fun countdownSeconds_millis1000_isOne() {
+ assertThat(1000L.toCountdownSeconds()).isEqualTo(1)
+ assertThat(ScreenRecordModel.Starting(1000L).countdownSeconds).isEqualTo(1)
+ }
+
+ @Test
+ fun countdownSeconds_millis1499_isOne() {
+ assertThat(1499L.toCountdownSeconds()).isEqualTo(1)
+ assertThat(ScreenRecordModel.Starting(1499L).countdownSeconds).isEqualTo(1)
+ }
+
+ @Test
+ fun countdownSeconds_millis1500_isTwo() {
+ assertThat(1500L.toCountdownSeconds()).isEqualTo(2)
+ assertThat(ScreenRecordModel.Starting(1500L).countdownSeconds).isEqualTo(2)
+ }
+
+ @Test
+ fun countdownSeconds_millis1999_isTwo() {
+ assertThat(1599L.toCountdownSeconds()).isEqualTo(2)
+ assertThat(ScreenRecordModel.Starting(1599L).countdownSeconds).isEqualTo(2)
+ }
+
+ @Test
+ fun countdownSeconds_millis2000_isTwo() {
+ assertThat(2000L.toCountdownSeconds()).isEqualTo(2)
+ assertThat(ScreenRecordModel.Starting(2000L).countdownSeconds).isEqualTo(2)
+ }
+
+ @Test
+ fun countdownSeconds_millis2500_isThree() {
+ assertThat(2500L.toCountdownSeconds()).isEqualTo(3)
+ assertThat(ScreenRecordModel.Starting(2500L).countdownSeconds).isEqualTo(3)
+ }
+
+ @Test
+ fun countdownSeconds_millis2999_isThree() {
+ assertThat(2999L.toCountdownSeconds()).isEqualTo(3)
+ assertThat(ScreenRecordModel.Starting(2999L).countdownSeconds).isEqualTo(3)
+ }
+
+ @Test
+ fun countdownSeconds_millis3000_isThree() {
+ assertThat(3000L.toCountdownSeconds()).isEqualTo(3)
+ assertThat(ScreenRecordModel.Starting(3000L).countdownSeconds).isEqualTo(3)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 3606b1b..c3e810e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -69,13 +69,13 @@
}
@Test
- fun chip_inCall_isShown() =
+ fun chip_inCall_isShownAsTimer() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 345, intent = null))
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
}
@Test
@@ -92,7 +92,8 @@
// started 2000ms ago (1000 - 3000). The OngoingActivityChipModel start time needs to be
// relative to elapsedRealtime, so it should be 2000ms before the elapsed realtime set
// on the clock.
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(398_000)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs)
+ .isEqualTo(398_000)
}
@Test
@@ -127,7 +128,8 @@
// Start a call
repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null))
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(398_000)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs)
+ .isEqualTo(398_000)
// End the call
repo.setOngoingCallState(OngoingCallModel.NoCall)
@@ -140,20 +142,18 @@
// Start a new call, which started 1000ms ago
repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 102_000, intent = null))
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(499_000)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs)
+ .isEqualTo(499_000)
}
@Test
- fun chip_inCall_nullIntent_clickListenerDoesNothing() =
+ fun chip_inCall_nullIntent_nullClickListener() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null))
- val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
-
- clickListener.onClick(chipView)
- // Just verify nothing crashes
+ assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull()
}
@Test
@@ -164,8 +164,9 @@
val intent = mock<PendingIntent>()
repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = intent))
val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(intent, null)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index d7935e5..bde668e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -91,7 +91,7 @@
}
@Test
- fun chip_singleTaskState_otherDevicesPackage_isShown() =
+ fun chip_singleTaskState_otherDevicesPackage_isShownAsTimer() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -101,20 +101,20 @@
createTask(taskId = 1),
)
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
}
@Test
- fun chip_entireScreenState_otherDevicesPackage_isShown() =
+ fun chip_entireScreenState_otherDevicesPackage_isShownAsTimer() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
}
@@ -162,7 +162,7 @@
MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
@@ -175,7 +175,7 @@
)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(5678)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678)
}
@Test
@@ -186,8 +186,9 @@
MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
eq(mockCastDialog),
@@ -209,8 +210,9 @@
)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
eq(mockCastDialog),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index fdf0e5d..8e8b082 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -88,29 +88,75 @@
}
@Test
- fun chip_startingState_isHidden() =
+ fun chip_startingState_isShownAsCountdownWithoutIconOrClickListener() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(400)
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Countdown::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown).icon).isNull()
+ assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull()
+ }
+
+ // The millis we typically get from [ScreenRecordRepository] are around 2995, 1995, and 995.
+ @Test
+ fun chip_startingState_millis2995_is3() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2995)
+
+ assertThat((latest as OngoingActivityChipModel.Shown.Countdown).secondsUntilStarted)
+ .isEqualTo(3)
}
@Test
- fun chip_recordingState_isShownWithIcon() =
+ fun chip_startingState_millis1995_is2() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(1995)
+
+ assertThat((latest as OngoingActivityChipModel.Shown.Countdown).secondsUntilStarted)
+ .isEqualTo(2)
+ }
+
+ @Test
+ fun chip_startingState_millis995_is1() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(995)
+
+ assertThat((latest as OngoingActivityChipModel.Shown.Countdown).secondsUntilStarted)
+ .isEqualTo(1)
+ }
+
+ @Test
+ fun chip_recordingState_isShownAsTimerWithIcon() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenrecord)
}
@Test
- fun chip_colorsAreRed() =
+ fun chip_startingState_colorsAreRed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2000L)
+
+ assertThat((latest as OngoingActivityChipModel.Shown).colors).isEqualTo(ColorsModel.Red)
+ }
+
+ @Test
+ fun chip_recordingState_colorsAreRed() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -128,7 +174,7 @@
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
@@ -137,7 +183,7 @@
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(5678)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678)
}
@Test
@@ -148,8 +194,9 @@
mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
// EndScreenRecordingDialogDelegate will test that the dialog has the right message
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
@@ -169,8 +216,9 @@
MediaProjectionState.Projecting.EntireScreen("host.package")
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
// EndScreenRecordingDialogDelegate will test that the dialog has the right message
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
@@ -193,8 +241,9 @@
)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
// EndScreenRecordingDialogDelegate will test that the dialog has the right message
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
index 4c2546e..63c29ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
@@ -59,7 +59,7 @@
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- verify(sysuiDialog).setIcon(R.drawable.ic_screenshot_share)
+ verify(sysuiDialog).setIcon(R.drawable.ic_present_to_all)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 8ea3f4a..2e5f7f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -116,7 +116,7 @@
}
@Test
- fun chip_singleTaskState_normalPackage_isShown() =
+ fun chip_singleTaskState_normalPackage_isShownAsTimer() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -126,22 +126,22 @@
createTask(taskId = 1),
)
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenshot_share)
+ assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all)
}
@Test
- fun chip_entireScreenState_normalPackage_isShown() =
+ fun chip_entireScreenState_normalPackage_isShownAsTimer() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenshot_share)
+ assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all)
}
@Test
@@ -165,7 +165,7 @@
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
@@ -178,7 +178,7 @@
)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(5678)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678)
}
@Test
@@ -189,8 +189,9 @@
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
eq(mockShareDialog),
@@ -211,8 +212,9 @@
)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
- clickListener.onClick(chipView)
+ clickListener!!.onClick(chipView)
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
eq(mockShareDialog),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 912a10a..8bc83cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -35,7 +35,11 @@
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -46,6 +50,7 @@
class OngoingActivityChipsViewModelTest : SysuiTestCase() {
private val kosmos = Kosmos().also { it.testCase = this }
private val testScope = kosmos.testScope
+ private val systemClock = kosmos.fakeSystemClock
private val screenRecordState = kosmos.screenRecordRepository.screenRecordState
private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState
@@ -191,6 +196,39 @@
assertIsCallChip(latest)
}
+ /** Regression test for b/347726238. */
+ @Test
+ fun chip_timerDoesNotResetAfterSubscribersRestart() =
+ testScope.runTest {
+ var latest: OngoingActivityChipModel? = null
+
+ val job1 = underTest.chip.onEach { latest = it }.launchIn(this)
+
+ // Start a chip with a timer
+ systemClock.setElapsedRealtime(1234)
+ screenRecordState.value = ScreenRecordModel.Recording
+
+ runCurrent()
+
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
+
+ // Stop subscribing to the chip flow
+ job1.cancel()
+
+ // Let time pass
+ systemClock.setElapsedRealtime(5678)
+
+ // WHEN we re-subscribe to the chip flow
+ val job2 = underTest.chip.onEach { latest = it }.launchIn(this)
+
+ runCurrent()
+
+ // THEN the old start time is still used
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
+
+ job2.cancel()
+ }
+
companion object {
fun assertIsScreenRecordChip(latest: OngoingActivityChipModel?) {
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
@@ -201,7 +239,7 @@
fun assertIsShareToAppChip(latest: OngoingActivityChipModel?) {
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenshot_share)
+ assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all)
}
fun assertIsCallChip(latest: OngoingActivityChipModel?) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index f2f336c..dfee2ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -21,11 +21,14 @@
import android.app.admin.DevicePolicyResourcesManager
import android.content.SharedPreferences
import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.telecom.TelecomManager
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
@@ -39,6 +42,7 @@
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.policy.BluetoothController
import com.android.systemui.statusbar.policy.CastController
+import com.android.systemui.statusbar.policy.CastDevice
import com.android.systemui.statusbar.policy.DataSaverController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.HotspotController
@@ -54,6 +58,7 @@
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.DateFormatUtil
import com.android.systemui.util.time.FakeSystemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -78,6 +83,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -87,6 +93,8 @@
companion object {
private const val ALARM_SLOT = "alarm"
+ private const val CAST_SLOT = "cast"
+ private const val SCREEN_RECORD_SLOT = "screen_record"
private const val CONNECTED_DISPLAY_SLOT = "connected_display"
private const val MANAGED_PROFILE_SLOT = "managed_profile"
}
@@ -271,6 +279,101 @@
verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
}
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun cast_chipsFlagOff_iconShown() {
+ statusBarPolicy.init()
+ clearInvocations(iconController)
+
+ val callbackCaptor = argumentCaptor<CastController.Callback>()
+ verify(castController).addCallback(callbackCaptor.capture())
+
+ whenever(castController.castDevices)
+ .thenReturn(
+ listOf(
+ CastDevice(
+ "id",
+ "name",
+ "description",
+ CastDevice.CastState.Connected,
+ CastDevice.CastOrigin.MediaProjection,
+ )
+ )
+ )
+ callbackCaptor.firstValue.onCastDevicesChanged()
+
+ verify(iconController).setIconVisibility(CAST_SLOT, true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun cast_chipsFlagOn_noCallbackRegistered() {
+ statusBarPolicy.init()
+
+ verify(castController, never()).addCallback(any())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun screenRecord_chipsFlagOff_iconShown_forAllStates() {
+ statusBarPolicy.init()
+ clearInvocations(iconController)
+
+ val callbackCaptor = argumentCaptor<RecordingController.RecordingStateChangeCallback>()
+ verify(recordingController).addCallback(callbackCaptor.capture())
+
+ callbackCaptor.firstValue.onCountdown(3000)
+ testableLooper.processAllMessages()
+ verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, true)
+ clearInvocations(iconController)
+
+ callbackCaptor.firstValue.onCountdownEnd()
+ testableLooper.processAllMessages()
+ verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, false)
+ clearInvocations(iconController)
+
+ callbackCaptor.firstValue.onRecordingStart()
+ testableLooper.processAllMessages()
+ verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, true)
+ clearInvocations(iconController)
+
+ callbackCaptor.firstValue.onRecordingEnd()
+ testableLooper.processAllMessages()
+ verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, false)
+ clearInvocations(iconController)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun screenRecord_chipsFlagOn_noCallbackRegistered() {
+ statusBarPolicy.init()
+
+ verify(recordingController, never()).addCallback(any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ fun screenRecord_chipsFlagOn_methodsDoNothing() {
+ statusBarPolicy.init()
+ clearInvocations(iconController)
+
+ statusBarPolicy.onCountdown(3000)
+ testableLooper.processAllMessages()
+ verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
+
+ statusBarPolicy.onCountdownEnd()
+ testableLooper.processAllMessages()
+ verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
+
+ statusBarPolicy.onRecordingStart()
+ testableLooper.processAllMessages()
+ verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
+
+ statusBarPolicy.onRecordingEnd()
+ testableLooper.processAllMessages()
+ verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
+ }
+
private fun createAlarmInfo(): AlarmManager.AlarmClockInfo {
return AlarmManager.AlarmClockInfo(10L, null)
}
@@ -315,13 +418,18 @@
private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor {
private val flow = MutableSharedFlow<State>()
+
suspend fun emit(value: State) = flow.emit(value)
+
override val connectedDisplayState: Flow<State>
get() = flow
+
override val connectedDisplayAddition: Flow<Unit>
get() = TODO("Not yet implemented")
+
override val pendingDisplay: Flow<PendingDisplay?>
get() = TODO("Not yet implemented")
+
override val concurrentDisplaysInProgress: Flow<Boolean>
get() = TODO("Not yet implemented")
}