1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui.statusbar.phone;
18 
19 import android.app.AlarmManager;
20 import android.app.PendingIntent;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.res.Configuration;
24 import android.content.res.Resources;
25 import android.graphics.Outline;
26 import android.graphics.Rect;
27 import android.graphics.drawable.Animatable;
28 import android.graphics.drawable.Drawable;
29 import android.util.AttributeSet;
30 import android.util.MathUtils;
31 import android.util.TypedValue;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.ViewOutlineProvider;
35 import android.widget.ImageView;
36 import android.widget.LinearLayout;
37 import android.widget.RelativeLayout;
38 import android.widget.Switch;
39 import android.widget.TextView;
40 import android.widget.Toast;
41 
42 import com.android.keyguard.KeyguardStatusView;
43 import com.android.systemui.BatteryMeterView;
44 import com.android.systemui.FontSizeUtils;
45 import com.android.systemui.R;
46 import com.android.systemui.qs.QSPanel;
47 import com.android.systemui.qs.QSTile;
48 import com.android.systemui.statusbar.policy.BatteryController;
49 import com.android.systemui.statusbar.policy.NetworkControllerImpl.EmergencyListener;
50 import com.android.systemui.statusbar.policy.NextAlarmController;
51 import com.android.systemui.statusbar.policy.UserInfoController;
52 import com.android.systemui.tuner.TunerService;
53 
54 import java.text.NumberFormat;
55 
56 /**
57  * The view to manage the header area in the expanded status bar.
58  */
59 public class StatusBarHeaderView extends RelativeLayout implements View.OnClickListener,
60         BatteryController.BatteryStateChangeCallback, NextAlarmController.NextAlarmChangeCallback,
61         EmergencyListener {
62 
63     private boolean mExpanded;
64     private boolean mListening;
65 
66     private ViewGroup mSystemIconsContainer;
67     private View mSystemIconsSuperContainer;
68     private View mDateGroup;
69     private View mClock;
70     private TextView mTime;
71     private TextView mAmPm;
72     private MultiUserSwitch mMultiUserSwitch;
73     private ImageView mMultiUserAvatar;
74     private TextView mDateCollapsed;
75     private TextView mDateExpanded;
76     private LinearLayout mSystemIcons;
77     private View mSignalCluster;
78     private SettingsButton mSettingsButton;
79     private View mSettingsContainer;
80     private View mQsDetailHeader;
81     private TextView mQsDetailHeaderTitle;
82     private Switch mQsDetailHeaderSwitch;
83     private ImageView mQsDetailHeaderProgress;
84     private TextView mEmergencyCallsOnly;
85     private TextView mBatteryLevel;
86     private TextView mAlarmStatus;
87 
88     private boolean mShowEmergencyCallsOnly;
89     private boolean mAlarmShowing;
90     private AlarmManager.AlarmClockInfo mNextAlarm;
91 
92     private int mCollapsedHeight;
93     private int mExpandedHeight;
94 
95     private int mMultiUserExpandedMargin;
96     private int mMultiUserCollapsedMargin;
97 
98     private int mClockMarginBottomExpanded;
99     private int mClockMarginBottomCollapsed;
100     private int mMultiUserSwitchWidthCollapsed;
101     private int mMultiUserSwitchWidthExpanded;
102 
103     private int mClockCollapsedSize;
104     private int mClockExpandedSize;
105 
106     /**
107      * In collapsed QS, the clock and avatar are scaled down a bit post-layout to allow for a nice
108      * transition. These values determine that factor.
109      */
110     private float mClockCollapsedScaleFactor;
111     private float mAvatarCollapsedScaleFactor;
112 
113     private ActivityStarter mActivityStarter;
114     private BatteryController mBatteryController;
115     private NextAlarmController mNextAlarmController;
116     private QSPanel mQSPanel;
117 
118     private final Rect mClipBounds = new Rect();
119 
120     private boolean mCaptureValues;
121     private boolean mSignalClusterDetached;
122     private final LayoutValues mCollapsedValues = new LayoutValues();
123     private final LayoutValues mExpandedValues = new LayoutValues();
124     private final LayoutValues mCurrentValues = new LayoutValues();
125 
126     private float mCurrentT;
127     private boolean mShowingDetail;
128     private boolean mDetailTransitioning;
129 
StatusBarHeaderView(Context context, AttributeSet attrs)130     public StatusBarHeaderView(Context context, AttributeSet attrs) {
131         super(context, attrs);
132     }
133 
134     @Override
onFinishInflate()135     protected void onFinishInflate() {
136         super.onFinishInflate();
137         mSystemIconsSuperContainer = findViewById(R.id.system_icons_super_container);
138         mSystemIconsContainer = (ViewGroup) findViewById(R.id.system_icons_container);
139         mSystemIconsSuperContainer.setOnClickListener(this);
140         mDateGroup = findViewById(R.id.date_group);
141         mClock = findViewById(R.id.clock);
142         mTime = (TextView) findViewById(R.id.time_view);
143         mAmPm = (TextView) findViewById(R.id.am_pm_view);
144         mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch);
145         mMultiUserAvatar = (ImageView) findViewById(R.id.multi_user_avatar);
146         mDateCollapsed = (TextView) findViewById(R.id.date_collapsed);
147         mDateExpanded = (TextView) findViewById(R.id.date_expanded);
148         mSettingsButton = (SettingsButton) findViewById(R.id.settings_button);
149         mSettingsContainer = findViewById(R.id.settings_button_container);
150         mSettingsButton.setOnClickListener(this);
151         mQsDetailHeader = findViewById(R.id.qs_detail_header);
152         mQsDetailHeader.setAlpha(0);
153         mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
154         mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
155         mQsDetailHeaderProgress = (ImageView) findViewById(R.id.qs_detail_header_progress);
156         mEmergencyCallsOnly = (TextView) findViewById(R.id.header_emergency_calls_only);
157         mBatteryLevel = (TextView) findViewById(R.id.battery_level);
158         mAlarmStatus = (TextView) findViewById(R.id.alarm_status);
159         mAlarmStatus.setOnClickListener(this);
160         mSignalCluster = findViewById(R.id.signal_cluster);
161         mSystemIcons = (LinearLayout) findViewById(R.id.system_icons);
162         loadDimens();
163         updateVisibilities();
164         updateClockScale();
165         updateAvatarScale();
166         addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
167             @Override
168             public void onLayoutChange(View v, int left, int top, int right,
169                     int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
170                 if ((right - left) != (oldRight - oldLeft)) {
171                     // width changed, update clipping
172                     setClipping(getHeight());
173                 }
174                 boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
175                 mTime.setPivotX(rtl ? mTime.getWidth() : 0);
176                 mTime.setPivotY(mTime.getBaseline());
177                 updateAmPmTranslation();
178             }
179         });
180         setOutlineProvider(new ViewOutlineProvider() {
181             @Override
182             public void getOutline(View view, Outline outline) {
183                 outline.setRect(mClipBounds);
184             }
185         });
186         requestCaptureValues();
187     }
188 
189     @Override
onLayout(boolean changed, int l, int t, int r, int b)190     protected void onLayout(boolean changed, int l, int t, int r, int b) {
191         super.onLayout(changed, l, t, r, b);
192         if (mCaptureValues) {
193             if (mExpanded) {
194                 captureLayoutValues(mExpandedValues);
195             } else {
196                 captureLayoutValues(mCollapsedValues);
197             }
198             mCaptureValues = false;
199             updateLayoutValues(mCurrentT);
200         }
201         mAlarmStatus.setX(mDateGroup.getLeft() + mDateCollapsed.getRight());
202     }
203 
204     @Override
onConfigurationChanged(Configuration newConfig)205     protected void onConfigurationChanged(Configuration newConfig) {
206         super.onConfigurationChanged(newConfig);
207         FontSizeUtils.updateFontSize(mBatteryLevel, R.dimen.battery_level_text_size);
208         FontSizeUtils.updateFontSize(mEmergencyCallsOnly,
209                 R.dimen.qs_emergency_calls_only_text_size);
210         FontSizeUtils.updateFontSize(mDateCollapsed, R.dimen.qs_date_collapsed_size);
211         FontSizeUtils.updateFontSize(mDateExpanded, R.dimen.qs_date_collapsed_size);
212         FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
213         FontSizeUtils.updateFontSize(this, android.R.id.title, R.dimen.qs_detail_header_text_size);
214         FontSizeUtils.updateFontSize(this, android.R.id.toggle, R.dimen.qs_detail_header_text_size);
215         FontSizeUtils.updateFontSize(mAmPm, R.dimen.qs_time_collapsed_size);
216         FontSizeUtils.updateFontSize(this, R.id.empty_time_view, R.dimen.qs_time_expanded_size);
217 
218         mEmergencyCallsOnly.setText(com.android.internal.R.string.emergency_calls_only);
219 
220         mClockCollapsedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size);
221         mClockExpandedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size);
222         mClockCollapsedScaleFactor = (float) mClockCollapsedSize / (float) mClockExpandedSize;
223 
224         updateClockScale();
225         updateClockCollapsedMargin();
226     }
227 
updateClockCollapsedMargin()228     private void updateClockCollapsedMargin() {
229         Resources res = getResources();
230         int padding = res.getDimensionPixelSize(R.dimen.clock_collapsed_bottom_margin);
231         int largePadding = res.getDimensionPixelSize(
232                 R.dimen.clock_collapsed_bottom_margin_large_text);
233         float largeFactor = (MathUtils.constrain(getResources().getConfiguration().fontScale, 1.0f,
234                 FontSizeUtils.LARGE_TEXT_SCALE) - 1f) / (FontSizeUtils.LARGE_TEXT_SCALE - 1f);
235         mClockMarginBottomCollapsed = Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
236         requestLayout();
237     }
238 
requestCaptureValues()239     private void requestCaptureValues() {
240         mCaptureValues = true;
241         requestLayout();
242     }
243 
loadDimens()244     private void loadDimens() {
245         mCollapsedHeight = getResources().getDimensionPixelSize(R.dimen.status_bar_header_height);
246         mExpandedHeight = getResources().getDimensionPixelSize(
247                 R.dimen.status_bar_header_height_expanded);
248         mMultiUserExpandedMargin =
249                 getResources().getDimensionPixelSize(R.dimen.multi_user_switch_expanded_margin);
250         mMultiUserCollapsedMargin =
251                 getResources().getDimensionPixelSize(R.dimen.multi_user_switch_collapsed_margin);
252         mClockMarginBottomExpanded =
253                 getResources().getDimensionPixelSize(R.dimen.clock_expanded_bottom_margin);
254         updateClockCollapsedMargin();
255         mMultiUserSwitchWidthCollapsed =
256                 getResources().getDimensionPixelSize(R.dimen.multi_user_switch_width_collapsed);
257         mMultiUserSwitchWidthExpanded =
258                 getResources().getDimensionPixelSize(R.dimen.multi_user_switch_width_expanded);
259         mAvatarCollapsedScaleFactor =
260                 getResources().getDimensionPixelSize(R.dimen.multi_user_avatar_collapsed_size)
261                 / (float) mMultiUserAvatar.getLayoutParams().width;
262         mClockCollapsedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size);
263         mClockExpandedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size);
264         mClockCollapsedScaleFactor = (float) mClockCollapsedSize / (float) mClockExpandedSize;
265 
266     }
267 
setActivityStarter(ActivityStarter activityStarter)268     public void setActivityStarter(ActivityStarter activityStarter) {
269         mActivityStarter = activityStarter;
270     }
271 
setBatteryController(BatteryController batteryController)272     public void setBatteryController(BatteryController batteryController) {
273         mBatteryController = batteryController;
274         ((BatteryMeterView) findViewById(R.id.battery)).setBatteryController(batteryController);
275     }
276 
setNextAlarmController(NextAlarmController nextAlarmController)277     public void setNextAlarmController(NextAlarmController nextAlarmController) {
278         mNextAlarmController = nextAlarmController;
279     }
280 
getCollapsedHeight()281     public int getCollapsedHeight() {
282         return mCollapsedHeight;
283     }
284 
getExpandedHeight()285     public int getExpandedHeight() {
286         return mExpandedHeight;
287     }
288 
setListening(boolean listening)289     public void setListening(boolean listening) {
290         if (listening == mListening) {
291             return;
292         }
293         mListening = listening;
294         updateListeners();
295     }
296 
setExpanded(boolean expanded)297     public void setExpanded(boolean expanded) {
298         boolean changed = expanded != mExpanded;
299         mExpanded = expanded;
300         if (changed) {
301             updateEverything();
302         }
303     }
304 
updateEverything()305     public void updateEverything() {
306         updateHeights();
307         updateVisibilities();
308         updateSystemIconsLayoutParams();
309         updateClickTargets();
310         updateMultiUserSwitch();
311         updateClockScale();
312         updateAvatarScale();
313         updateClockLp();
314         requestCaptureValues();
315     }
316 
updateHeights()317     private void updateHeights() {
318         int height = mExpanded ? mExpandedHeight : mCollapsedHeight;
319         ViewGroup.LayoutParams lp = getLayoutParams();
320         if (lp.height != height) {
321             lp.height = height;
322             setLayoutParams(lp);
323         }
324     }
325 
updateVisibilities()326     private void updateVisibilities() {
327         mDateCollapsed.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
328         mDateExpanded.setVisibility(mExpanded && mAlarmShowing ? View.INVISIBLE : View.VISIBLE);
329         mAlarmStatus.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
330         mSettingsContainer.setVisibility(mExpanded ? View.VISIBLE : View.INVISIBLE);
331         mQsDetailHeader.setVisibility(mExpanded && mShowingDetail? View.VISIBLE : View.INVISIBLE);
332         if (mSignalCluster != null) {
333             updateSignalClusterDetachment();
334         }
335         mEmergencyCallsOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly ? VISIBLE : GONE);
336         mBatteryLevel.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
337         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
338                 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
339     }
340 
updateSignalClusterDetachment()341     private void updateSignalClusterDetachment() {
342         boolean detached = mExpanded;
343         if (detached != mSignalClusterDetached) {
344             if (detached) {
345                 getOverlay().add(mSignalCluster);
346             } else {
347                 reattachSignalCluster();
348             }
349         }
350         mSignalClusterDetached = detached;
351     }
352 
reattachSignalCluster()353     private void reattachSignalCluster() {
354         getOverlay().remove(mSignalCluster);
355         mSystemIcons.addView(mSignalCluster, 1);
356     }
357 
updateSystemIconsLayoutParams()358     private void updateSystemIconsLayoutParams() {
359         RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsSuperContainer.getLayoutParams();
360         int rule = mExpanded
361                 ? mSettingsContainer.getId()
362                 : mMultiUserSwitch.getId();
363         if (rule != lp.getRules()[RelativeLayout.START_OF]) {
364             lp.addRule(RelativeLayout.START_OF, rule);
365             mSystemIconsSuperContainer.setLayoutParams(lp);
366         }
367     }
368 
updateListeners()369     private void updateListeners() {
370         if (mListening) {
371             mBatteryController.addStateChangedCallback(this);
372             mNextAlarmController.addStateChangedCallback(this);
373         } else {
374             mBatteryController.removeStateChangedCallback(this);
375             mNextAlarmController.removeStateChangedCallback(this);
376         }
377     }
378 
updateAvatarScale()379     private void updateAvatarScale() {
380         if (mExpanded) {
381             mMultiUserAvatar.setScaleX(1f);
382             mMultiUserAvatar.setScaleY(1f);
383         } else {
384             mMultiUserAvatar.setScaleX(mAvatarCollapsedScaleFactor);
385             mMultiUserAvatar.setScaleY(mAvatarCollapsedScaleFactor);
386         }
387     }
388 
updateClockScale()389     private void updateClockScale() {
390         mTime.setTextSize(TypedValue.COMPLEX_UNIT_PX, mExpanded
391                 ? mClockExpandedSize
392                 : mClockCollapsedSize);
393         mTime.setScaleX(1f);
394         mTime.setScaleY(1f);
395         updateAmPmTranslation();
396     }
397 
updateAmPmTranslation()398     private void updateAmPmTranslation() {
399         boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
400         mAmPm.setTranslationX((rtl ? 1 : -1) * mTime.getWidth() * (1 - mTime.getScaleX()));
401     }
402 
403     @Override
onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging)404     public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
405         String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
406         mBatteryLevel.setText(percentage);
407     }
408 
409     @Override
onPowerSaveChanged()410     public void onPowerSaveChanged() {
411         // could not care less
412     }
413 
414     @Override
onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm)415     public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
416         mNextAlarm = nextAlarm;
417         if (nextAlarm != null) {
418             mAlarmStatus.setText(KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm));
419         }
420         mAlarmShowing = nextAlarm != null;
421         updateEverything();
422         requestCaptureValues();
423     }
424 
updateClickTargets()425     private void updateClickTargets() {
426         mMultiUserSwitch.setClickable(mExpanded);
427         mMultiUserSwitch.setFocusable(mExpanded);
428         mSystemIconsSuperContainer.setClickable(mExpanded);
429         mSystemIconsSuperContainer.setFocusable(mExpanded);
430         mAlarmStatus.setClickable(mNextAlarm != null && mNextAlarm.getShowIntent() != null);
431     }
432 
updateClockLp()433     private void updateClockLp() {
434         int marginBottom = mExpanded
435                 ? mClockMarginBottomExpanded
436                 : mClockMarginBottomCollapsed;
437         LayoutParams lp = (LayoutParams) mDateGroup.getLayoutParams();
438         if (marginBottom != lp.bottomMargin) {
439             lp.bottomMargin = marginBottom;
440             mDateGroup.setLayoutParams(lp);
441         }
442     }
443 
updateMultiUserSwitch()444     private void updateMultiUserSwitch() {
445         int marginEnd;
446         int width;
447         if (mExpanded) {
448             marginEnd = mMultiUserExpandedMargin;
449             width = mMultiUserSwitchWidthExpanded;
450         } else {
451             marginEnd = mMultiUserCollapsedMargin;
452             width = mMultiUserSwitchWidthCollapsed;
453         }
454         MarginLayoutParams lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams();
455         if (marginEnd != lp.getMarginEnd() || lp.width != width) {
456             lp.setMarginEnd(marginEnd);
457             lp.width = width;
458             mMultiUserSwitch.setLayoutParams(lp);
459         }
460     }
461 
setExpansion(float t)462     public void setExpansion(float t) {
463         if (!mExpanded) {
464             t = 0f;
465         }
466         mCurrentT = t;
467         float height = mCollapsedHeight + t * (mExpandedHeight - mCollapsedHeight);
468         if (height < mCollapsedHeight) {
469             height = mCollapsedHeight;
470         }
471         if (height > mExpandedHeight) {
472             height = mExpandedHeight;
473         }
474         setClipping(height);
475         updateLayoutValues(t);
476     }
477 
updateLayoutValues(float t)478     private void updateLayoutValues(float t) {
479         if (mCaptureValues) {
480             return;
481         }
482         mCurrentValues.interpoloate(mCollapsedValues, mExpandedValues, t);
483         applyLayoutValues(mCurrentValues);
484     }
485 
setClipping(float height)486     private void setClipping(float height) {
487         mClipBounds.set(getPaddingLeft(), 0, getWidth() - getPaddingRight(), (int) height);
488         setClipBounds(mClipBounds);
489         invalidateOutline();
490     }
491 
setUserInfoController(UserInfoController userInfoController)492     public void setUserInfoController(UserInfoController userInfoController) {
493         userInfoController.addListener(new UserInfoController.OnUserInfoChangedListener() {
494             @Override
495             public void onUserInfoChanged(String name, Drawable picture) {
496                 mMultiUserAvatar.setImageDrawable(picture);
497             }
498         });
499     }
500 
501     @Override
onClick(View v)502     public void onClick(View v) {
503         if (v == mSettingsButton) {
504             if (mSettingsButton.isTunerClick()) {
505                 if (TunerService.isTunerEnabled(mContext)) {
506                     TunerService.showResetRequest(mContext, new Runnable() {
507                         @Override
508                         public void run() {
509                             // Relaunch settings so that the tuner disappears.
510                             startSettingsActivity();
511                         }
512                     });
513                 } else {
514                     Toast.makeText(getContext(), R.string.tuner_toast, Toast.LENGTH_LONG).show();
515                     TunerService.setTunerEnabled(mContext, true);
516                 }
517             }
518             startSettingsActivity();
519         } else if (v == mSystemIconsSuperContainer) {
520             startBatteryActivity();
521         } else if (v == mAlarmStatus && mNextAlarm != null) {
522             PendingIntent showIntent = mNextAlarm.getShowIntent();
523             if (showIntent != null && showIntent.isActivity()) {
524                 mActivityStarter.startActivity(showIntent.getIntent(), true /* dismissShade */);
525             }
526         }
527     }
528 
startSettingsActivity()529     private void startSettingsActivity() {
530         mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
531                 true /* dismissShade */);
532     }
533 
startBatteryActivity()534     private void startBatteryActivity() {
535         mActivityStarter.startActivity(new Intent(Intent.ACTION_POWER_USAGE_SUMMARY),
536                 true /* dismissShade */);
537     }
538 
setQSPanel(QSPanel qsp)539     public void setQSPanel(QSPanel qsp) {
540         mQSPanel = qsp;
541         if (mQSPanel != null) {
542             mQSPanel.setCallback(mQsPanelCallback);
543         }
544         mMultiUserSwitch.setQsPanel(qsp);
545     }
546 
547     @Override
shouldDelayChildPressedState()548     public boolean shouldDelayChildPressedState() {
549         return true;
550     }
551 
552     @Override
setEmergencyCallsOnly(boolean show)553     public void setEmergencyCallsOnly(boolean show) {
554         boolean changed = show != mShowEmergencyCallsOnly;
555         if (changed) {
556             mShowEmergencyCallsOnly = show;
557             if (mExpanded) {
558                 updateEverything();
559                 requestCaptureValues();
560             }
561         }
562     }
563 
564     @Override
dispatchSetPressed(boolean pressed)565     protected void dispatchSetPressed(boolean pressed) {
566         // We don't want that everything lights up when we click on the header, so block the request
567         // here.
568     }
569 
captureLayoutValues(LayoutValues target)570     private void captureLayoutValues(LayoutValues target) {
571         target.timeScale = mExpanded ? 1f : mClockCollapsedScaleFactor;
572         target.clockY = mClock.getBottom();
573         target.dateY = mDateGroup.getTop();
574         target.emergencyCallsOnlyAlpha = getAlphaForVisibility(mEmergencyCallsOnly);
575         target.alarmStatusAlpha = getAlphaForVisibility(mAlarmStatus);
576         target.dateCollapsedAlpha = getAlphaForVisibility(mDateCollapsed);
577         target.dateExpandedAlpha = getAlphaForVisibility(mDateExpanded);
578         target.avatarScale = mMultiUserAvatar.getScaleX();
579         target.avatarX = mMultiUserSwitch.getLeft() + mMultiUserAvatar.getLeft();
580         target.avatarY = mMultiUserSwitch.getTop() + mMultiUserAvatar.getTop();
581         if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) {
582             target.batteryX = mSystemIconsSuperContainer.getLeft()
583                     + mSystemIconsContainer.getRight();
584         } else {
585             target.batteryX = mSystemIconsSuperContainer.getLeft()
586                     + mSystemIconsContainer.getLeft();
587         }
588         target.batteryY = mSystemIconsSuperContainer.getTop() + mSystemIconsContainer.getTop();
589         target.batteryLevelAlpha = getAlphaForVisibility(mBatteryLevel);
590         target.settingsAlpha = getAlphaForVisibility(mSettingsContainer);
591         target.settingsTranslation = mExpanded
592                 ? 0
593                 : mMultiUserSwitch.getLeft() - mSettingsContainer.getLeft();
594         target.signalClusterAlpha = mSignalClusterDetached ? 0f : 1f;
595         target.settingsRotation = !mExpanded ? 90f : 0f;
596     }
597 
getAlphaForVisibility(View v)598     private float getAlphaForVisibility(View v) {
599         return v == null || v.getVisibility() == View.VISIBLE ? 1f : 0f;
600     }
601 
applyAlpha(View v, float alpha)602     private void applyAlpha(View v, float alpha) {
603         if (v == null || v.getVisibility() == View.GONE) {
604             return;
605         }
606         if (alpha == 0f) {
607             v.setVisibility(View.INVISIBLE);
608         } else {
609             v.setVisibility(View.VISIBLE);
610             v.setAlpha(alpha);
611         }
612     }
613 
applyLayoutValues(LayoutValues values)614     private void applyLayoutValues(LayoutValues values) {
615         mTime.setScaleX(values.timeScale);
616         mTime.setScaleY(values.timeScale);
617         mClock.setY(values.clockY - mClock.getHeight());
618         mDateGroup.setY(values.dateY);
619         mAlarmStatus.setY(values.dateY - mAlarmStatus.getPaddingTop());
620         mMultiUserAvatar.setScaleX(values.avatarScale);
621         mMultiUserAvatar.setScaleY(values.avatarScale);
622         mMultiUserAvatar.setX(values.avatarX - mMultiUserSwitch.getLeft());
623         mMultiUserAvatar.setY(values.avatarY - mMultiUserSwitch.getTop());
624         if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) {
625             mSystemIconsSuperContainer.setX(values.batteryX - mSystemIconsContainer.getRight());
626         } else {
627             mSystemIconsSuperContainer.setX(values.batteryX - mSystemIconsContainer.getLeft());
628         }
629         mSystemIconsSuperContainer.setY(values.batteryY - mSystemIconsContainer.getTop());
630         if (mSignalCluster != null && mExpanded) {
631             if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) {
632                 mSignalCluster.setX(mSystemIconsSuperContainer.getX()
633                         - mSignalCluster.getWidth());
634             } else {
635                 mSignalCluster.setX(mSystemIconsSuperContainer.getX()
636                         + mSystemIconsSuperContainer.getWidth());
637             }
638             mSignalCluster.setY(
639                     mSystemIconsSuperContainer.getY() + mSystemIconsSuperContainer.getHeight()/2
640                             - mSignalCluster.getHeight()/2);
641         } else if (mSignalCluster != null) {
642             mSignalCluster.setTranslationX(0f);
643             mSignalCluster.setTranslationY(0f);
644         }
645         if (!mSettingsButton.isAnimating()) {
646             mSettingsContainer.setTranslationY(mSystemIconsSuperContainer.getTranslationY());
647             mSettingsContainer.setTranslationX(values.settingsTranslation);
648             mSettingsButton.setRotation(values.settingsRotation);
649         }
650         applyAlpha(mEmergencyCallsOnly, values.emergencyCallsOnlyAlpha);
651         if (!mShowingDetail && !mDetailTransitioning) {
652             // Otherwise it needs to stay invisible
653             applyAlpha(mAlarmStatus, values.alarmStatusAlpha);
654         }
655         applyAlpha(mDateCollapsed, values.dateCollapsedAlpha);
656         applyAlpha(mDateExpanded, values.dateExpandedAlpha);
657         applyAlpha(mBatteryLevel, values.batteryLevelAlpha);
658         applyAlpha(mSettingsContainer, values.settingsAlpha);
659         applyAlpha(mSignalCluster, values.signalClusterAlpha);
660         if (!mExpanded) {
661             mTime.setScaleX(1f);
662             mTime.setScaleY(1f);
663         }
664         updateAmPmTranslation();
665     }
666 
667     /**
668      * Captures all layout values (position, visibility) for a certain state. This is used for
669      * animations.
670      */
671     private static final class LayoutValues {
672 
673         float dateExpandedAlpha;
674         float dateCollapsedAlpha;
675         float emergencyCallsOnlyAlpha;
676         float alarmStatusAlpha;
677         float timeScale = 1f;
678         float clockY;
679         float dateY;
680         float avatarScale;
681         float avatarX;
682         float avatarY;
683         float batteryX;
684         float batteryY;
685         float batteryLevelAlpha;
686         float settingsAlpha;
687         float settingsTranslation;
688         float signalClusterAlpha;
689         float settingsRotation;
690 
interpoloate(LayoutValues v1, LayoutValues v2, float t)691         public void interpoloate(LayoutValues v1, LayoutValues v2, float t) {
692             timeScale = v1.timeScale * (1 - t) + v2.timeScale * t;
693             clockY = v1.clockY * (1 - t) + v2.clockY * t;
694             dateY = v1.dateY * (1 - t) + v2.dateY * t;
695             avatarScale = v1.avatarScale * (1 - t) + v2.avatarScale * t;
696             avatarX = v1.avatarX * (1 - t) + v2.avatarX * t;
697             avatarY = v1.avatarY * (1 - t) + v2.avatarY * t;
698             batteryX = v1.batteryX * (1 - t) + v2.batteryX * t;
699             batteryY = v1.batteryY * (1 - t) + v2.batteryY * t;
700             settingsTranslation = v1.settingsTranslation * (1 - t) + v2.settingsTranslation * t;
701 
702             float t1 = Math.max(0, t - 0.5f) * 2;
703             settingsRotation = v1.settingsRotation * (1 - t1) + v2.settingsRotation * t1;
704             emergencyCallsOnlyAlpha =
705                     v1.emergencyCallsOnlyAlpha * (1 - t1) + v2.emergencyCallsOnlyAlpha * t1;
706 
707             float t2 = Math.min(1, 2 * t);
708             signalClusterAlpha = v1.signalClusterAlpha * (1 - t2) + v2.signalClusterAlpha * t2;
709 
710             float t3 = Math.max(0, t - 0.7f) / 0.3f;
711             batteryLevelAlpha = v1.batteryLevelAlpha * (1 - t3) + v2.batteryLevelAlpha * t3;
712             settingsAlpha = v1.settingsAlpha * (1 - t3) + v2.settingsAlpha * t3;
713             dateExpandedAlpha = v1.dateExpandedAlpha * (1 - t3) + v2.dateExpandedAlpha * t3;
714             dateCollapsedAlpha = v1.dateCollapsedAlpha * (1 - t3) + v2.dateCollapsedAlpha * t3;
715             alarmStatusAlpha = v1.alarmStatusAlpha * (1 - t3) + v2.alarmStatusAlpha * t3;
716         }
717     }
718 
719     private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() {
720         private boolean mScanState;
721 
722         @Override
723         public void onToggleStateChanged(final boolean state) {
724             post(new Runnable() {
725                 @Override
726                 public void run() {
727                     handleToggleStateChanged(state);
728                 }
729             });
730         }
731 
732         @Override
733         public void onShowingDetail(final QSTile.DetailAdapter detail) {
734             mDetailTransitioning = true;
735             post(new Runnable() {
736                 @Override
737                 public void run() {
738                     handleShowingDetail(detail);
739                 }
740             });
741         }
742 
743         @Override
744         public void onScanStateChanged(final boolean state) {
745             post(new Runnable() {
746                 @Override
747                 public void run() {
748                     handleScanStateChanged(state);
749                 }
750             });
751         }
752 
753         private void handleToggleStateChanged(boolean state) {
754             mQsDetailHeaderSwitch.setChecked(state);
755         }
756 
757         private void handleScanStateChanged(boolean state) {
758             if (mScanState == state) return;
759             mScanState = state;
760             final Animatable anim = (Animatable) mQsDetailHeaderProgress.getDrawable();
761             if (state) {
762                 mQsDetailHeaderProgress.animate().alpha(1f);
763                 anim.start();
764             } else {
765                 mQsDetailHeaderProgress.animate().alpha(0f);
766                 anim.stop();
767             }
768         }
769 
770         private void handleShowingDetail(final QSTile.DetailAdapter detail) {
771             final boolean showingDetail = detail != null;
772             transition(mClock, !showingDetail);
773             transition(mDateGroup, !showingDetail);
774             if (mAlarmShowing) {
775                 transition(mAlarmStatus, !showingDetail);
776             }
777             transition(mQsDetailHeader, showingDetail);
778             mShowingDetail = showingDetail;
779             if (showingDetail) {
780                 mQsDetailHeaderTitle.setText(detail.getTitle());
781                 final Boolean toggleState = detail.getToggleState();
782                 if (toggleState == null) {
783                     mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
784                     mQsDetailHeader.setClickable(false);
785                 } else {
786                     mQsDetailHeaderSwitch.setVisibility(VISIBLE);
787                     mQsDetailHeaderSwitch.setChecked(toggleState);
788                     mQsDetailHeader.setClickable(true);
789                     mQsDetailHeader.setOnClickListener(new OnClickListener() {
790                         @Override
791                         public void onClick(View v) {
792                             boolean checked = !mQsDetailHeaderSwitch.isChecked();
793                             mQsDetailHeaderSwitch.setChecked(checked);
794                             detail.setToggleState(checked);
795                         }
796                     });
797                 }
798             } else {
799                 mQsDetailHeader.setClickable(false);
800             }
801         }
802 
803         private void transition(final View v, final boolean in) {
804             if (in) {
805                 v.bringToFront();
806                 v.setVisibility(VISIBLE);
807             }
808             if (v.hasOverlappingRendering()) {
809                 v.animate().withLayer();
810             }
811             v.animate()
812                     .alpha(in ? 1 : 0)
813                     .withEndAction(new Runnable() {
814                         @Override
815                         public void run() {
816                             if (!in) {
817                                 v.setVisibility(INVISIBLE);
818                             }
819                             mDetailTransitioning = false;
820                         }
821                     })
822                     .start();
823         }
824     };
825 }
826