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