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