Add alarm info to QS Panel

Added alarm info in the same space as the tooltip. Moved the header info
out to the QuickStatusBarHeader to consolidate all header views.

Since, theoretically, there can be some weird races between the
animations of the long press tooltip and the alarm info, added
protective animation state code in the header (such as fading out the
long press tooltip before animating in the alarm text).

Fully removed the alarm tile.

Test: Visually/manually
Bug: 73764084
Change-Id: Ic1ec450e560a56567f9efeb04d3efbd916f05bc3
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 175cddc..2a4bb60 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -66,10 +66,6 @@
     private TouchAnimator mNonfirstPageDelayedAnimator;
     private TouchAnimator mBrightnessAnimator;
 
-    /**
-     * Whether we're in the middle of animating between the collapsed and expanded states.
-     */
-    private boolean mIsAnimating;
     private boolean mOnKeyguard;
 
     private boolean mAllowFancy;
@@ -94,9 +90,6 @@
             Log.w(TAG, "QS Not using page layout");
         }
         panel.setPageListener(this);
-
-        // At time of creation, the QS panel is never animating.
-        mIsAnimating = false;
     }
 
     public void onRtlChanged() {
@@ -251,11 +244,6 @@
             } else {
                 mBrightnessAnimator = null;
             }
-            View headerView = mQsPanel.getHeaderView();
-            if (headerView!= null) {
-                firstPageBuilder.addFloat(headerView, "translationY", heightDiff, 0);
-                mAllViews.add(headerView);
-            }
             mFirstPageAnimator = firstPageBuilder
                     .setListener(this)
                     .build();
