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 StatusBarHeaderView(Context context, AttributeSet attrs)125 public StatusBarHeaderView(Context context, AttributeSet attrs) { 126 super(context, attrs); 127 } 128 129 @Override onFinishInflate()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 onLayout(boolean changed, int l, int t, int r, int b)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 onConfigurationChanged(Configuration newConfig)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 updateClockCollapsedMargin()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 requestCaptureValues()233 private void requestCaptureValues() { 234 mCaptureValues = true; 235 requestLayout(); 236 } 237 loadDimens()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 setActivityStarter(ActivityStarter activityStarter)262 public void setActivityStarter(ActivityStarter activityStarter) { 263 mActivityStarter = activityStarter; 264 } 265 setBatteryController(BatteryController batteryController)266 public void setBatteryController(BatteryController batteryController) { 267 mBatteryController = batteryController; 268 ((BatteryMeterView) findViewById(R.id.battery)).setBatteryController(batteryController); 269 } 270 setNextAlarmController(NextAlarmController nextAlarmController)271 public void setNextAlarmController(NextAlarmController nextAlarmController) { 272 mNextAlarmController = nextAlarmController; 273 } 274 getCollapsedHeight()275 public int getCollapsedHeight() { 276 return mCollapsedHeight; 277 } 278 getExpandedHeight()279 public int getExpandedHeight() { 280 return mExpandedHeight; 281 } 282 setListening(boolean listening)283 public void setListening(boolean listening) { 284 if (listening == mListening) { 285 return; 286 } 287 mListening = listening; 288 updateListeners(); 289 } 290 setExpanded(boolean expanded)291 public void setExpanded(boolean expanded) { 292 boolean changed = expanded != mExpanded; 293 mExpanded = expanded; 294 if (changed) { 295 updateEverything(); 296 } 297 } 298 updateEverything()299 public void updateEverything() { 300 updateHeights(); 301 updateVisibilities(); 302 updateSystemIconsLayoutParams(); 303 updateClickTargets(); 304 updateMultiUserSwitch(); 305 updateClockScale(); 306 updateAvatarScale(); 307 updateClockLp(); 308 requestCaptureValues(); 309 } 310 updateHeights()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 updateVisibilities()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 updateSignalClusterDetachment()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 reattachSignalCluster()345 private void reattachSignalCluster() { 346 getOverlay().remove(mSignalCluster); 347 mSystemIcons.addView(mSignalCluster, 1); 348 } 349 updateSystemIconsLayoutParams()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 updateListeners()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 updateAvatarScale()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 updateClockScale()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 updateAmPmTranslation()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 onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging)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 onPowerSaveChanged()402 public void onPowerSaveChanged() { 403 // could not care less 404 } 405 406 @Override onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm)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 updateClickTargets()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 updateClockLp()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 updateMultiUserSwitch()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 setExpansion(float t)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 updateLayoutValues(float t)470 private void updateLayoutValues(float t) { 471 if (mCaptureValues) { 472 return; 473 } 474 mCurrentValues.interpoloate(mCollapsedValues, mExpandedValues, t); 475 applyLayoutValues(mCurrentValues); 476 } 477 setClipping(float height)478 private void setClipping(float height) { 479 mClipBounds.set(getPaddingLeft(), 0, getWidth() - getPaddingRight(), (int) height); 480 setClipBounds(mClipBounds); 481 invalidateOutline(); 482 } 483 setUserInfoController(UserInfoController userInfoController)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 onClick(View v)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 startSettingsActivity()507 private void startSettingsActivity() { 508 mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS), 509 true /* dismissShade */); 510 } 511 startBatteryActivity()512 private void startBatteryActivity() { 513 mActivityStarter.startActivity(new Intent(Intent.ACTION_POWER_USAGE_SUMMARY), 514 true /* dismissShade */); 515 } 516 setQSPanel(QSPanel qsp)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 shouldDelayChildPressedState()526 public boolean shouldDelayChildPressedState() { 527 return true; 528 } 529 setShowEmergencyCallsOnly(boolean show)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 dispatchSetPressed(boolean pressed)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 captureLayoutValues(LayoutValues target)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 getAlphaForVisibility(View v)575 private float getAlphaForVisibility(View v) { 576 return v == null || v.getVisibility() == View.VISIBLE ? 1f : 0f; 577 } 578 applyAlpha(View v, float alpha)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 applyLayoutValues(LayoutValues values)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 interpoloate(LayoutValues v1, LayoutValues v2, float t)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 } 797