OSDN Git Service

Add alarm tile to QS.
authorAmin Shaikh <ashaikh@google.com>
Mon, 29 Jan 2018 14:52:15 +0000 (09:52 -0500)
committerAmin Shaikh <ashaikh@google.com>
Wed, 31 Jan 2018 19:38:23 +0000 (14:38 -0500)
- Add an alarm tile to QS
- Add the tile to QS the first time the user creates an alarm
- Tapping on the tile navigates to alarm settings
- Added unit tests for alarm tile
- Updated AutoAddTracker to remove deprecated shared preferences values
so the keys can be removed in a later release

Bug: 70799533
Test: manual testing the alarm QS tile behavior
Change-Id: I2b10468c41b4720b66c9e7bb32e22eb958c199f7

packages/SystemUI/res/values/config.xml
packages/SystemUI/res/values/strings.xml
packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
proto/src/metrics_constants.proto

index 1cc1cc8..ae910fe 100644 (file)
 
     <!-- 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 -->
index 72615ce..5557f8e 100644 (file)
     <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] -->
index f960dc5..2a2bc09 100644 (file)
@@ -51,10 +51,11 @@ public class AutoAddTracker {
     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(
index 77c3bfa..bf9746e 100644 (file)
@@ -23,6 +23,7 @@ import com.android.systemui.plugins.qs.*;
 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;
@@ -69,6 +70,7 @@ public class QSFactoryImpl implements QSFactory {
         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);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java
new file mode 100644 (file)
index 0000000..ff3fe73
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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
index 36f9f6b..b220686 100644 (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;
@@ -31,27 +28,37 @@ import com.android.systemui.statusbar.policy.DataSaverController;
 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);
         }
@@ -76,20 +83,25 @@ public class AutoTileManager {
         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 =
@@ -138,6 +150,19 @@ public class AutoTileManager {
         }
     };
 
+    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() {
index 40f8059..dfc1852 100644 (file)
@@ -30,6 +30,7 @@ import com.android.systemui.Prefs;
 import com.android.systemui.Prefs.Key;
 import com.android.systemui.SysuiTestCase;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,6 +41,11 @@ public class AutoAddTrackerTest extends SysuiTestCase {
 
     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);
@@ -50,7 +56,10 @@ public class AutoAddTrackerTest extends SysuiTestCase {
         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();
@@ -96,5 +105,4 @@ public class AutoAddTrackerTest extends SysuiTestCase {
 
         mAutoTracker.destroy();
     }
-
 }
\ No newline at end of file
index a95e3db..2d2db1b 100644 (file)
@@ -18,45 +18,48 @@ package com.android.systemui.statusbar.phone;
 
 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
@@ -106,4 +109,30 @@ public class AutoTileManagerTest extends SysuiTestCase {
                 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");
+    }
 }
index 7539d88..a2d69a8 100644 (file)
@@ -5173,6 +5173,13 @@ message MetricsEvent {
     // 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