Merge "Add new Next Alarm tile" into sc-dev am: b938d78c8f
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13807719 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: Ida5242c74bb748cd5b87ca5e82d4ebebb291f027
This commit is contained in:
@@ -107,7 +107,7 @@
|
||||
|
||||
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
|
||||
<string name="quick_settings_tiles_stock" translatable="false">
|
||||
wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls
|
||||
wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls,alarm
|
||||
</string>
|
||||
|
||||
<!-- The tiles to display in QuickSettings -->
|
||||
|
||||
@@ -46,4 +46,6 @@
|
||||
<bool name="flag_navigation_bar_overlay">false</bool>
|
||||
|
||||
<bool name="flag_pm_lite">false</bool>
|
||||
|
||||
<bool name="flag_alarm_tile">false</bool>
|
||||
</resources>
|
||||
|
||||
@@ -2861,4 +2861,7 @@
|
||||
<string name="qs_remove_labels" translatable="false"></string>
|
||||
|
||||
<string name="qs_tile_label_fontFamily" translatable="false">@*android:string/config_headlineFontFamily</string>
|
||||
|
||||
<!-- Secondary label for alarm tile when there is no next alarm information [CHAR LIMIT=20] -->
|
||||
<string name="qs_alarm_tile_no_alarm">No alarm set</string>
|
||||
</resources>
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.android.systemui.plugins.qs.QSTileView;
|
||||
import com.android.systemui.qs.QSHost;
|
||||
import com.android.systemui.qs.external.CustomTile;
|
||||
import com.android.systemui.qs.tiles.AirplaneModeTile;
|
||||
import com.android.systemui.qs.tiles.AlarmTile;
|
||||
import com.android.systemui.qs.tiles.BatterySaverTile;
|
||||
import com.android.systemui.qs.tiles.BluetoothTile;
|
||||
import com.android.systemui.qs.tiles.CameraToggleTile;
|
||||
@@ -91,6 +92,7 @@ public class QSFactoryImpl implements QSFactory {
|
||||
private final Provider<CameraToggleTile> mCameraToggleTileProvider;
|
||||
private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
|
||||
private final Provider<DeviceControlsTile> mDeviceControlsTileProvider;
|
||||
private final Provider<AlarmTile> mAlarmTileProvider;
|
||||
|
||||
private final Lazy<QSHost> mQsHostLazy;
|
||||
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
|
||||
@@ -126,7 +128,8 @@ public class QSFactoryImpl implements QSFactory {
|
||||
Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider,
|
||||
Provider<CameraToggleTile> cameraToggleTileProvider,
|
||||
Provider<MicrophoneToggleTile> microphoneToggleTileProvider,
|
||||
Provider<DeviceControlsTile> deviceControlsTileProvider) {
|
||||
Provider<DeviceControlsTile> deviceControlsTileProvider,
|
||||
Provider<AlarmTile> alarmTileProvider) {
|
||||
mQsHostLazy = qsHostLazy;
|
||||
mCustomTileBuilderProvider = customTileBuilderProvider;
|
||||
|
||||
@@ -157,6 +160,7 @@ public class QSFactoryImpl implements QSFactory {
|
||||
mCameraToggleTileProvider = cameraToggleTileProvider;
|
||||
mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
|
||||
mDeviceControlsTileProvider = deviceControlsTileProvider;
|
||||
mAlarmTileProvider = alarmTileProvider;
|
||||
}
|
||||
|
||||
public QSTile createTile(String tileSpec) {
|
||||
@@ -218,6 +222,8 @@ public class QSFactoryImpl implements QSFactory {
|
||||
return mMicrophoneToggleTileProvider.get();
|
||||
case "controls":
|
||||
return mDeviceControlsTileProvider.get();
|
||||
case "alarm":
|
||||
return mAlarmTileProvider.get();
|
||||
}
|
||||
|
||||
// Custom tiles
|
||||
|
||||
117
packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
Normal file
117
packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
Normal file
@@ -0,0 +1,117 @@
|
||||
package com.android.systemui.qs.tiles
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.AlarmManager.AlarmClockInfo
|
||||
import android.content.Intent
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.AlarmClock
|
||||
import android.service.quicksettings.Tile
|
||||
import android.text.TextUtils
|
||||
import android.text.format.DateFormat
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.internal.logging.MetricsLogger
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.plugins.qs.QSTile
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.qs.QSHost
|
||||
import com.android.systemui.qs.logging.QSLogger
|
||||
import com.android.systemui.qs.tileimpl.QSTileImpl
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.statusbar.FeatureFlags
|
||||
import com.android.systemui.statusbar.policy.NextAlarmController
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
class AlarmTile @Inject constructor(
|
||||
host: QSHost,
|
||||
@Background backgroundLooper: Looper,
|
||||
@Main mainHandler: Handler,
|
||||
metricsLogger: MetricsLogger,
|
||||
statusBarStateController: StatusBarStateController,
|
||||
activityStarter: ActivityStarter,
|
||||
qsLogger: QSLogger,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val userTracker: UserTracker,
|
||||
nextAlarmController: NextAlarmController
|
||||
) : QSTileImpl<QSTile.State>(
|
||||
host,
|
||||
backgroundLooper,
|
||||
mainHandler,
|
||||
metricsLogger,
|
||||
statusBarStateController,
|
||||
activityStarter,
|
||||
qsLogger
|
||||
) {
|
||||
|
||||
private var lastAlarmInfo: AlarmManager.AlarmClockInfo? = null
|
||||
private val icon = ResourceIcon.get(R.drawable.ic_alarm)
|
||||
@VisibleForTesting
|
||||
internal val defaultIntent = Intent(AlarmClock.ACTION_SET_ALARM)
|
||||
private val callback = NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
|
||||
lastAlarmInfo = nextAlarm
|
||||
refreshState()
|
||||
}
|
||||
|
||||
init {
|
||||
nextAlarmController.observe(this, callback)
|
||||
}
|
||||
|
||||
override fun isAvailable(): Boolean {
|
||||
return featureFlags.isAlarmTileAvailable
|
||||
}
|
||||
|
||||
override fun newTileState(): QSTile.State {
|
||||
return QSTile.State().apply {
|
||||
handlesLongClick = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun startDefaultSetAlarm() {
|
||||
mActivityStarter.postStartActivityDismissingKeyguard(defaultIntent, 0)
|
||||
}
|
||||
|
||||
override fun handleClick() {
|
||||
lastAlarmInfo?.showIntent?.let {
|
||||
mActivityStarter.postStartActivityDismissingKeyguard(it)
|
||||
} ?: startDefaultSetAlarm()
|
||||
}
|
||||
|
||||
override fun handleUpdateState(state: QSTile.State, arg: Any?) {
|
||||
state.icon = icon
|
||||
state.label = tileLabel
|
||||
lastAlarmInfo?.let {
|
||||
state.secondaryLabel = formatNextAlarm(it)
|
||||
state.state = Tile.STATE_ACTIVE
|
||||
} ?: run {
|
||||
state.secondaryLabel = mContext.getString(R.string.qs_alarm_tile_no_alarm)
|
||||
state.state = Tile.STATE_INACTIVE
|
||||
}
|
||||
state.contentDescription = TextUtils.concat(state.label, ", ", state.secondaryLabel)
|
||||
}
|
||||
|
||||
override fun getTileLabel(): CharSequence {
|
||||
return mContext.getString(R.string.status_bar_alarm)
|
||||
}
|
||||
|
||||
private fun formatNextAlarm(info: AlarmClockInfo): String {
|
||||
val skeleton = if (use24HourFormat()) "EHm" else "Ehma"
|
||||
val pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton)
|
||||
return DateFormat.format(pattern, info.triggerTime).toString()
|
||||
}
|
||||
|
||||
private fun use24HourFormat(): Boolean {
|
||||
return DateFormat.is24HourFormat(mContext, userTracker.userId)
|
||||
}
|
||||
|
||||
override fun getMetricsCategory(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getLongClickIntent(): Intent? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -90,4 +90,8 @@ public class FeatureFlags {
|
||||
public boolean isPMLiteEnabled() {
|
||||
return mFlagReader.isEnabled(R.bool.flag_pm_lite);
|
||||
}
|
||||
|
||||
public boolean isAlarmTileAvailable() {
|
||||
return mFlagReader.isEnabled(R.bool.flag_alarm_tile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.android.systemui.qs.tiles
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.os.Handler
|
||||
import android.service.quicksettings.Tile
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.logging.MetricsLogger
|
||||
import com.android.internal.logging.UiEventLogger
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.qs.QSHost
|
||||
import com.android.systemui.qs.logging.QSLogger
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.statusbar.FeatureFlags
|
||||
import com.android.systemui.statusbar.policy.NextAlarmController
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
class AlarmTileTest : SysuiTestCase() {
|
||||
|
||||
@Mock
|
||||
private lateinit var qsHost: QSHost
|
||||
@Mock
|
||||
private lateinit var metricsLogger: MetricsLogger
|
||||
@Mock
|
||||
private lateinit var statusBarStateController: StatusBarStateController
|
||||
@Mock
|
||||
private lateinit var activityStarter: ActivityStarter
|
||||
@Mock
|
||||
private lateinit var qsLogger: QSLogger
|
||||
@Mock
|
||||
private lateinit var featureFlags: FeatureFlags
|
||||
@Mock
|
||||
private lateinit var userTracker: UserTracker
|
||||
@Mock
|
||||
private lateinit var nextAlarmController: NextAlarmController
|
||||
@Mock
|
||||
private lateinit var uiEventLogger: UiEventLogger
|
||||
@Mock
|
||||
private lateinit var pendingIntent: PendingIntent
|
||||
@Captor
|
||||
private lateinit var callbackCaptor: ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
|
||||
|
||||
private lateinit var testableLooper: TestableLooper
|
||||
private lateinit var tile: AlarmTile
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
testableLooper = TestableLooper.get(this)
|
||||
|
||||
`when`(qsHost.context).thenReturn(mContext)
|
||||
`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
|
||||
|
||||
tile = AlarmTile(
|
||||
qsHost,
|
||||
testableLooper.looper,
|
||||
Handler(testableLooper.looper),
|
||||
metricsLogger,
|
||||
statusBarStateController,
|
||||
activityStarter,
|
||||
qsLogger,
|
||||
featureFlags,
|
||||
userTracker,
|
||||
nextAlarmController
|
||||
)
|
||||
|
||||
verify(nextAlarmController).observe(eq(tile), capture(callbackCaptor))
|
||||
tile.refreshState()
|
||||
testableLooper.processAllMessages()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotAvailableFeatureFlag() {
|
||||
`when`(featureFlags.isAlarmTileAvailable).thenReturn(false)
|
||||
assertThat(tile.isAvailable).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAvailableFeatureFlag() {
|
||||
`when`(featureFlags.isAlarmTileAvailable).thenReturn(true)
|
||||
assertThat(tile.isAvailable).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDoesntHandleLongClick() {
|
||||
assertThat(tile.state.handlesLongClick).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInactiveByDefault() {
|
||||
assertThat(tile.state.state).isEqualTo(Tile.STATE_INACTIVE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInactiveAfterNullNextAlarm() {
|
||||
callbackCaptor.value.onNextAlarmChanged(null)
|
||||
|
||||
testableLooper.processAllMessages()
|
||||
assertThat(tile.state.state).isEqualTo(Tile.STATE_INACTIVE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityStartedWhenNullNextAlarm() {
|
||||
callbackCaptor.value.onNextAlarmChanged(null)
|
||||
tile.click()
|
||||
|
||||
testableLooper.processAllMessages()
|
||||
verify(activityStarter).postStartActivityDismissingKeyguard(tile.defaultIntent, 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActiveAfterNextAlarm() {
|
||||
val alarmInfo = AlarmManager.AlarmClockInfo(1L, pendingIntent)
|
||||
callbackCaptor.value.onNextAlarmChanged(alarmInfo)
|
||||
|
||||
testableLooper.processAllMessages()
|
||||
assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityStartedWhenNextAlarm() {
|
||||
val alarmInfo = AlarmManager.AlarmClockInfo(1L, pendingIntent)
|
||||
callbackCaptor.value.onNextAlarmChanged(alarmInfo)
|
||||
tile.click()
|
||||
|
||||
testableLooper.processAllMessages()
|
||||
verify(activityStarter).postStartActivityDismissingKeyguard(pendingIntent)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user