OSDN Git Service

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