blob: 2151436e9dd62964ae39b9c8b81e718d2e831d34 [file] [log] [blame]
Jason Monke5b770e2017-03-03 21:49:29 -05001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.systemui.qs;
16
Charles Hece2a7c02017-10-11 20:25:20 +010017import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
Charles Hece2a7c02017-10-11 20:25:20 +010018
Rohan Shahd3cf7562018-02-23 11:12:28 -080019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Lucas Dupin1f7374a2018-02-26 18:08:33 -080021import android.app.ActivityManager;
Rohan Shahd3cf7562018-02-23 11:12:28 -080022import android.app.AlarmManager;
Jason Monke5b770e2017-03-03 21:49:29 -050023import android.content.Context;
Evan Laird4ea2a492018-01-22 11:29:12 -050024import android.content.Intent;
Jason Monke5b770e2017-03-03 21:49:29 -050025import android.content.res.Configuration;
Jason Monke5b770e2017-03-03 21:49:29 -050026import android.graphics.Color;
27import android.graphics.Rect;
Rohan Shahd3cf7562018-02-23 11:12:28 -080028import android.os.Handler;
Evan Laird4ea2a492018-01-22 11:29:12 -050029import android.provider.AlarmClock;
Jason Monke5b770e2017-03-03 21:49:29 -050030import android.support.annotation.VisibleForTesting;
Rohan Shahd3cf7562018-02-23 11:12:28 -080031import android.text.TextUtils;
32import android.text.format.DateUtils;
Jason Monke5b770e2017-03-03 21:49:29 -050033import android.util.AttributeSet;
Jason Monk824ffff2017-04-11 15:49:06 -040034import android.view.View;
Jason Monke5b770e2017-03-03 21:49:29 -050035import android.widget.RelativeLayout;
Rohan Shahd3cf7562018-02-23 11:12:28 -080036import android.widget.TextView;
Jason Monke5b770e2017-03-03 21:49:29 -050037
38import com.android.settingslib.Utils;
39import com.android.systemui.BatteryMeterView;
40import com.android.systemui.Dependency;
Rohan Shahd3cf7562018-02-23 11:12:28 -080041import com.android.systemui.Prefs;
Jason Monke5b770e2017-03-03 21:49:29 -050042import com.android.systemui.R;
Jason Monk01df36f2017-06-07 13:02:47 -040043import com.android.systemui.R.id;
Charles Hece2a7c02017-10-11 20:25:20 +010044import com.android.systemui.SysUiServiceProvider;
Jason Monke5b770e2017-03-03 21:49:29 -050045import com.android.systemui.plugins.ActivityStarter;
46import com.android.systemui.qs.QSDetail.Callback;
Charles Hece2a7c02017-10-11 20:25:20 +010047import com.android.systemui.statusbar.CommandQueue;
Evan Laird95896952018-01-22 19:30:05 -050048import com.android.systemui.statusbar.phone.StatusBarIconController;
49import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
Evan Laird39254d42018-01-18 16:05:30 -050050import com.android.systemui.statusbar.policy.DarkIconDispatcher;
Jason Monk824ffff2017-04-11 15:49:06 -040051import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
Rohan Shahd3cf7562018-02-23 11:12:28 -080052import com.android.systemui.statusbar.policy.NextAlarmController;
Jason Monke5b770e2017-03-03 21:49:29 -050053
Lucas Dupin1f7374a2018-02-26 18:08:33 -080054import java.util.Locale;
55
Rohan Shahd3cf7562018-02-23 11:12:28 -080056/**
57 * View that contains the top-most bits of the screen (primarily the status bar with date, time, and
58 * battery) and also contains the {@link QuickQSPanel} along with some of the panel's inner
59 * contents.
60 */
61public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue.Callbacks,
62 View.OnClickListener, NextAlarmController.NextAlarmChangeCallback {
Jason Monke5b770e2017-03-03 21:49:29 -050063
Rohan Shahd3cf7562018-02-23 11:12:28 -080064 /** Delay for auto fading out the long press tooltip after it's fully visible (in ms). */
65 private static final long AUTO_FADE_OUT_DELAY_MS = DateUtils.SECOND_IN_MILLIS * 6;
66 private static final int FADE_ANIMATION_DURATION_MS = 300;
67 private static final int TOOLTIP_NOT_YET_SHOWN_COUNT = 0;
68 public static final int MAX_TOOLTIP_SHOWN_COUNT = 3;
69
70 private final Handler mHandler = new Handler();
Jason Monke5b770e2017-03-03 21:49:29 -050071
72 private QSPanel mQsPanel;
73
74 private boolean mExpanded;
75 private boolean mListening;
Charles Hece2a7c02017-10-11 20:25:20 +010076 private boolean mQsDisabled;
Jason Monke5b770e2017-03-03 21:49:29 -050077
78 protected QuickQSPanel mHeaderQsPanel;
79 protected QSTileHost mHost;
Evan Laird95896952018-01-22 19:30:05 -050080 private TintedIconManager mIconManager;
Rohan Shahd3cf7562018-02-23 11:12:28 -080081 private TouchAnimator mStatusIconsAlphaAnimator;
82 private TouchAnimator mHeaderTextContainerAlphaAnimator;
Evan Laird95896952018-01-22 19:30:05 -050083
84 private View mQuickQsStatusIcons;
Evan Laird4ea2a492018-01-22 11:29:12 -050085 private View mDate;
Rohan Shahd3cf7562018-02-23 11:12:28 -080086 private View mHeaderTextContainerView;
87 /** View corresponding to the next alarm info (including the icon). */
88 private View mNextAlarmView;
89 /** Tooltip for educating users that they can long press on icons to see more details. */
90 private View mLongPressTooltipView;
91 /** {@link TextView} containing the actual text indicating when the next alarm will go off. */
92 private TextView mNextAlarmTextView;
93
94 private NextAlarmController mAlarmController;
95 private String mNextAlarmText;
96 /** Counts how many times the long press tooltip has been shown to the user. */
97 private int mShownCount;
98
99 /**
100 * Runnable for automatically fading out the long press tooltip (as if it were animating away).
101 */
102 private final Runnable mAutoFadeOutTooltipRunnable = () -> hideLongPressTooltip(false);
Evan Laird4ea2a492018-01-22 11:29:12 -0500103
Jason Monke5b770e2017-03-03 21:49:29 -0500104 public QuickStatusBarHeader(Context context, AttributeSet attrs) {
105 super(context, attrs);
Rohan Shahd3cf7562018-02-23 11:12:28 -0800106
107 mAlarmController = Dependency.get(NextAlarmController.class);
108 mShownCount = getStoredShownCount();
Jason Monke5b770e2017-03-03 21:49:29 -0500109 }
110
111 @Override
112 protected void onFinishInflate() {
113 super.onFinishInflate();
Jason Monke5b770e2017-03-03 21:49:29 -0500114
115 mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
Evan Laird4ea2a492018-01-22 11:29:12 -0500116 mDate = findViewById(R.id.date);
117 mDate.setOnClickListener(this);
Evan Laird95896952018-01-22 19:30:05 -0500118 mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons);
119 mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
Jason Monke5b770e2017-03-03 21:49:29 -0500120
Rohan Shahd3cf7562018-02-23 11:12:28 -0800121 // Views corresponding to the header info section (e.g. tooltip and next alarm).
122 mHeaderTextContainerView = findViewById(R.id.header_text_container);
123 mLongPressTooltipView = findViewById(R.id.long_press_tooltip);
124 mNextAlarmView = findViewById(R.id.next_alarm);
125 mNextAlarmTextView = findViewById(R.id.next_alarm_text);
Jason Monke5b770e2017-03-03 21:49:29 -0500126
127 updateResources();
128
Jason Monk824ffff2017-04-11 15:49:06 -0400129 Rect tintArea = new Rect(0, 0, 0, 0);
Evan Laird95896952018-01-22 19:30:05 -0500130 int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
131 float intensity = colorForeground == Color.WHITE ? 0 : 1;
132 int fillColor = fillColorForIntensity(intensity, getContext());
133
134 // Set light text on the header icons because they will always be on a black background
Evan Laird39254d42018-01-18 16:05:30 -0500135 applyDarkness(R.id.clock, tintArea, 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
Evan Laird95896952018-01-22 19:30:05 -0500136 applyDarkness(id.signal_cluster, tintArea, intensity, colorForeground);
137
138 // Set the correct tint for the status icons so they contrast
139 mIconManager.setTint(fillColor);
Jason Monke5b770e2017-03-03 21:49:29 -0500140
141 BatteryMeterView battery = findViewById(R.id.battery);
142 battery.setForceShowPercent(true);
Jason Monke5b770e2017-03-03 21:49:29 -0500143 }
144
Jason Monk824ffff2017-04-11 15:49:06 -0400145 private void applyDarkness(int id, Rect tintArea, float intensity, int color) {
146 View v = findViewById(id);
147 if (v instanceof DarkReceiver) {
148 ((DarkReceiver) v).onDarkChanged(tintArea, intensity, color);
149 }
150 }
151
Evan Laird95896952018-01-22 19:30:05 -0500152 private int fillColorForIntensity(float intensity, Context context) {
153 if (intensity == 0) {
154 return context.getColor(R.color.light_mode_icon_color_dual_tone_fill);
155 }
156 return context.getColor(R.color.dark_mode_icon_color_dual_tone_fill);
157 }
158
Jason Monke5b770e2017-03-03 21:49:29 -0500159 @Override
160 protected void onConfigurationChanged(Configuration newConfig) {
161 super.onConfigurationChanged(newConfig);
162 updateResources();
163 }
164
165 @Override
166 public void onRtlPropertiesChanged(int layoutDirection) {
167 super.onRtlPropertiesChanged(layoutDirection);
168 updateResources();
169 }
170
171 private void updateResources() {
Rohan Shahd3cf7562018-02-23 11:12:28 -0800172 // Update height, especially due to landscape mode restricting space.
173 mHeaderTextContainerView.getLayoutParams().height =
174 mContext.getResources().getDimensionPixelSize(R.dimen.qs_header_tooltip_height);
175 mHeaderTextContainerView.setLayoutParams(mHeaderTextContainerView.getLayoutParams());
176
177 updateStatusIconAlphaAnimator();
178 updateHeaderTextContainerAlphaAnimator();
Evan Laird95896952018-01-22 19:30:05 -0500179 }
180
Rohan Shahd3cf7562018-02-23 11:12:28 -0800181 private void updateStatusIconAlphaAnimator() {
182 mStatusIconsAlphaAnimator = new TouchAnimator.Builder()
Evan Laird95896952018-01-22 19:30:05 -0500183 .addFloat(mQuickQsStatusIcons, "alpha", 1, 0)
184 .build();
Jason Monke5b770e2017-03-03 21:49:29 -0500185 }
186
Rohan Shahd3cf7562018-02-23 11:12:28 -0800187 private void updateHeaderTextContainerAlphaAnimator() {
188 mHeaderTextContainerAlphaAnimator = new TouchAnimator.Builder()
189 .addFloat(mHeaderTextContainerView, "alpha", 0, 1)
190 .setStartDelay(.5f)
191 .build();
Jason Monke5b770e2017-03-03 21:49:29 -0500192 }
193
194 public void setExpanded(boolean expanded) {
195 if (mExpanded == expanded) return;
196 mExpanded = expanded;
197 mHeaderQsPanel.setExpanded(expanded);
198 updateEverything();
199 }
200
Rohan Shahd3cf7562018-02-23 11:12:28 -0800201 /**
202 * Animates the inner contents based on the given expansion details.
203 *
204 * @param isKeyguardShowing whether or not we're showing the keyguard (a.k.a. lockscreen)
205 * @param expansionFraction how much the QS panel is expanded/pulled out (up to 1f)
206 * @param panelTranslationY how much the panel has physically moved down vertically (required
207 * for keyguard animations only)
208 */
209 public void setExpansion(boolean isKeyguardShowing, float expansionFraction,
210 float panelTranslationY) {
211 final float keyguardExpansionFraction = isKeyguardShowing ? 1f : expansionFraction;
212 if (mStatusIconsAlphaAnimator != null) {
213 mStatusIconsAlphaAnimator.setPosition(keyguardExpansionFraction);
Evan Laird95896952018-01-22 19:30:05 -0500214 }
Rohan Shahd3cf7562018-02-23 11:12:28 -0800215
216 if (isKeyguardShowing) {
217 // If the keyguard is showing, we want to offset the text so that it comes in at the
218 // same time as the panel as it slides down.
219 mHeaderTextContainerView.setTranslationY(panelTranslationY);
220 } else {
221 mHeaderTextContainerView.setTranslationY(0f);
222 }
223
224 if (mHeaderTextContainerAlphaAnimator != null) {
225 mHeaderTextContainerAlphaAnimator.setPosition(keyguardExpansionFraction);
226 }
227
228 // Check the original expansion fraction - we don't want to show the tooltip until the
229 // panel is pulled all the way out.
230 if (expansionFraction == 1f) {
231 // QS is fully expanded, bring in the tooltip.
232 showLongPressTooltip();
233 }
234 }
235
236 /** Returns the latest stored tooltip shown count from SharedPreferences. */
237 private int getStoredShownCount() {
238 return Prefs.getInt(
239 mContext,
240 Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
241 TOOLTIP_NOT_YET_SHOWN_COUNT);
Jason Monke5b770e2017-03-03 21:49:29 -0500242 }
243
244 @Override
Charles Hece2a7c02017-10-11 20:25:20 +0100245 public void disable(int state1, int state2, boolean animate) {
246 final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
247 if (disabled == mQsDisabled) return;
248 mQsDisabled = disabled;
249 mHeaderQsPanel.setDisabledByPolicy(disabled);
Evan Laird19bf52c2018-01-24 19:54:58 -0500250 final int rawHeight = (int) getResources().getDimension(
251 com.android.internal.R.dimen.quick_qs_total_height);
Charles Hece2a7c02017-10-11 20:25:20 +0100252 getLayoutParams().height = disabled ? (rawHeight - mHeaderQsPanel.getHeight()) : rawHeight;
253 }
254
255 @Override
256 public void onAttachedToWindow() {
257 SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
Evan Laird95896952018-01-22 19:30:05 -0500258 Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
Charles Hece2a7c02017-10-11 20:25:20 +0100259 }
260
261 @Override
Jason Monke5b770e2017-03-03 21:49:29 -0500262 @VisibleForTesting
263 public void onDetachedFromWindow() {
264 setListening(false);
Charles Hece2a7c02017-10-11 20:25:20 +0100265 SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
Evan Laird95896952018-01-22 19:30:05 -0500266 Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
Jason Monke5b770e2017-03-03 21:49:29 -0500267 super.onDetachedFromWindow();
268 }
269
270 public void setListening(boolean listening) {
271 if (listening == mListening) {
272 return;
273 }
274 mHeaderQsPanel.setListening(listening);
275 mListening = listening;
Rohan Shahd3cf7562018-02-23 11:12:28 -0800276
277 if (listening) {
278 mAlarmController.addCallback(this);
279 } else {
280 mAlarmController.removeCallback(this);
281 }
Jason Monke5b770e2017-03-03 21:49:29 -0500282 }
283
Evan Laird95896952018-01-22 19:30:05 -0500284 @Override
285 public void onClick(View v) {
286 if(v == mDate){
287 Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent(
288 AlarmClock.ACTION_SHOW_ALARMS),0);
289 }
290 }
291
Rohan Shahd3cf7562018-02-23 11:12:28 -0800292 @Override
293 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800294 mNextAlarmText = nextAlarm != null ? formatNextAlarm(nextAlarm) : null;
Rohan Shahd3cf7562018-02-23 11:12:28 -0800295 if (mNextAlarmText != null) {
296 hideLongPressTooltip(true /* shouldFadeInAlarmText */);
297 } else {
298 hideAlarmText();
299 }
300 updateHeaderTextContainerAlphaAnimator();
301 }
302
303 /**
304 * Animates in the long press tooltip (as long as the next alarm text isn't currently occupying
305 * the space).
306 */
307 public void showLongPressTooltip() {
308 // If we have alarm text to show, don't bother fading in the tooltip.
309 if (!TextUtils.isEmpty(mNextAlarmText)) {
310 return;
311 }
312
313 if (mShownCount < MAX_TOOLTIP_SHOWN_COUNT) {
314 mLongPressTooltipView.animate().cancel();
315 mLongPressTooltipView.setVisibility(View.VISIBLE);
316 mLongPressTooltipView.animate()
317 .alpha(1f)
318 .setDuration(FADE_ANIMATION_DURATION_MS)
319 .setListener(new AnimatorListenerAdapter() {
320 @Override
321 public void onAnimationEnd(Animator animation) {
322 mHandler.postDelayed(
323 mAutoFadeOutTooltipRunnable, AUTO_FADE_OUT_DELAY_MS);
324 }
325 })
326 .start();
327
328 // Increment and drop the shown count in prefs for the next time we're deciding to
329 // fade in the tooltip. We first sanity check that the tooltip count hasn't changed yet
330 // in prefs (say, from a long press).
331 if (getStoredShownCount() <= mShownCount) {
332 Prefs.putInt(mContext, Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT, ++mShownCount);
333 }
334 }
335 }
336
337 /**
338 * Fades out the long press tooltip if it's partially visible - short circuits any running
339 * animation. Additionally has the ability to fade in the alarm info text.
340 *
341 * @param shouldShowAlarmText whether we should fade in the next alarm text
342 */
343 private void hideLongPressTooltip(boolean shouldShowAlarmText) {
344 mLongPressTooltipView.animate().cancel();
345 if (mLongPressTooltipView.getVisibility() == View.VISIBLE
346 && mLongPressTooltipView.getAlpha() != 0f) {
347 mHandler.removeCallbacks(mAutoFadeOutTooltipRunnable);
348 mLongPressTooltipView.animate()
349 .alpha(0f)
350 .setDuration(FADE_ANIMATION_DURATION_MS)
351 .setListener(new AnimatorListenerAdapter() {
352 @Override
353 public void onAnimationEnd(Animator animation) {
354 mLongPressTooltipView.setVisibility(View.INVISIBLE);
355
356 if (shouldShowAlarmText) {
357 showAlarmText();
358 }
359 }
360 })
361 .start();
362 } else {
363 mLongPressTooltipView.setVisibility(View.INVISIBLE);
364
365 if (shouldShowAlarmText) {
366 showAlarmText();
367 }
368 }
369 }
370
371 /**
372 * Fades in the updated alarm text. Note that if there's already an alarm showing, this will
373 * immediately hide it and fade in the updated time.
374 */
375 private void showAlarmText() {
376 mNextAlarmView.setAlpha(0f);
377 mNextAlarmView.setVisibility(View.VISIBLE);
378 mNextAlarmTextView.setText(mNextAlarmText);
379
380 mNextAlarmView.animate()
381 .alpha(1f)
382 .setDuration(FADE_ANIMATION_DURATION_MS)
383 .start();
384 }
385
386 /**
387 * Fades out and hides the next alarm text. This also resets the text contents to null in
388 * preparation for the next alarm update.
389 */
390 private void hideAlarmText() {
391 if (mNextAlarmView.getVisibility() == View.VISIBLE) {
392 mNextAlarmView.animate()
393 .alpha(0f)
394 .setListener(new AnimatorListenerAdapter() {
395 @Override
396 public void onAnimationEnd(Animator animation) {
397 // Reset the alpha regardless of how the animation ends for the next
398 // time we show this view/want to animate it.
399 mNextAlarmView.setVisibility(View.INVISIBLE);
400 mNextAlarmView.setAlpha(1f);
401 mNextAlarmTextView.setText(null);
402 }
403 })
404 .start();
405 } else {
406 // Next alarm view is already hidden, only need to clear the text.
407 mNextAlarmTextView.setText(null);
408 }
409 }
410
Jason Monke5b770e2017-03-03 21:49:29 -0500411 public void updateEverything() {
Jason Monk1fdde2d2017-03-08 09:39:21 -0500412 post(() -> setClickable(false));
Jason Monke5b770e2017-03-03 21:49:29 -0500413 }
414
415 public void setQSPanel(final QSPanel qsPanel) {
416 mQsPanel = qsPanel;
417 setupHost(qsPanel.getHost());
418 }
419
420 public void setupHost(final QSTileHost host) {
421 mHost = host;
422 //host.setHeaderView(mExpandIndicator);
423 mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this);
424 mHeaderQsPanel.setHost(host, null /* No customization in header */);
Evan Lairdef160f22018-01-29 14:08:45 -0500425
426 // Use SystemUI context to get battery meter colors, and let it use the default tint (white)
427 BatteryMeterView battery = findViewById(R.id.battery);
428 battery.setColorsFromContext(mHost.getContext());
429 battery.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
Jason Monke5b770e2017-03-03 21:49:29 -0500430 }
431
432 public void setCallback(Callback qsPanelCallback) {
433 mHeaderQsPanel.setCallback(qsPanelCallback);
434 }
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800435
436 private String formatNextAlarm(AlarmManager.AlarmClockInfo info) {
437 if (info == null) {
438 return "";
439 }
440 String skeleton = android.text.format.DateFormat
441 .is24HourFormat(mContext, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
442 String pattern = android.text.format.DateFormat
443 .getBestDateTimePattern(Locale.getDefault(), skeleton);
444 return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
445 }
Jason Monke5b770e2017-03-03 21:49:29 -0500446}