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