diff --git a/packages/SystemUI/res/layout/quick_settings_header.xml b/packages/SystemUI/res/layout/quick_settings_header.xml
new file mode 100644
index 0000000000000..43197c4001399
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_header.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index a5e37d529bee6..13ca11401c03e 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -40,7 +40,7 @@
27dp
27dp
- 16dp
+ 32dp
6dp
28dp
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d11ab4298b827..bc828ff8efb4e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -290,7 +290,7 @@
106dp
19dp
- 16dp
+ 32dp
48dp
12dp
16dp
@@ -309,6 +309,7 @@
16dp
4dp
0dp
+ 32dp
56dp
0dp
56dp
@@ -333,6 +334,9 @@
32dp
0dp
20dp
+ 16dp
+ 24dp
+ 32dp
16dp
24dp
16dp
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8c59e75315a1f..86cab22a27565 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -666,6 +666,8 @@
Ethernet
+
+ Press & hold on the icons for more options
Do not disturb
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index adb4e33d1a191..8b577400357d0 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -47,6 +47,7 @@ public final class Prefs {
Key.QS_INVERT_COLORS_ADDED,
Key.QS_WORK_ADDED,
Key.QS_NIGHTDISPLAY_ADDED,
+ Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
Key.SEEN_MULTI_USER,
Key.NUM_APPS_LAUNCHED,
Key.HAS_SEEN_RECENTS_ONBOARDING,
@@ -76,6 +77,11 @@ public final class Prefs {
String QS_WORK_ADDED = "QsWorkAdded";
@Deprecated
String QS_NIGHTDISPLAY_ADDED = "QsNightDisplayAdded";
+ /**
+ * Used for tracking how many times the user has seen the long press tooltip in the Quick
+ * Settings panel.
+ */
+ String QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT = "QsLongPressTooltipShownCount";
String SEEN_MULTI_USER = "HasSeenMultiUser";
String NUM_APPS_LAUNCHED = "NumAppsLaunched";
String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 222c6e8274f53..fccd9ceb5c97e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -44,6 +44,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
public static final float EXPANDED_TILE_DELAY = .86f;
+
private final ArrayList mAllViews = new ArrayList<>();
/**
* List of {@link View}s representing Quick Settings that are being animated from the quick QS
@@ -65,6 +66,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
private TouchAnimator mNonfirstPageDelayedAnimator;
private TouchAnimator mBrightnessAnimator;
+ /**
+ * Whether the animation is stable and not in the middle of animating between the collapsed and
+ * expanded states.
+ */
+ private boolean mIsInStableState;
private boolean mOnKeyguard;
private boolean mAllowFancy;
@@ -89,6 +95,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
Log.w(TAG, "QS Not using page layout");
}
panel.setPageListener(this);
+
+ // At time of creation, the QS panel is always considered stable as it's not in the middle
+ // of collapse/expanded.
+ mIsInStableState = true;
}
public void onRtlChanged() {
@@ -243,6 +253,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
} else {
mBrightnessAnimator = null;
}
+ View headerView = mQsPanel.getHeaderView();
+ if (headerView!= null) {
+ firstPageBuilder.addFloat(headerView, "translationY", heightDiff, 0);
+ mAllViews.add(headerView);
+ }
mFirstPageAnimator = firstPageBuilder
.setListener(this)
.build();
@@ -326,11 +341,21 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
@Override
public void onAnimationAtStart() {
+ if (!mIsInStableState) {
+ mQsPanel.onCollapse();
+ }
+ mIsInStableState = true;
+
mQuickQsPanel.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationAtEnd() {
+ if (!mIsInStableState) {
+ mQsPanel.onExpanded();
+ }
+ mIsInStableState = true;
+
mQuickQsPanel.setVisibility(View.INVISIBLE);
final int N = mQuickQsViews.size();
for (int i = 0; i < N; i++) {
@@ -340,6 +365,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
@Override
public void onAnimationStarted() {
+ if (mIsInStableState) {
+ mQsPanel.onAnimating();
+ }
+ mIsInStableState = false;
+
mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
if (mOnFirstPage) {
final int N = mQuickQsViews.size();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index f7c388db0840b..5640be55d2d24 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -18,6 +18,7 @@ package com.android.systemui.qs;
import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
@@ -58,6 +59,7 @@ import java.util.Collection;
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";
protected final Context mContext;
protected final ArrayList mRecords = new ArrayList();
@@ -72,6 +74,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
private BrightnessController mBrightnessController;
protected QSTileHost mHost;
+ protected QSTooltipView mTooltipView;
protected QSSecurityFooter mFooter;
private boolean mGridContentVisible = true;
@@ -94,6 +97,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
setOrientation(VERTICAL);
+ mTooltipView = (QSTooltipView) LayoutInflater.from(mContext)
+ .inflate(R.layout.quick_settings_header, this, false);
+
mBrightnessView = LayoutInflater.from(mContext).inflate(
R.layout.quick_settings_brightness_dialog, this, false);
mTileLayout = new TileLayout(mContext);
@@ -101,7 +107,12 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
Space space = new Space(mContext);
space.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
mContext.getResources().getDimensionPixelSize(R.dimen.qs_footer_height)));
- mScrollLayout = new QSScrollLayout(mContext, mBrightnessView, (View) mTileLayout, space);
+ mScrollLayout = new QSScrollLayout(
+ mContext,
+ mTooltipView,
+ mBrightnessView,
+ (View) mTileLayout,
+ space);
addView(mScrollLayout);
addDivider();
@@ -134,7 +145,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- Dependency.get(TunerService.class).addTunable(this, QS_SHOW_BRIGHTNESS);
+ 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());
}
@@ -166,11 +180,16 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
@Override
public void onTuningChanged(String key, String newValue) {
if (QS_SHOW_BRIGHTNESS.equals(key)) {
- mBrightnessView.setVisibility(newValue == null || Integer.parseInt(newValue) != 0
- ? VISIBLE : GONE);
+ updateViewVisibilityForTuningValue(mBrightnessView, newValue);
+ } else if (QS_SHOW_LONG_PRESS_TOOLTIP.equals(key)) {
+ updateViewVisibilityForTuningValue(mTooltipView, newValue);
}
}
+ private void updateViewVisibilityForTuningValue(View view, @Nullable String newValue) {
+ view.setVisibility(newValue == null || Integer.parseInt(newValue) != 0 ? VISIBLE : GONE);
+ }
+
public void openDetails(String subPanel) {
QSTile tile = getTile(subPanel);
showDetailAdapter(true, tile.getDetailAdapter(), new int[]{getWidth() / 2, 0});
@@ -205,6 +224,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
return mBrightnessView;
}
+ View getHeaderView() {
+ return mTooltipView;
+ }
+
public void setCallback(QSDetail.Callback callback) {
mCallback = callback;
}
@@ -266,11 +289,27 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
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) {
if (mExpanded == expanded) return;
mExpanded = expanded;
+
if (!mExpanded) {
if (mTileLayout instanceof PagedTileLayout) {
((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java
new file mode 100644
index 0000000000000..d1f9741ba6c2d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java
@@ -0,0 +1,122 @@
+/*
+ * 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 83148558ea1d1..1b4b7dfb310f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -124,9 +124,8 @@ public class QuickQSPanel extends QSPanel {
@Override
public void onTuningChanged(String key, String newValue) {
- // No tunings for you.
- if (key.equals(QS_SHOW_BRIGHTNESS)) {
- // No Brightness for you.
+ if (QS_SHOW_BRIGHTNESS.equals(key) || QS_SHOW_LONG_PRESS_TOOLTIP.equals(key)) {
+ // No Brightness or Tooltip for you!
super.onTuningChanged(key, "0");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 65135ab142d71..9fa7bebf6ee0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -99,7 +99,11 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
record.tileView.measure(exactly(mCellWidth), exactly(mCellHeight));
previousView = record.tileView.updateAccessibilityOrder(previousView);
}
- int height = (mCellHeight + mCellMargin) * rows + (mCellMarginTop - mCellMargin);
+
+ // Only include the top margin in our measurement if we have more than 1 row to show.
+ // Otherwise, don't add the extra margin buffer at top.
+ int height = (mCellHeight + mCellMargin) * rows +
+ rows != 0 ? (mCellMarginTop - mCellMargin) : 0;
if (height < 0) height = 0;
setMeasuredDimension(width, height);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
index 37f2528205f70..6263efa2c7114 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
@@ -107,7 +107,7 @@ public class TouchAnimator {
void onAnimationAtStart();
/**
- * Called when the animator moves into a position of "0". Start and end delays are
+ * Called when the animator moves into a position of "1". Start and end delays are
* taken into account, so this position may cover a range of fractional inputs.
*/
void onAnimationAtEnd();
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 7259282935a04..016cbd6f6675b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -42,6 +42,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
+import com.android.systemui.Prefs;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSIconView;
@@ -49,6 +50,7 @@ import com.android.systemui.plugins.qs.QSTile;
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 java.util.ArrayList;
@@ -191,6 +193,11 @@ public abstract class QSTileImpl implements QSTile {
public void longClick() {
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)));
mHandler.sendEmptyMessage(H.LONG_CLICK);
+
+ Prefs.putInt(
+ mContext,
+ Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
+ QSTooltipView.MAX_TOOLTIP_SHOWN_COUNT);
}
public LogMaker populate(LogMaker logMaker) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 1c9c7949a971f..676463407f3ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -37,6 +37,7 @@ import static java.lang.Thread.sleep;
import android.content.Intent;
import android.metrics.LogMaker;
import android.support.test.filters.SmallTest;
+import android.support.test.InstrumentationRegistry;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -73,6 +74,7 @@ public class QSTileImplTest extends SysuiTestCase {
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mHost = mock(QSTileHost.class);
when(mHost.indexOf(spec)).thenReturn(POSITION);
+ when(mHost.getContext()).thenReturn(mContext.getBaseContext());
mTile = spy(new TileImpl(mHost));
mTile.mHandler = mTile.new H(mTestableLooper.getLooper());