StatusBarHeaderView.java revision 805131627369b35fbf78d491a6018851517a8314
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        mEmergencyCallsOnly.setText(com.android.internal.R.string.emergency_calls_only);
211
212        mClockCollapsedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size);
213        mClockExpandedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size);
214        mClockCollapsedScaleFactor = (float) mClockCollapsedSize / (float) mClockExpandedSize;
215
216        updateClockScale();
217        updateClockCollapsedMargin();
218    }
219
220    private void updateClockCollapsedMargin() {
221        Resources res = getResources();
222        int padding = res.getDimensionPixelSize(R.dimen.clock_collapsed_bottom_margin);
223        int largePadding = res.getDimensionPixelSize(
224                R.dimen.clock_collapsed_bottom_margin_large_text);
225        float largeFactor = (MathUtils.constrain(getResources().getConfiguration().fontScale, 1.0f,
226                FontSizeUtils.LARGE_TEXT_SCALE) - 1f) / (FontSizeUtils.LARGE_TEXT_SCALE - 1f);
227        mClockMarginBottomCollapsed = Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
228        requestLayout();
229    }
230
231    private void requestCaptureValues() {
232        mCaptureValues = true;
233        requestLayout();
234    }
235
236    private void loadDimens() {
237        mCollapsedHeight = getResources().getDimensionPixelSize(R.dimen.status_bar_header_height);
238        mExpandedHeight = getResources().getDimensionPixelSize(
239                R.dimen.status_bar_header_height_expanded);
240        mMultiUserExpandedMargin =
241                getResources().getDimensionPixelSize(R.dimen.multi_user_switch_expanded_margin);
242        mMultiUserCollapsedMargin =
243                getResources().getDimensionPixelSize(R.dimen.multi_user_switch_collapsed_margin);
244        mClockMarginBottomExpanded =
245                getResources().getDimensionPixelSize(R.dimen.clock_expanded_bottom_margin);
246        updateClockCollapsedMargin();
247        mMultiUserSwitchWidthCollapsed =
248                getResources().getDimensionPixelSize(R.dimen.multi_user_switch_width_collapsed);
249        mMultiUserSwitchWidthExpanded =
250                getResources().getDimensionPixelSize(R.dimen.multi_user_switch_width_expanded);
251        mAvatarCollapsedScaleFactor =
252                getResources().getDimensionPixelSize(R.dimen.multi_user_avatar_collapsed_size)
253                / (float) mMultiUserAvatar.getLayoutParams().width;
254        mClockCollapsedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size);
255        mClockExpandedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size);
256        mClockCollapsedScaleFactor = (float) mClockCollapsedSize / (float) mClockExpandedSize;
257
258    }
259
260    public void setActivityStarter(ActivityStarter activityStarter) {
261        mActivityStarter = activityStarter;
262    }
263
264    public void setBatteryController(BatteryController batteryController) {
265        mBatteryController = batteryController;
266        ((BatteryMeterView) findViewById(R.id.battery)).setBatteryController(batteryController);
267    }
268
269    public void setNextAlarmController(NextAlarmController nextAlarmController) {
270        mNextAlarmController = nextAlarmController;
271    }
272
273    public int getCollapsedHeight() {
274        return mCollapsedHeight;
275    }
276
277    public int getExpandedHeight() {
278        return mExpandedHeight;
279    }
280
281    public void setListening(boolean listening) {
282        if (listening == mListening) {
283            return;
284        }
285        mListening = listening;
286        updateListeners();
287    }
288
289    public void setExpanded(boolean expanded) {
290        boolean changed = expanded != mExpanded;
291        mExpanded = expanded;
292        if (changed) {
293            updateEverything();
294        }
295    }
296
297    public void updateEverything() {
298        updateHeights();
299        updateVisibilities();
300        updateSystemIconsLayoutParams();
301        updateClickTargets();
302        updateMultiUserSwitch();
303        if (mQSPanel != null) {
304            mQSPanel.setExpanded(mExpanded);
305        }
306        updateClockScale();
307        updateAvatarScale();
308        updateClockLp();
309        requestCaptureValues();
310    }
311
312    private void updateHeights() {
313        int height = mExpanded ? mExpandedHeight : mCollapsedHeight;
314        ViewGroup.LayoutParams lp = getLayoutParams();
315        if (lp.height != height) {
316            lp.height = height;
317            setLayoutParams(lp);
318        }
319    }
320
321    private void updateVisibilities() {
322        mDateCollapsed.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
323        mDateExpanded.setVisibility(mExpanded && mAlarmShowing ? View.INVISIBLE : View.VISIBLE);
324        mAlarmStatus.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
325        mSettingsButton.setVisibility(mExpanded ? View.VISIBLE : View.INVISIBLE);
326        mQsDetailHeader.setVisibility(mExpanded && mShowingDetail? View.VISIBLE : View.INVISIBLE);
327        if (mSignalCluster != null) {
328            updateSignalClusterDetachment();
329        }
330        mEmergencyCallsOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly ? VISIBLE : GONE);
331        mBatteryLevel.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
332    }
333
334    private void updateSignalClusterDetachment() {
335        boolean detached = mExpanded;
336        if (detached != mSignalClusterDetached) {
337            if (detached) {
338                getOverlay().add(mSignalCluster);
339            } else {
340                reattachSignalCluster();
341            }
342        }
343        mSignalClusterDetached = detached;
344    }
345
346    private void reattachSignalCluster() {
347        getOverlay().remove(mSignalCluster);
348        mSystemIcons.addView(mSignalCluster, 1);
349    }
350
351    private void updateSystemIconsLayoutParams() {
352        RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsSuperContainer.getLayoutParams();
353        int rule = mExpanded
354                ? mSettingsButton.getId()
355                : mMultiUserSwitch.getId();
356        if (rule != lp.getRules()[RelativeLayout.START_OF]) {
357            lp.addRule(RelativeLayout.START_OF, rule);
358            mSystemIconsSuperContainer.setLayoutParams(lp);
359        }
360    }
361
362    private void updateListeners() {
363        if (mListening) {
364            mBatteryController.addStateChangedCallback(this);
365            mNextAlarmController.addStateChangedCallback(this);
366        } else {
367            mBatteryController.removeStateChangedCallback(this);
368            mNextAlarmController.removeStateChangedCallback(this);
369        }
370    }
371
372    private void updateAvatarScale() {
373        if (mExpanded) {
374            mMultiUserAvatar.setScaleX(1f);
375            mMultiUserAvatar.setScaleY(1f);
376        } else {
377            mMultiUserAvatar.setScaleX(mAvatarCollapsedScaleFactor);
378            mMultiUserAvatar.setScaleY(mAvatarCollapsedScaleFactor);
379        }
380    }
381
382    private void updateClockScale() {
383        mTime.setTextSize(TypedValue.COMPLEX_UNIT_PX, mExpanded
384                ? mClockExpandedSize
385                : mClockCollapsedSize);
386        mTime.setScaleX(1f);
387        mTime.setScaleY(1f);
388        updateAmPmTranslation();
389    }
390
391    private void updateAmPmTranslation() {
392        boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
393        mAmPm.setTranslationX((rtl ? 1 : -1) * mTime.getWidth() * (1 - mTime.getScaleX()));
394    }
395
396    @Override
397    public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
398        mBatteryLevel.setText(getResources().getString(R.string.battery_level_template, level));
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        applyAlpha(mAlarmStatus, values.alarmStatusAlpha);
627        applyAlpha(mDateCollapsed, values.dateCollapsedAlpha);
628        applyAlpha(mDateExpanded, values.dateExpandedAlpha);
629        applyAlpha(mBatteryLevel, values.batteryLevelAlpha);
630        applyAlpha(mSettingsButton, values.settingsAlpha);
631        applyAlpha(mSignalCluster, values.signalClusterAlpha);
632        if (!mExpanded) {
633            mTime.setScaleX(1f);
634            mTime.setScaleY(1f);
635        }
636        updateAmPmTranslation();
637    }
638
639    /**
640     * Captures all layout values (position, visibility) for a certain state. This is used for
641     * animations.
642     */
643    private static final class LayoutValues {
644
645        float dateExpandedAlpha;
646        float dateCollapsedAlpha;
647        float emergencyCallsOnlyAlpha;
648        float alarmStatusAlpha;
649        float timeScale = 1f;
650        float clockY;
651        float dateY;
652        float avatarScale;
653        float avatarX;
654        float avatarY;
655        float batteryX;
656        float batteryY;
657        float batteryLevelAlpha;
658        float settingsAlpha;
659        float settingsTranslation;
660        float signalClusterAlpha;
661        float settingsRotation;
662
663        public void interpoloate(LayoutValues v1, LayoutValues v2, float t) {
664            timeScale = v1.timeScale * (1 - t) + v2.timeScale * t;
665            clockY = v1.clockY * (1 - t) + v2.clockY * t;
666            dateY = v1.dateY * (1 - t) + v2.dateY * t;
667            avatarScale = v1.avatarScale * (1 - t) + v2.avatarScale * t;
668            avatarX = v1.avatarX * (1 - t) + v2.avatarX * t;
669            avatarY = v1.avatarY * (1 - t) + v2.avatarY * t;
670            batteryX = v1.batteryX * (1 - t) + v2.batteryX * t;
671            batteryY = v1.batteryY * (1 - t) + v2.batteryY * t;
672            settingsTranslation = v1.settingsTranslation * (1 - t) + v2.settingsTranslation * t;
673
674            float t1 = Math.max(0, t - 0.5f) * 2;
675            settingsRotation = v1.settingsRotation * (1 - t1) + v2.settingsRotation * t1;
676            emergencyCallsOnlyAlpha =
677                    v1.emergencyCallsOnlyAlpha * (1 - t1) + v2.emergencyCallsOnlyAlpha * t1;
678
679            float t2 = Math.min(1, 2 * t);
680            signalClusterAlpha = v1.signalClusterAlpha * (1 - t2) + v2.signalClusterAlpha * t2;
681
682            float t3 = Math.max(0, t - 0.7f) / 0.3f;
683            batteryLevelAlpha = v1.batteryLevelAlpha * (1 - t3) + v2.batteryLevelAlpha * t3;
684            settingsAlpha = v1.settingsAlpha * (1 - t3) + v2.settingsAlpha * t3;
685            dateExpandedAlpha = v1.dateExpandedAlpha * (1 - t3) + v2.dateExpandedAlpha * t3;
686            dateCollapsedAlpha = v1.dateCollapsedAlpha * (1 - t3) + v2.dateCollapsedAlpha * t3;
687            alarmStatusAlpha = v1.alarmStatusAlpha * (1 - t3) + v2.alarmStatusAlpha * t3;
688        }
689    }
690
691    private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() {
692        private boolean mScanState;
693
694        @Override
695        public void onToggleStateChanged(final boolean state) {
696            post(new Runnable() {
697                @Override
698                public void run() {
699                    handleToggleStateChanged(state);
700                }
701            });
702        }
703
704        @Override
705        public void onShowingDetail(final QSTile.DetailAdapter detail) {
706            post(new Runnable() {
707                @Override
708                public void run() {
709                    handleShowingDetail(detail);
710                }
711            });
712        }
713
714        @Override
715        public void onScanStateChanged(final boolean state) {
716            post(new Runnable() {
717                @Override
718                public void run() {
719                    handleScanStateChanged(state);
720                }
721            });
722        }
723
724        private void handleToggleStateChanged(boolean state) {
725            mQsDetailHeaderSwitch.setChecked(state);
726        }
727
728        private void handleScanStateChanged(boolean state) {
729            if (mScanState == state) return;
730            mScanState = state;
731            final Animatable anim = (Animatable) mQsDetailHeaderProgress.getDrawable();
732            if (state) {
733                mQsDetailHeaderProgress.animate().alpha(1f);
734                anim.start();
735            } else {
736                mQsDetailHeaderProgress.animate().alpha(0f);
737                anim.stop();
738            }
739        }
740
741        private void handleShowingDetail(final QSTile.DetailAdapter detail) {
742            final boolean showingDetail = detail != null;
743            transition(mClock, !showingDetail);
744            transition(mDateGroup, !showingDetail);
745            if (mAlarmShowing) {
746                transition(mAlarmStatus, !showingDetail);
747            }
748            transition(mQsDetailHeader, showingDetail);
749            mShowingDetail = showingDetail;
750            if (showingDetail) {
751                String title = mContext.getString(detail.getTitle());
752                mQsDetailHeaderTitle.setText(title);
753                announceForAccessibility(title);
754                final Boolean toggleState = detail.getToggleState();
755                if (toggleState == null) {
756                    mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
757                    mQsDetailHeader.setClickable(false);
758                } else {
759                    mQsDetailHeaderSwitch.setVisibility(VISIBLE);
760                    mQsDetailHeaderSwitch.setChecked(toggleState);
761                    mQsDetailHeader.setClickable(true);
762                    mQsDetailHeader.setOnClickListener(new OnClickListener() {
763                        @Override
764                        public void onClick(View v) {
765                            detail.setToggleState(!mQsDetailHeaderSwitch.isChecked());
766                        }
767                    });
768                }
769            } else {
770                mQsDetailHeader.setClickable(false);
771            }
772        }
773
774        private void transition(final View v, final boolean in) {
775            if (in) {
776                v.bringToFront();
777                v.setVisibility(VISIBLE);
778            }
779            v.animate()
780                    .alpha(in ? 1 : 0)
781                    .withLayer()
782                    .withEndAction(new Runnable() {
783                        @Override
784                        public void run() {
785                            if (!in) {
786                                v.setVisibility(INVISIBLE);
787                            }
788                        }
789                    })
790                    .start();
791        }
792    };
793}
794