OSDN Git Service

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