<!-- 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,work,cast,night
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night,alarm
</string>
<!-- The tiles to display in QuickSettings -->
<string name="quick_settings_wifi_on_label">Wi-Fi On</string>
<!-- QuickSettings: Wifi detail panel, text when there are no items [CHAR LIMIT=NONE] -->
<string name="quick_settings_wifi_detail_empty_text">No Wi-Fi networks available</string>
+ <!-- QuickSettings: Alarm title [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_alarm_title">Alarm</string>
<!-- QuickSettings: Cast title [CHAR LIMIT=NONE] -->
<string name="quick_settings_cast_title">Cast</string>
<!-- QuickSettings: Cast detail panel, status text when casting [CHAR LIMIT=NONE] -->
public AutoAddTracker(Context context) {
mContext = context;
mAutoAdded = new ArraySet<>(getAdded());
+ // TODO: remove migration code and shared preferences keys after P release
for (String[] convertPref : CONVERT_PREFS) {
if (Prefs.getBoolean(context, convertPref[0], false)) {
setTileAdded(convertPref[1]);
- Prefs.putBoolean(context, convertPref[0], false);
+ Prefs.remove(context, convertPref[0]);
}
}
mContext.getContentResolver().registerContentObserver(
import com.android.systemui.plugins.qs.QSTileView;
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.CastTile;
else if (tileSpec.equals("saver")) return new DataSaverTile(mHost);
else if (tileSpec.equals("night")) return new NightDisplayTile(mHost);
else if (tileSpec.equals("nfc")) return new NfcTile(mHost);
+ else if (tileSpec.equals("alarm")) return new AlarmTile(mHost);
// Intent tiles.
else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec);
else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec);
--- /dev/null
+/*
+ * Copyright (C) 2018 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.qs.tiles;
+
+import static android.service.quicksettings.Tile.STATE_ACTIVE;
+import static android.service.quicksettings.Tile.STATE_UNAVAILABLE;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.QS_ALARM;
+import static com.android.systemui.keyguard.KeyguardSliceProvider.formatNextAlarm;
+
+import android.app.AlarmManager.AlarmClockInfo;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.provider.AlarmClock;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+
+public class AlarmTile extends QSTileImpl implements NextAlarmChangeCallback {
+ private final NextAlarmController mController;
+ private String mNextAlarm;
+ private PendingIntent mIntent;
+
+ public AlarmTile(QSTileHost host) {
+ super(host);
+ mController = Dependency.get(NextAlarmController.class);
+ }
+
+ @Override
+ public State newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+ if (mIntent != null) {
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(mIntent);
+ }
+ }
+
+ @Override
+ protected void handleUpdateState(State state, Object arg) {
+ state.state = mNextAlarm != null ? STATE_ACTIVE : STATE_UNAVAILABLE;
+ state.label = getTileLabel();
+ state.secondaryLabel = mNextAlarm;
+ state.icon = ResourceIcon.get(R.drawable.stat_sys_alarm);
+ ((BooleanState) state).value = mNextAlarm != null;
+ }
+
+ @Override
+ public void onNextAlarmChanged(AlarmClockInfo nextAlarm) {
+ if (nextAlarm != null) {
+ mNextAlarm = formatNextAlarm(mContext, nextAlarm);
+ mIntent = nextAlarm.getShowIntent();
+ } else {
+ mNextAlarm = null;
+ mIntent = null;
+ }
+ refreshState();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return QS_ALARM;
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return new Intent(AlarmClock.ACTION_SET_ALARM);
+ }
+
+ @Override
+ protected void handleSetListening(boolean listening) {
+ if (listening) {
+ mController.addCallback(this);
+ } else {
+ mController.removeCallback(this);
+ }
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mContext.getString(R.string.status_bar_alarm);
+ }
+}
\ No newline at end of file
package com.android.systemui.statusbar.phone;
+import android.app.AlarmManager.AlarmClockInfo;
import android.content.Context;
import android.os.Handler;
-import android.os.Looper;
import android.provider.Settings.Secure;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ColorDisplayController;
import com.android.systemui.Dependency;
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.SecureSetting;
import com.android.systemui.statusbar.policy.DataSaverController.Listener;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotController.Callback;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
/**
* Manages which tiles should be automatically added to QS.
*/
public class AutoTileManager {
-
public static final String HOTSPOT = "hotspot";
public static final String SAVER = "saver";
public static final String INVERSION = "inversion";
public static final String WORK = "work";
public static final String NIGHT = "night";
+ public static final String ALARM = "alarm";
+
private final Context mContext;
private final QSTileHost mHost;
private final Handler mHandler;
private final AutoAddTracker mAutoTracker;
public AutoTileManager(Context context, QSTileHost host) {
- mAutoTracker = new AutoAddTracker(context);
+ this(context, new AutoAddTracker(context), host,
+ new Handler(Dependency.get(Dependency.BG_LOOPER)));
+ }
+
+ @VisibleForTesting
+ AutoTileManager(Context context, AutoAddTracker autoAddTracker, QSTileHost host,
+ Handler handler) {
+ mAutoTracker = autoAddTracker;
mContext = context;
mHost = host;
- mHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
+ mHandler = handler;
if (!mAutoTracker.isAdded(HOTSPOT)) {
Dependency.get(HotspotController.class).addCallback(mHotspotCallback);
}
if (!mAutoTracker.isAdded(WORK)) {
Dependency.get(ManagedProfileController.class).addCallback(mProfileCallback);
}
-
if (!mAutoTracker.isAdded(NIGHT)
- && ColorDisplayController.isAvailable(mContext)) {
+ && ColorDisplayController.isAvailable(mContext)) {
Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback);
}
+ if (!mAutoTracker.isAdded(ALARM)) {
+ Dependency.get(NextAlarmController.class).addCallback(mNextAlarmChangeCallback);
+ }
}
public void destroy() {
- mColorsSetting.setListening(false);
+ if (mColorsSetting != null) {
+ mColorsSetting.setListening(false);
+ }
mAutoTracker.destroy();
Dependency.get(HotspotController.class).removeCallback(mHotspotCallback);
Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener);
Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback);
Dependency.get(ColorDisplayController.class).setListener(null);
+ Dependency.get(NextAlarmController.class).removeCallback(mNextAlarmChangeCallback);
}
private final ManagedProfileController.Callback mProfileCallback =
}
};
+ private final NextAlarmChangeCallback mNextAlarmChangeCallback = new NextAlarmChangeCallback() {
+ @Override
+ public void onNextAlarmChanged(AlarmClockInfo nextAlarm) {
+ if (mAutoTracker.isAdded(ALARM)) return;
+ if (nextAlarm != null) {
+ mHost.addTile(ALARM);
+ mAutoTracker.setTileAdded(ALARM);
+ mHandler.post(() -> Dependency.get(NextAlarmController.class)
+ .removeCallback(mNextAlarmChangeCallback));
+ }
+ }
+ };
+
@VisibleForTesting
final ColorDisplayController.Callback mColorDisplayCallback =
new ColorDisplayController.Callback() {
import com.android.systemui.Prefs.Key;
import com.android.systemui.SysuiTestCase;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
private AutoAddTracker mAutoTracker;
+ @Before
+ public void setUp() {
+ Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, "");
+ }
+
@Test
public void testMigration() {
Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true);
assertTrue(mAutoTracker.isAdded(WORK));
assertFalse(mAutoTracker.isAdded(INVERSION));
+ // These keys have been removed; retrieving their values should always return the default.
+ assertTrue(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true ));
assertFalse(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, false));
+ assertTrue(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, true));
assertFalse(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, false));
mAutoTracker.destroy();
mAutoTracker.destroy();
}
-
}
\ No newline at end of file
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.AlarmManager.AlarmClockInfo;
+import android.os.Handler;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
-
import com.android.internal.app.ColorDisplayController;
import com.android.systemui.Dependency;
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSTileHost;
-
-import org.junit.After;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mockito;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
public class AutoTileManagerTest extends SysuiTestCase {
- private QSTileHost mQsTileHost;
+ @Mock private QSTileHost mQsTileHost;
+ @Mock private AutoAddTracker mAutoAddTracker;
+ @Captor private ArgumentCaptor<NextAlarmChangeCallback> mAlarmCallback;
+
private AutoTileManager mAutoTileManager;
@Before
public void setUp() throws Exception {
- mDependency.injectTestDependency(Dependency.BG_LOOPER,
- TestableLooper.get(this).getLooper());
- Prefs.putBoolean(mContext, Key.QS_NIGHTDISPLAY_ADDED, false);
- mQsTileHost = Mockito.mock(QSTileHost.class);
- mAutoTileManager = new AutoTileManager(mContext, mQsTileHost);
- }
-
- @After
- public void tearDown() throws Exception {
- mAutoTileManager = null;
+ MockitoAnnotations.initMocks(this);
+ mDependency.injectMockDependency(NextAlarmController.class);
+ mAutoTileManager = new AutoTileManager(mContext, mAutoAddTracker,
+ mQsTileHost, new Handler(TestableLooper.get(this).getLooper()));
+ verify(Dependency.get(NextAlarmController.class))
+ .addCallback(mAlarmCallback.capture());
}
@Test
ColorDisplayController.AUTO_MODE_DISABLED);
verify(mQsTileHost, never()).addTile("night");
}
+
+ @Test
+ public void alarmTileAdded_whenAlarmSet() {
+ mAlarmCallback.getValue().onNextAlarmChanged(new AlarmClockInfo(0, null));
+
+ verify(mQsTileHost).addTile("alarm");
+ verify(mAutoAddTracker).setTileAdded("alarm");
+ }
+
+ @Test
+ public void alarmTileNotAdded_whenAlarmNotSet() {
+ mAlarmCallback.getValue().onNextAlarmChanged(null);
+
+ verify(mQsTileHost, never()).addTile("alarm");
+ verify(mAutoAddTracker, never()).setTileAdded("alarm");
+ }
+
+ @Test
+ public void alarmTileNotAdded_whenAlreadyAdded() {
+ when(mAutoAddTracker.isAdded("alarm")).thenReturn(true);
+
+ mAlarmCallback.getValue().onNextAlarmChanged(new AlarmClockInfo(0, null));
+
+ verify(mQsTileHost, never()).addTile("alarm");
+ verify(mAutoAddTracker, never()).setTileAdded("alarm");
+ }
}
// OS: P
AUTOFILL_INVALID_PERMISSION = 1289;
+ // OPEN: QS Alarm tile shown
+ // ACTION: QS Alarm tile tapped
+ // SUBTYPE: 0 is off, 1 is on
+ // CATEGORY: QUICK_SETTINGS
+ // OS: P
+ QS_ALARM = 1290;
+
// ---- End P Constants, all P constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS