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