Merge "Add new Next Alarm tile" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
b938d78c8f
@@ -107,7 +107,7 @@
|
|||||||
|
|
||||||
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
|
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
|
||||||
<string name="quick_settings_tiles_stock" translatable="false">
|
<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>
|
</string>
|
||||||
|
|
||||||
<!-- The tiles to display in QuickSettings -->
|
<!-- The tiles to display in QuickSettings -->
|
||||||
|
|||||||
@@ -46,4 +46,6 @@
|
|||||||
<bool name="flag_navigation_bar_overlay">false</bool>
|
<bool name="flag_navigation_bar_overlay">false</bool>
|
||||||
|
|
||||||
<bool name="flag_pm_lite">false</bool>
|
<bool name="flag_pm_lite">false</bool>
|
||||||
|
|
||||||
|
<bool name="flag_alarm_tile">false</bool>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2861,4 +2861,7 @@
|
|||||||
<string name="qs_remove_labels" translatable="false"></string>
|
<string name="qs_remove_labels" translatable="false"></string>
|
||||||
|
|
||||||
<string name="qs_tile_label_fontFamily" translatable="false">@*android:string/config_headlineFontFamily</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>
|
</resources>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import com.android.systemui.plugins.qs.QSTileView;
|
|||||||
import com.android.systemui.qs.QSHost;
|
import com.android.systemui.qs.QSHost;
|
||||||
import com.android.systemui.qs.external.CustomTile;
|
import com.android.systemui.qs.external.CustomTile;
|
||||||
import com.android.systemui.qs.tiles.AirplaneModeTile;
|
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.BatterySaverTile;
|
||||||
import com.android.systemui.qs.tiles.BluetoothTile;
|
import com.android.systemui.qs.tiles.BluetoothTile;
|
||||||
import com.android.systemui.qs.tiles.CameraToggleTile;
|
import com.android.systemui.qs.tiles.CameraToggleTile;
|
||||||
@@ -91,6 +92,7 @@ public class QSFactoryImpl implements QSFactory {
|
|||||||
private final Provider<CameraToggleTile> mCameraToggleTileProvider;
|
private final Provider<CameraToggleTile> mCameraToggleTileProvider;
|
||||||
private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
|
private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
|
||||||
private final Provider<DeviceControlsTile> mDeviceControlsTileProvider;
|
private final Provider<DeviceControlsTile> mDeviceControlsTileProvider;
|
||||||
|
private final Provider<AlarmTile> mAlarmTileProvider;
|
||||||
|
|
||||||
private final Lazy<QSHost> mQsHostLazy;
|
private final Lazy<QSHost> mQsHostLazy;
|
||||||
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
|
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
|
||||||
@@ -126,7 +128,8 @@ public class QSFactoryImpl implements QSFactory {
|
|||||||
Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider,
|
Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider,
|
||||||
Provider<CameraToggleTile> cameraToggleTileProvider,
|
Provider<CameraToggleTile> cameraToggleTileProvider,
|
||||||
Provider<MicrophoneToggleTile> microphoneToggleTileProvider,
|
Provider<MicrophoneToggleTile> microphoneToggleTileProvider,
|
||||||
Provider<DeviceControlsTile> deviceControlsTileProvider) {
|
Provider<DeviceControlsTile> deviceControlsTileProvider,
|
||||||
|
Provider<AlarmTile> alarmTileProvider) {
|
||||||
mQsHostLazy = qsHostLazy;
|
mQsHostLazy = qsHostLazy;
|
||||||
mCustomTileBuilderProvider = customTileBuilderProvider;
|
mCustomTileBuilderProvider = customTileBuilderProvider;
|
||||||
|
|
||||||
@@ -157,6 +160,7 @@ public class QSFactoryImpl implements QSFactory {
|
|||||||
mCameraToggleTileProvider = cameraToggleTileProvider;
|
mCameraToggleTileProvider = cameraToggleTileProvider;
|
||||||
mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
|
mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
|
||||||
mDeviceControlsTileProvider = deviceControlsTileProvider;
|
mDeviceControlsTileProvider = deviceControlsTileProvider;
|
||||||
|
mAlarmTileProvider = alarmTileProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QSTile createTile(String tileSpec) {
|
public QSTile createTile(String tileSpec) {
|
||||||
@@ -218,6 +222,8 @@ public class QSFactoryImpl implements QSFactory {
|
|||||||
return mMicrophoneToggleTileProvider.get();
|
return mMicrophoneToggleTileProvider.get();
|
||||||
case "controls":
|
case "controls":
|
||||||
return mDeviceControlsTileProvider.get();
|
return mDeviceControlsTileProvider.get();
|
||||||
|
case "alarm":
|
||||||
|
return mAlarmTileProvider.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom tiles
|
// 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() {
|
public boolean isPMLiteEnabled() {
|
||||||
return mFlagReader.isEnabled(R.bool.flag_pm_lite);
|
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