@@ -342,21 +330,11 @@
 
     @Override
     public void onAnimationAtStart() {
-        if (mIsAnimating) {
-            mQsPanel.onCollapse();
-        }
-        mIsAnimating = false;
-
         mQuickQsPanel.setVisibility(View.VISIBLE);
     }
 
     @Override
     public void onAnimationAtEnd() {
-        if (mIsAnimating) {
-            mQsPanel.onExpanded();
-        }
-        mIsAnimating = false;
-
         mQuickQsPanel.setVisibility(View.INVISIBLE);
         final int N = mQuickQsViews.size();
         for (int i = 0; i < N; i++) {
@@ -366,11 +344,6 @@
 
     @Override
     public void onAnimationStarted() {
-        if (!mIsAnimating) {
-            mQsPanel.onAnimating();
-        }
-        mIsAnimating = true;
-
         mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
         if (mOnFirstPage) {
             final int N = mQuickQsViews.size();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index d437f49..5758762 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -272,21 +272,27 @@
         mContainer.setExpansion(expansion);
         final float translationScaleY = expansion - 1;
         if (!mHeaderAnimating) {
-            int height = mHeader.getHeight();
-            getView().setTranslationY(mKeyguardShowing ? (translationScaleY * height)
-                    : headerTranslation);
+            getView().setTranslationY(
+                    mKeyguardShowing
+                            ? translationScaleY * mHeader.getHeight()
+                            : headerTranslation);
         }
         if (expansion == mLastQSExpansion) {
             return;
         }
         mLastQSExpansion = expansion;
-        mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
-        mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
+
+        boolean fullyExpanded = expansion == 1;
         int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom()
                 + mFooter.getHeight();
+        float panelTranslationY = translationScaleY * heightDiff;
+
+        // Let the views animate their contents correctly by giving them the necessary context.
+        mHeader.setExpansion(mKeyguardShowing, expansion, panelTranslationY);
+        mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
         mQSPanel.setTranslationY(translationScaleY * heightDiff);
-        boolean fullyExpanded = expansion == 1;
         mQSDetail.setFullyExpanded(fullyExpanded);
+
         if (fullyExpanded) {
             // Always draw within the bounds of the view when fully expanded.
             mQSPanel.setClipBounds(null);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index a92e346..143ad21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -29,7 +29,6 @@
 import android.service.quicksettings.Tile;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
 import android.widget.LinearLayout;
 
@@ -54,11 +53,11 @@
 import java.util.ArrayList;
 import java.util.Collection;
 
-/** View that represents the quick settings tile panel. **/
+/** View that represents the quick settings tile panel (when expanded/pulled down). **/
 public class QSPanel extends LinearLayout implements Tunable, Callback, BrightnessMirrorListener {
 
     public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness";
-    public static final String QS_SHOW_LONG_PRESS_TOOLTIP = "qs_show_long_press";
+    public static final String QS_SHOW_HEADER = "qs_show_header";
 
     protected final Context mContext;
     protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
@@ -74,7 +73,6 @@
     private BrightnessController mBrightnessController;
     protected QSTileHost mHost;
 
-    protected QSTooltipView mTooltipView;
     protected QSSecurityFooter mFooter;
     private boolean mGridContentVisible = true;
 
@@ -96,11 +94,6 @@
 
         setOrientation(VERTICAL);
 
-        mTooltipView = (QSTooltipView) LayoutInflater.from(mContext)
-                .inflate(R.layout.quick_settings_header, this, false);
-
-        addView(mTooltipView);
-
         mBrightnessView = LayoutInflater.from(mContext).inflate(
             R.layout.quick_settings_brightness_dialog, this, false);
         addView(mBrightnessView);
@@ -152,7 +145,6 @@
         super.onAttachedToWindow();
         final TunerService tunerService = Dependency.get(TunerService.class);
         tunerService.addTunable(this, QS_SHOW_BRIGHTNESS);
-        tunerService.addTunable(this, QS_SHOW_LONG_PRESS_TOOLTIP);
 
         if (mHost != null) {
             setTiles(mHost.getTiles());
@@ -186,8 +178,6 @@
     public void onTuningChanged(String key, String newValue) {
         if (QS_SHOW_BRIGHTNESS.equals(key)) {
             updateViewVisibilityForTuningValue(mBrightnessView, newValue);
-        } else if (QS_SHOW_LONG_PRESS_TOOLTIP.equals(key)) {
-            updateViewVisibilityForTuningValue(mTooltipView, newValue);
         }
     }
 
@@ -229,10 +219,6 @@
         return mBrightnessView;
     }
 
-    View getHeaderView() {
-        return mTooltipView;
-    }
-
     public void setCallback(QSDetail.Callback callback) {
         mCallback = callback;
     }
@@ -254,10 +240,7 @@
 
     public void updateResources() {
         final Resources res = mContext.getResources();
-        setPadding(0, 0, 0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
-        mTooltipView.getLayoutParams().height =
-                res.getDimensionPixelSize(R.dimen.qs_header_tooltip_height);
-        mTooltipView.setLayoutParams(mTooltipView.getLayoutParams());
+        setPadding(0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_top), 0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
         for (TileRecord r : mRecords) {
             r.tile.clearState();
         }
@@ -291,21 +274,6 @@
         if (mCustomizePanel != null && mCustomizePanel.isShown()) {
             mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
         }
-
-        // Instantly hide the header here since we don't want it to still be animating.
-        mTooltipView.setVisibility(View.INVISIBLE);
-    }
-
-    /**
-     * Called when the panel is fully animated out/expanded. This is different from the state
-     * tracked by {@link #mExpanded}, which only checks if the panel is even partially pulled out.
-     */
-    public void onExpanded() {
-        mTooltipView.fadeIn();
-    }
-
-    public void onAnimating() {
-        mTooltipView.fadeOut();
     }
 
     public void setExpanded(boolean expanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java
deleted file mode 100644
index d1f9741..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
-import android.os.Handler;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.systemui.Prefs;
-
-import java.util.concurrent.TimeUnit;
-
-
-/**
- * Tooltip/header view for the Quick Settings panel.
- */
-public class QSTooltipView extends LinearLayout {
-
-    private static final int FADE_ANIMATION_DURATION_MS = 300;
-    private static final long AUTO_FADE_OUT_DELAY_MS = TimeUnit.SECONDS.toMillis(6);
-    private static final int TOOLTIP_NOT_YET_SHOWN_COUNT = 0;
-    public static final int MAX_TOOLTIP_SHOWN_COUNT = 3;
-
-    private final Handler mHandler = new Handler();
-    private final Runnable mAutoFadeOutRunnable = () -> fadeOut();
-
-    private int mShownCount;
-
-    public QSTooltipView(Context context) {
-        this(context, null);
-    }
-
-    public QSTooltipView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mShownCount = getStoredShownCount();
-    }
-
-    /** Returns the latest stored tooltip shown count from SharedPreferences. */
-    private int getStoredShownCount() {
-        return Prefs.getInt(
-                mContext,
-                Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
-                TOOLTIP_NOT_YET_SHOWN_COUNT);
-    }
-
-    /**
-     * Fades in the header view if we can show the tooltip - short circuits any running animation.
-     */
-    public void fadeIn() {
-        if (mShownCount < MAX_TOOLTIP_SHOWN_COUNT) {
-            animate().cancel();
-            setVisibility(View.VISIBLE);
-            animate()
-                    .alpha(1f)
-                    .setDuration(FADE_ANIMATION_DURATION_MS)
-                    .setListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mHandler.postDelayed(mAutoFadeOutRunnable, AUTO_FADE_OUT_DELAY_MS);
-                        }
-                    })
-                    .start();
-
-            // Increment and drop the shown count in prefs for the next time we're deciding to
-            // fade in the tooltip. We first sanity check that the tooltip count hasn't changed yet
-            // in prefs (say, from a long press).
-            if (getStoredShownCount() <= mShownCount) {
-                Prefs.putInt(mContext, Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT, ++mShownCount);
-            }
-        }
-    }
-
-    /**
-     * Fades out the header view if it's partially visible - short circuits any running animation.
-     */
-    public void fadeOut() {
-        animate().cancel();
-        if (getVisibility() == View.VISIBLE && getAlpha() != 0f) {
-            mHandler.removeCallbacks(mAutoFadeOutRunnable);
-            animate()
-                    .alpha(0f)
-                    .setDuration(FADE_ANIMATION_DURATION_MS)
-                    .setListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            perhapsMakeViewInvisible();
-                        }
-                    })
-                    .start();
-        } else {
-            perhapsMakeViewInvisible();
-        }
-    }
-
-    /**
-     * Only update visibility if the view is currently being shown. Otherwise, it's already been
-     * hidden by some other manner.
-     */
-    private void perhapsMakeViewInvisible() {
-        if (getVisibility() == View.VISIBLE) {
-            setVisibility(View.INVISIBLE);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index f0684e1..2270b60 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -20,7 +20,6 @@
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.LinearLayout;
 import android.widget.Space;
 
@@ -123,7 +122,7 @@
 
     @Override
     public void onTuningChanged(String key, String newValue) {
-        if (QS_SHOW_BRIGHTNESS.equals(key) || QS_SHOW_LONG_PRESS_TOOLTIP.equals(key)) {
+        if (QS_SHOW_BRIGHTNESS.equals(key)) {
             // No Brightness or Tooltip for you!
             super.onTuningChanged(key, "0");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 4d7333b..78481d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -15,24 +15,30 @@
 package com.android.systemui.qs;
 
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static android.app.StatusBarManager.DISABLE_NONE;
+import static com.android.systemui.keyguard.KeyguardSliceProvider.formatNextAlarm;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.AlarmManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.provider.AlarmClock;
 import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.RelativeLayout;
-import android.widget.TextClock;
+import android.widget.TextView;
 
 import com.android.settingslib.Utils;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.Dependency;
+import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.R.id;
 import com.android.systemui.SysUiServiceProvider;
@@ -43,11 +49,23 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.policy.NextAlarmController;
 
-public class QuickStatusBarHeader extends RelativeLayout
-        implements CommandQueue.Callbacks, View.OnClickListener {
+/**
+ * View that contains the top-most bits of the screen (primarily the status bar with date, time, and
+ * battery) and also contains the {@link QuickQSPanel} along with some of the panel's inner
+ * contents.
+ */
+public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue.Callbacks,
+        View.OnClickListener, NextAlarmController.NextAlarmChangeCallback {
 
-    private ActivityStarter mActivityStarter;
+    /** Delay for auto fading out the long press tooltip after it's fully visible (in ms). */
+    private static final long AUTO_FADE_OUT_DELAY_MS = DateUtils.SECOND_IN_MILLIS * 6;
+    private static final int FADE_ANIMATION_DURATION_MS = 300;
+    private static final int TOOLTIP_NOT_YET_SHOWN_COUNT = 0;
+    public static final int MAX_TOOLTIP_SHOWN_COUNT = 3;
+
+    private final Handler mHandler = new Handler();
 
     private QSPanel mQsPanel;
 
@@ -58,20 +76,39 @@
     protected QuickQSPanel mHeaderQsPanel;
     protected QSTileHost mHost;
     private TintedIconManager mIconManager;
-    private TouchAnimator mAlphaAnimator;
+    private TouchAnimator mStatusIconsAlphaAnimator;
+    private TouchAnimator mHeaderTextContainerAlphaAnimator;
 
     private View mQuickQsStatusIcons;
-
     private View mDate;
+    private View mHeaderTextContainerView;
+    /** View corresponding to the next alarm info (including the icon). */
+    private View mNextAlarmView;
+    /** Tooltip for educating users that they can long press on icons to see more details. */
+    private View mLongPressTooltipView;
+    /** {@link TextView} containing the actual text indicating when the next alarm will go off. */
+    private TextView mNextAlarmTextView;
+
+    private NextAlarmController mAlarmController;
+    private String mNextAlarmText;
+    /** Counts how many times the long press tooltip has been shown to the user. */
+    private int mShownCount;
+
+    /**
+     * Runnable for automatically fading out the long press tooltip (as if it were animating away).
+     */
+    private final Runnable mAutoFadeOutTooltipRunnable = () -> hideLongPressTooltip(false);
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
+
+        mAlarmController = Dependency.get(NextAlarmController.class);
+        mShownCount = getStoredShownCount();
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        Resources res = getResources();
 
         mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
         mDate = findViewById(R.id.date);
@@ -79,8 +116,11 @@
         mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons);
         mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
 
-        // RenderThread is doing more harm than good when touching the header (to expand quick
-        // settings), so disable it for this view
+        // Views corresponding to the header info section (e.g. tooltip and next alarm).
+        mHeaderTextContainerView = findViewById(R.id.header_text_container);
+        mLongPressTooltipView = findViewById(R.id.long_press_tooltip);
+        mNextAlarmView = findViewById(R.id.next_alarm);
+        mNextAlarmTextView = findViewById(R.id.next_alarm_text);
 
         updateResources();
 
@@ -98,8 +138,6 @@
 
         BatteryMeterView battery = findViewById(R.id.battery);
         battery.setForceShowPercent(true);
-
-        mActivityStarter = Dependency.get(ActivityStarter.class);
     }
 
     private void applyDarkness(int id, Rect tintArea, float intensity, int color) {
@@ -129,21 +167,26 @@
     }
 
     private void updateResources() {
-        updateAlphaAnimator();
+        // Update height, especially due to landscape mode restricting space.
+        mHeaderTextContainerView.getLayoutParams().height =
+                mContext.getResources().getDimensionPixelSize(R.dimen.qs_header_tooltip_height);
+        mHeaderTextContainerView.setLayoutParams(mHeaderTextContainerView.getLayoutParams());
+
+        updateStatusIconAlphaAnimator();
+        updateHeaderTextContainerAlphaAnimator();
     }
 
-    private void updateAlphaAnimator() {
-        mAlphaAnimator = new TouchAnimator.Builder()
+    private void updateStatusIconAlphaAnimator() {
+        mStatusIconsAlphaAnimator = new TouchAnimator.Builder()
                 .addFloat(mQuickQsStatusIcons, "alpha", 1, 0)
                 .build();
     }
 
-    public int getCollapsedHeight() {
-        return getHeight();
-    }
-
-    public int getExpandedHeight() {
-        return getHeight();
+    private void updateHeaderTextContainerAlphaAnimator() {
+        mHeaderTextContainerAlphaAnimator = new TouchAnimator.Builder()
+                .addFloat(mHeaderTextContainerView, "alpha", 0, 1)
+                .setStartDelay(.5f)
+                .build();
     }
 
     public void setExpanded(boolean expanded) {
@@ -153,10 +196,47 @@
         updateEverything();
     }
 
-    public void setExpansion(float headerExpansionFraction) {
-        if (mAlphaAnimator != null ) {
-            mAlphaAnimator.setPosition(headerExpansionFraction);
+    /**
+     * Animates the inner contents based on the given expansion details.
+     *
+     * @param isKeyguardShowing whether or not we're showing the keyguard (a.k.a. lockscreen)
+     * @param expansionFraction how much the QS panel is expanded/pulled out (up to 1f)
+     * @param panelTranslationY how much the panel has physically moved down vertically (required
+     *                          for keyguard animations only)
+     */
+    public void setExpansion(boolean isKeyguardShowing, float expansionFraction,
+                             float panelTranslationY) {
+        final float keyguardExpansionFraction = isKeyguardShowing ? 1f : expansionFraction;
+        if (mStatusIconsAlphaAnimator != null) {
+            mStatusIconsAlphaAnimator.setPosition(keyguardExpansionFraction);
         }
+
+        if (isKeyguardShowing) {
+            // If the keyguard is showing, we want to offset the text so that it comes in at the
+            // same time as the panel as it slides down.
+            mHeaderTextContainerView.setTranslationY(panelTranslationY);
+        } else {
+            mHeaderTextContainerView.setTranslationY(0f);
+        }
+
+        if (mHeaderTextContainerAlphaAnimator != null) {
+            mHeaderTextContainerAlphaAnimator.setPosition(keyguardExpansionFraction);
+        }
+
+        // Check the original expansion fraction - we don't want to show the tooltip until the
+        // panel is pulled all the way out.
+        if (expansionFraction == 1f) {
+            // QS is fully expanded, bring in the tooltip.
+            showLongPressTooltip();
+        }
+    }
+
+    /** Returns the latest stored tooltip shown count from SharedPreferences. */
+    private int getStoredShownCount() {
+        return Prefs.getInt(
+                mContext,
+                Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
+                TOOLTIP_NOT_YET_SHOWN_COUNT);
     }
 
     @Override
@@ -191,6 +271,12 @@
         }
         mHeaderQsPanel.setListening(listening);
         mListening = listening;
+
+        if (listening) {
+            mAlarmController.addCallback(this);
+        } else {
+            mAlarmController.removeCallback(this);
+        }
     }
 
     @Override
@@ -201,6 +287,125 @@
         }
     }
 
+    @Override
+    public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
+        mNextAlarmText = nextAlarm != null ? formatNextAlarm(mContext, nextAlarm) : null;
+        if (mNextAlarmText != null) {
+            hideLongPressTooltip(true /* shouldFadeInAlarmText */);
+        } else {
+            hideAlarmText();
+        }
+        updateHeaderTextContainerAlphaAnimator();
+    }
+
+    /**
+     * Animates in the long press tooltip (as long as the next alarm text isn't currently occupying
+     * the space).
+     */
+    public void showLongPressTooltip() {
+        // If we have alarm text to show, don't bother fading in the tooltip.
+        if (!TextUtils.isEmpty(mNextAlarmText)) {
+            return;
+        }
+
+        if (mShownCount < MAX_TOOLTIP_SHOWN_COUNT) {
+            mLongPressTooltipView.animate().cancel();
+            mLongPressTooltipView.setVisibility(View.VISIBLE);
+            mLongPressTooltipView.animate()
+                    .alpha(1f)
+                    .setDuration(FADE_ANIMATION_DURATION_MS)
+                    .setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mHandler.postDelayed(
+                                    mAutoFadeOutTooltipRunnable, AUTO_FADE_OUT_DELAY_MS);
+                        }
+                    })
+                    .start();
+
+            // Increment and drop the shown count in prefs for the next time we're deciding to
+            // fade in the tooltip. We first sanity check that the tooltip count hasn't changed yet
+            // in prefs (say, from a long press).
+            if (getStoredShownCount() <= mShownCount) {
+                Prefs.putInt(mContext, Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT, ++mShownCount);
+            }
+        }
+    }
+
+    /**
+     * Fades out the long press tooltip if it's partially visible - short circuits any running
+     * animation. Additionally has the ability to fade in the alarm info text.
+     *
+     * @param shouldShowAlarmText whether we should fade in the next alarm text
+     */
+    private void hideLongPressTooltip(boolean shouldShowAlarmText) {
+        mLongPressTooltipView.animate().cancel();
+        if (mLongPressTooltipView.getVisibility() == View.VISIBLE
+                && mLongPressTooltipView.getAlpha() != 0f) {
+            mHandler.removeCallbacks(mAutoFadeOutTooltipRunnable);
+            mLongPressTooltipView.animate()
+                    .alpha(0f)
+                    .setDuration(FADE_ANIMATION_DURATION_MS)
+                    .setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mLongPressTooltipView.setVisibility(View.INVISIBLE);
+
+                            if (shouldShowAlarmText) {
+                                showAlarmText();
+                            }
+                        }
+                    })
+                    .start();
+        } else {
+            mLongPressTooltipView.setVisibility(View.INVISIBLE);
+
+            if (shouldShowAlarmText) {
+                showAlarmText();
+            }
+        }
+    }
+
+    /**
+     * Fades in the updated alarm text. Note that if there's already an alarm showing, this will
+     * immediately hide it and fade in the updated time.
+     */
+    private void showAlarmText() {
+        mNextAlarmView.setAlpha(0f);
+        mNextAlarmView.setVisibility(View.VISIBLE);
+        mNextAlarmTextView.setText(mNextAlarmText);
+
+        mNextAlarmView.animate()
+                .alpha(1f)
+                .setDuration(FADE_ANIMATION_DURATION_MS)
+                .start();
+    }
+
+    /**
+     * Fades out and hides the next alarm text. This also resets the text contents to null in
+     * preparation for the next alarm update.
+     */
+    private void hideAlarmText() {
+        if (mNextAlarmView.getVisibility() == View.VISIBLE) {
+            mNextAlarmView.animate()
+                    .alpha(0f)
+                    .setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            // Reset the alpha regardless of how the animation ends for the next
+                            // time we show this view/want to animate it.
+                            mNextAlarmView.setVisibility(View.INVISIBLE);
+                            mNextAlarmView.setAlpha(1f);
+                            mNextAlarmTextView.setText(null);
+                        }
+                    })
+                    .start();
+        } else {
+            // Next alarm view is already hidden, only need to clear the text.
+            mNextAlarmTextView.setText(null);
+        }
+    }
+
     public void updateEverything() {
         post(() -> setClickable(false));
     }
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 bf9746e..77c3bfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -23,7 +23,6 @@
 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;
@@ -70,7 +69,6 @@
         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/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 016cbd6..04dbb88 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -50,7 +50,7 @@
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.qs.PagedTileLayout.TilePage;
 import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.QSTooltipView;
+import com.android.systemui.qs.QuickStatusBarHeader;
 
 import java.util.ArrayList;
 
@@ -197,7 +197,7 @@
         Prefs.putInt(
                 mContext,
                 Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
-                QSTooltipView.MAX_TOOLTIP_SHOWN_COUNT);
+                QuickStatusBarHeader.MAX_TOOLTIP_SHOWN_COUNT);
     }
 
     public LogMaker populate(LogMaker logMaker) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java
deleted file mode 100644
index ff3fe73..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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