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