Home | History | Annotate | Download | only in phone
      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 /**
     52  * The view to manage the header area in the expanded status bar.
     53  */
     54 public class StatusBarHeaderView extends RelativeLayout implements View.OnClickListener,
     55         BatteryController.BatteryStateChangeCallback, NextAlarmController.NextAlarmChangeCallback {
     56 
     57     private boolean mExpanded;
     58     private boolean mListening;
     59 
     60     private ViewGroup mSystemIconsContainer;
     61     private View mSystemIconsSuperContainer;
     62     private View mDateGroup;
     63     private View mClock;
     64     private TextView mTime;
     65     private TextView mAmPm;
     66     private MultiUserSwitch mMultiUserSwitch;
     67     private ImageView mMultiUserAvatar;
     68     private TextView mDateCollapsed;
     69     private TextView mDateExpanded;
     70     private LinearLayout mSystemIcons;
     71     private View mSignalCluster;
     72     private View mSettingsButton;
     73     private View mQsDetailHeader;
     74     private TextView mQsDetailHeaderTitle;
     75     private Switch mQsDetailHeaderSwitch;
     76     private ImageView mQsDetailHeaderProgress;
     77     private TextView mEmergencyCallsOnly;
     78     private TextView mBatteryLevel;
     79     private TextView mAlarmStatus;
     80 
     81     private boolean mShowEmergencyCallsOnly;
     82     private boolean mAlarmShowing;
     83     private AlarmManager.AlarmClockInfo mNextAlarm;
     84 
     85     private int mCollapsedHeight;
     86     private int mExpandedHeight;
     87 
     88     private int mMultiUserExpandedMargin;
     89     private int mMultiUserCollapsedMargin;
     90 
     91     private int mClockMarginBottomExpanded;
     92     private int mClockMarginBottomCollapsed;
     93     private int mMultiUserSwitchWidthCollapsed;
     94     private int mMultiUserSwitchWidthExpanded;
     95 
     96     private int mClockCollapsedSize;
     97     private int mClockExpandedSize;
     98 
     99     /**
    100      * In collapsed QS, the clock and avatar are scaled down a bit post-layout to allow for a nice
    101      * transition. These values determine that factor.
    102      */
    103     private float mClockCollapsedScaleFactor;
    104     private float mAvatarCollapsedScaleFactor;
    105 
    106     private ActivityStarter mActivityStarter;
    107     private BatteryController mBatteryController;
    108     private NextAlarmController mNextAlarmController;
    109     private QSPanel mQSPanel;
    110 
    111 
    112     private final Rect mClipBounds = new Rect();
    113 
    114     private boolean mCaptureValues;
    115     private boolean mSignalClusterDetached;
    116     private final LayoutValues mCollapsedValues = new LayoutValues();
    117     private final LayoutValues mExpandedValues = new LayoutValues();
    118     private final LayoutValues mCurrentValues = new LayoutValues();
    119 
    120     private float mCurrentT;
    121     private boolean mShowingDetail;
    122 
    123     public StatusBarHeaderView(Context context, AttributeSet attrs) {
    124         super(context, attrs);
    125     }
    126 
    127     @Override
    128     protected void onFinishInflate() {
    129         super.onFinishInflate();
    130         mSystemIconsSuperContainer = findViewById(R.id.system_icons_super_container);
    131         mSystemIconsContainer = (ViewGroup) findViewById(R.id.system_icons_container);
    132         mSystemIconsSuperContainer.setOnClickListener(this);
    133         mDateGroup = findViewById(R.id.date_group);
    134         mClock = findViewById(R.id.clock);
    135         mTime = (TextView) findViewById(R.id.time_view);
    136         mAmPm = (TextView) findViewById(R.id.am_pm_view);
    137         mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch);
    138         mMultiUserAvatar = (ImageView) findViewById(R.id.multi_user_avatar);
    139         mDateCollapsed = (TextView) findViewById(R.id.date_collapsed);
    140         mDateExpanded = (TextView) findViewById(R.id.date_expanded);
    141         mSettingsButton = findViewById(R.id.settings_button);
    142         mSettingsButton.setOnClickListener(this);
    143         mQsDetailHeader = findViewById(R.id.qs_detail_header);
    144         mQsDetailHeader.setAlpha(0);
    145         mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
    146         mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
    147         mQsDetailHeaderProgress = (ImageView) findViewById(R.id.qs_detail_header_progress);
    148         mEmergencyCallsOnly = (TextView) findViewById(R.id.header_emergency_calls_only);
    149         mBatteryLevel = (TextView) findViewById(R.id.battery_level);
    150         mAlarmStatus = (TextView) findViewById(R.id.alarm_status);
    151         mAlarmStatus.setOnClickListener(this);
    152         mSignalCluster = findViewById(R.id.signal_cluster);
    153         mSystemIcons = (LinearLayout) findViewById(R.id.system_icons);
    154         loadDimens();
    155         updateVisibilities();
    156         updateClockScale();
    157         updateAvatarScale();
    158         addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    159             @Override
    160             public void onLayoutChange(View v, int left, int top, int right,
    161                     int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
    162                 if ((right - left) != (oldRight - oldLeft)) {
    163                     // width changed, update clipping
    164                     setClipping(getHeight());
    165                 }
    166                 boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
    167                 mTime.setPivotX(rtl ? mTime.getWidth() : 0);
    168                 mTime.setPivotY(mTime.getBaseline());
    169                 updateAmPmTranslation();
    170             }
    171         });
    172         setOutlineProvider(new ViewOutlineProvider() {
    173             @Override
    174             public void getOutline(View view, Outline outline) {
    175                 outline.setRect(mClipBounds);
    176             }
    177         });
    178         requestCaptureValues();
    179     }
    180 
    181     @Override
    182     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    183         super.onLayout(changed, l, t, r, b);
    184         if (mCaptureValues) {
    185             if (mExpanded) {
    186                 captureLayoutValues(mExpandedValues);
    187             } else {
    188                 captureLayoutValues(mCollapsedValues);
    189             }
    190             mCaptureValues = false;
    191             updateLayoutValues(mCurrentT);
    192         }
    193         mAlarmStatus.setX(mDateGroup.getLeft() + mDateCollapsed.getRight());
    194     }
    195 
    196     @Override
    197     protected void onConfigurationChanged(Configuration newConfig) {
    198         super.onConfigurationChanged(newConfig);
    199         FontSizeUtils.updateFontSize(mBatteryLevel, R.dimen.battery_level_text_size);
    200         FontSizeUtils.updateFontSize(mEmergencyCallsOnly,
    201                 R.dimen.qs_emergency_calls_only_text_size);
    202         FontSizeUtils.updateFontSize(mDateCollapsed, R.dimen.qs_date_collapsed_size);
    203         FontSizeUtils.updateFontSize(mDateExpanded, R.dimen.qs_date_collapsed_size);
    204         FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
    205         FontSizeUtils.updateFontSize(this, android.R.id.title, R.dimen.qs_detail_header_text_size);
    206         FontSizeUtils.updateFontSize(this, android.R.id.toggle, R.dimen.qs_detail_header_text_size);
    207         FontSizeUtils.updateFontSize(mAmPm, R.dimen.qs_time_collapsed_size);
    208         FontSizeUtils.updateFontSize(this, R.id.empty_time_view, R.dimen.qs_time_expanded_size);
    209 
    210         mEmergencyCallsOnly.setText(com.android.internal.R.string.emergency_calls_only);
    211 
    212         mClockCollapsedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size);
    213         mClockExpandedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size);
    214         mClockCollapsedScaleFactor = (float) mClockCollapsedSize / (float) mClockExpandedSize;
    215 
    216         updateClockScale();
    217         updateClockCollapsedMargin();
    218     }
    219 
    220     private void updateClockCollapsedMargin() {
    221         Resources res = getResources();
    222         int padding = res.getDimensionPixelSize(R.dimen.clock_collapsed_bottom_margin);
    223         int largePadding = res.getDimensionPixelSize(
    224                 R.dimen.clock_collapsed_bottom_margin_large_text);
    225         float largeFactor = (MathUtils.constrain(getResources().getConfiguration().fontScale, 1.0f,
    226                 FontSizeUtils.LARGE_TEXT_SCALE) - 1f) / (FontSizeUtils.LARGE_TEXT_SCALE - 1f);
    227         mClockMarginBottomCollapsed = Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
    228         requestLayout();
    229     }
    230 
    231     private void requestCaptureValues() {
    232         mCaptureValues = true;
    233         requestLayout();
    234     }
    235 
    236     private void loadDimens() {
    237         mCollapsedHeight = getResources().getDimensionPixelSize(R.dimen.status_bar_header_height);
    238         mExpandedHeight = getResources().getDimensionPixelSize(
    239                 R.dimen.status_bar_header_height_expanded);
    240         mMultiUserExpandedMargin =
    241                 getResources().getDimensionPixelSize(R.dimen.multi_user_switch_expanded_margin);
    242         mMultiUserCollapsedMargin =
    243                 getResources().getDimensionPixelSize(R.dimen.multi_user_switch_collapsed_margin);
    244         mClockMarginBottomExpanded =
    245                 getResources().getDimensionPixelSize(R.dimen.clock_expanded_bottom_margin);
    246         updateClockCollapsedMargin();
    247         mMultiUserSwitchWidthCollapsed =
    248                 getResources().getDimensionPixelSize(R.dimen.multi_user_switch_width_collapsed);
    249         mMultiUserSwitchWidthExpanded =
    250                 getResources().getDimensionPixelSize(R.dimen.multi_user_switch_width_expanded);
    251         mAvatarCollapsedScaleFactor =
    252                 getResources().getDimensionPixelSize(R.dimen.multi_user_avatar_collapsed_size)
    253                 / (float) mMultiUserAvatar.getLayoutParams().width;
    254         mClockCollapsedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size);
    255         mClockExpandedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size);
    256         mClockCollapsedScaleFactor = (float) mClockCollapsedSize / (float) mClockExpandedSize;
    257 
    258     }
    259 
    260     public void setActivityStarter(ActivityStarter activityStarter) {
    261         mActivityStarter = activityStarter;
    262     }
    263 
    264     public void setBatteryController(BatteryController batteryController) {
    265         mBatteryController = batteryController;
    266         ((BatteryMeterView) findViewById(R.id.battery)).setBatteryController(batteryController);
    267     }
    268 
    269     public void setNextAlarmController(NextAlarmController nextAlarmController) {
    270         mNextAlarmController = nextAlarmController;
    271     }
    272 
    273     public int getCollapsedHeight() {
    274         return mCollapsedHeight;
    275     }
    276 
    277     public int getExpandedHeight() {
    278         return mExpandedHeight;
    279     }
    280 
    281     public void setListening(boolean listening) {
    282         if (listening == mListening) {
    283             return;
    284         }
    285         mListening = listening;
    286         updateListeners();
    287     }
    288 
    289     public void setExpanded(boolean expanded) {
    290         boolean changed = expanded != mExpanded;
    291         mExpanded = expanded;
    292         if (changed) {
    293             updateEverything();
    294         }
    295     }
    296 
    297     public void updateEverything() {
    298         updateHeights();
    299         updateVisibilities();
    300         updateSystemIconsLayoutParams();
    301         updateClickTargets();
    302         updateMultiUserSwitch();
    303         if (mQSPanel != null) {
    304             mQSPanel.setExpanded(mExpanded);
    305         }
    306         updateClockScale();
    307         updateAvatarScale();
    308         updateClockLp();
    309         requestCaptureValues();
    310     }
    311 
    312     private void updateHeights() {
    313         int height = mExpanded ? mExpandedHeight : mCollapsedHeight;
    314         ViewGroup.LayoutParams lp = getLayoutParams();
    315         if (lp.height != height) {
    316             lp.height = height;
    317             setLayoutParams(lp);
    318         }
    319     }
    320 
    321     private void updateVisibilities() {
    322         mDateCollapsed.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
    323         mDateExpanded.setVisibility(mExpanded && mAlarmShowing ? View.INVISIBLE : View.VISIBLE);
    324         mAlarmStatus.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
    325         mSettingsButton.setVisibility(mExpanded ? View.VISIBLE : View.INVISIBLE);
    326         mQsDetailHeader.setVisibility(mExpanded && mShowingDetail? View.VISIBLE : View.INVISIBLE);
    327         if (mSignalCluster != null) {
    328             updateSignalClusterDetachment();
    329         }
    330         mEmergencyCallsOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly ? VISIBLE : GONE);
    331         mBatteryLevel.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
    332     }
    333 
    334     private void updateSignalClusterDetachment() {
    335         boolean detached = mExpanded;
    336         if (detached != mSignalClusterDetached) {
    337             if (detached) {
    338                 getOverlay().add(mSignalCluster);
    339             } else {
    340                 reattachSignalCluster();
    341             }
    342         }
    343         mSignalClusterDetached = detached;
    344     }
    345 
    346     private void reattachSignalCluster() {
    347         getOverlay().remove(mSignalCluster);
    348         mSystemIcons.addView(mSignalCluster, 1);
    349     }
    350 
    351     private void updateSystemIconsLayoutParams() {
    352         RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsSuperContainer.getLayoutParams();
    353         int rule = mExpanded
    354                 ? mSettingsButton.getId()
    355                 : mMultiUserSwitch.getId();
    356         if (rule != lp.getRules()[RelativeLayout.START_OF]) {
    357             lp.addRule(RelativeLayout.START_OF, rule);
    358             mSystemIconsSuperContainer.setLayoutParams(lp);
    359         }
    360     }
    361 
    362     private void updateListeners() {
    363         if (mListening) {
    364             mBatteryController.addStateChangedCallback(this);
    365             mNextAlarmController.addStateChangedCallback(this);
    366         } else {
    367             mBatteryController.removeStateChangedCallback(this);
    368             mNextAlarmController.removeStateChangedCallback(this);
    369         }
    370     }
    371 
    372     private void updateAvatarScale() {
    373         if (mExpanded) {
    374             mMultiUserAvatar.setScaleX(1f);
    375             mMultiUserAvatar.setScaleY(1f);
    376         } else {
    377             mMultiUserAvatar.setScaleX(mAvatarCollapsedScaleFactor);
    378             mMultiUserAvatar.setScaleY(mAvatarCollapsedScaleFactor);
    379         }
    380     }
    381 
    382     private void updateClockScale() {
    383         mTime.setTextSize(TypedValue.COMPLEX_UNIT_PX, mExpanded
    384                 ? mClockExpandedSize
    385                 : mClockCollapsedSize);
    386         mTime.setScaleX(1f);
    387         mTime.setScaleY(1f);
    388         updateAmPmTranslation();
    389     }
    390 
    391     private void updateAmPmTranslation() {
    392         boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
    393         mAmPm.setTranslationX((rtl ? 1 : -1) * mTime.getWidth() * (1 - mTime.getScaleX()));
    394     }
    395 
    396     @Override
    397     public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
    398         mBatteryLevel.setText(getResources().getString(R.string.battery_level_template, level));
    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 && showIntent.isActivity()) {
    502                 mActivityStarter.startActivity(showIntent.getIntent(), true /* dismissShade */);
    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             v.animate()
    781                     .alpha(in ? 1 : 0)
    782                     .withLayer()
    783                     .withEndAction(new Runnable() {
    784                         @Override
    785                         public void run() {
    786                             if (!in) {
    787                                 v.setVisibility(INVISIBLE);
    788                             }
    789                         }
    790                     })
    791                     .start();
    792         }
    793     };
    794 }
    795