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