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