Add alarm tile to QS.

- 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
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
index f960dc5..2a2bc09 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
@@ -51,10 +51,11 @@
     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(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 77c3bfa..bf9746e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -23,6 +23,7 @@
 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 @@
         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
index 0000000..ff3fe73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java
@@ -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
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 36f9f6b..b220686 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -14,16 +14,13 @@
 
 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.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 @@
         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 @@
         }
     };
 
+    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() {