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