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