1 /* 2 * Copyright (C) 2015 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.graphics.drawable.Drawable; 25 import android.graphics.drawable.RippleDrawable; 26 import android.util.AttributeSet; 27 import android.view.View; 28 import android.view.View.OnClickListener; 29 import android.view.ViewGroup; 30 import android.widget.ImageView; 31 import android.widget.TextView; 32 import android.widget.Toast; 33 34 import com.android.internal.logging.MetricsLogger; 35 import com.android.internal.logging.MetricsProto; 36 import com.android.keyguard.KeyguardStatusView; 37 import com.android.systemui.FontSizeUtils; 38 import com.android.systemui.R; 39 import com.android.systemui.qs.QSAnimator; 40 import com.android.systemui.qs.QSPanel; 41 import com.android.systemui.qs.QSPanel.Callback; 42 import com.android.systemui.qs.QuickQSPanel; 43 import com.android.systemui.qs.TouchAnimator; 44 import com.android.systemui.statusbar.policy.BatteryController; 45 import com.android.systemui.statusbar.policy.NextAlarmController; 46 import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; 47 import com.android.systemui.statusbar.policy.UserInfoController; 48 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; 49 import com.android.systemui.tuner.TunerService; 50 51 public class QuickStatusBarHeader extends BaseStatusBarHeader implements 52 NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener { 53 54 private static final String TAG = "QuickStatusBarHeader"; 55 56 private static final float EXPAND_INDICATOR_THRESHOLD = .93f; 57 58 private ActivityStarter mActivityStarter; 59 private NextAlarmController mNextAlarmController; 60 private SettingsButton mSettingsButton; 61 protected View mSettingsContainer; 62 63 private TextView mAlarmStatus; 64 private View mAlarmStatusCollapsed; 65 66 private QSPanel mQsPanel; 67 68 private boolean mExpanded; 69 private boolean mAlarmShowing; 70 71 private ViewGroup mDateTimeGroup; 72 private ViewGroup mDateTimeAlarmGroup; 73 private TextView mEmergencyOnly; 74 75 protected ExpandableIndicator mExpandIndicator; 76 77 private boolean mListening; 78 private AlarmManager.AlarmClockInfo mNextAlarm; 79 80 private QuickQSPanel mHeaderQsPanel; 81 private boolean mShowEmergencyCallsOnly; 82 protected MultiUserSwitch mMultiUserSwitch; 83 private ImageView mMultiUserAvatar; 84 85 private float mDateTimeTranslation; 86 private float mDateTimeAlarmTranslation; 87 private float mDateScaleFactor; 88 protected float mGearTranslation; 89 90 private TouchAnimator mSecondHalfAnimator; 91 private TouchAnimator mFirstHalfAnimator; 92 private TouchAnimator mDateSizeAnimator; 93 private TouchAnimator mAlarmTranslation; 94 protected TouchAnimator mSettingsAlpha; 95 private float mExpansionAmount; 96 private QSTileHost mHost; 97 private boolean mShowFullAlarm; 98 99 public QuickStatusBarHeader(Context context, AttributeSet attrs) { 100 super(context, attrs); 101 } 102 103 @Override 104 protected void onFinishInflate() { 105 super.onFinishInflate(); 106 107 mEmergencyOnly = (TextView) findViewById(R.id.header_emergency_calls_only); 108 109 mDateTimeAlarmGroup = (ViewGroup) findViewById(R.id.date_time_alarm_group); 110 mDateTimeAlarmGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE); 111 mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group); 112 mDateTimeGroup.setPivotX(0); 113 mDateTimeGroup.setPivotY(0); 114 mShowFullAlarm = getResources().getBoolean(R.bool.quick_settings_show_full_alarm); 115 116 mExpandIndicator = (ExpandableIndicator) findViewById(R.id.expand_indicator); 117 118 mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel); 119 120 mSettingsButton = (SettingsButton) findViewById(R.id.settings_button); 121 mSettingsContainer = findViewById(R.id.settings_button_container); 122 mSettingsButton.setOnClickListener(this); 123 124 mAlarmStatusCollapsed = findViewById(R.id.alarm_status_collapsed); 125 mAlarmStatus = (TextView) findViewById(R.id.alarm_status); 126 mAlarmStatus.setOnClickListener(this); 127 128 mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch); 129 mMultiUserAvatar = (ImageView) mMultiUserSwitch.findViewById(R.id.multi_user_avatar); 130 131 // RenderThread is doing more harm than good when touching the header (to expand quick 132 // settings), so disable it for this view 133 ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true); 134 ((RippleDrawable) mExpandIndicator.getBackground()).setForceSoftware(true); 135 136 updateResources(); 137 } 138 139 @Override 140 protected void onConfigurationChanged(Configuration newConfig) { 141 super.onConfigurationChanged(newConfig); 142 updateResources(); 143 } 144 145 @Override 146 public void onRtlPropertiesChanged(int layoutDirection) { 147 super.onRtlPropertiesChanged(layoutDirection); 148 updateResources(); 149 } 150 151 private void updateResources() { 152 FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size); 153 FontSizeUtils.updateFontSize(mEmergencyOnly, R.dimen.qs_emergency_calls_only_text_size); 154 155 mGearTranslation = mContext.getResources().getDimension(R.dimen.qs_header_gear_translation); 156 157 mDateTimeTranslation = mContext.getResources().getDimension( 158 R.dimen.qs_date_anim_translation); 159 mDateTimeAlarmTranslation = mContext.getResources().getDimension( 160 R.dimen.qs_date_alarm_anim_translation); 161 float dateCollapsedSize = mContext.getResources().getDimension( 162 R.dimen.qs_date_collapsed_text_size); 163 float dateExpandedSize = mContext.getResources().getDimension( 164 R.dimen.qs_date_text_size); 165 mDateScaleFactor = dateExpandedSize / dateCollapsedSize; 166 updateDateTimePosition(); 167 168 mSecondHalfAnimator = new TouchAnimator.Builder() 169 .addFloat(mShowFullAlarm ? mAlarmStatus : findViewById(R.id.date), "alpha", 0, 1) 170 .addFloat(mEmergencyOnly, "alpha", 0, 1) 171 .setStartDelay(.5f) 172 .build(); 173 if (mShowFullAlarm) { 174 mFirstHalfAnimator = new TouchAnimator.Builder() 175 .addFloat(mAlarmStatusCollapsed, "alpha", 1, 0) 176 .setEndDelay(.5f) 177 .build(); 178 } 179 mDateSizeAnimator = new TouchAnimator.Builder() 180 .addFloat(mDateTimeGroup, "scaleX", 1, mDateScaleFactor) 181 .addFloat(mDateTimeGroup, "scaleY", 1, mDateScaleFactor) 182 .setStartDelay(.36f) 183 .build(); 184 185 updateSettingsAnimator(); 186 } 187 188 protected void updateSettingsAnimator() { 189 mSettingsAlpha = new TouchAnimator.Builder() 190 .addFloat(mSettingsContainer, "translationY", -mGearTranslation, 0) 191 .addFloat(mMultiUserSwitch, "translationY", -mGearTranslation, 0) 192 .addFloat(mSettingsButton, "rotation", -90, 0) 193 .addFloat(mSettingsContainer, "alpha", 0, 1) 194 .addFloat(mMultiUserSwitch, "alpha", 0, 1) 195 .setStartDelay(QSAnimator.EXPANDED_TILE_DELAY) 196 .build(); 197 198 final boolean isRtl = isLayoutRtl(); 199 if (isRtl && mDateTimeGroup.getWidth() == 0) { 200 mDateTimeGroup.addOnLayoutChangeListener(new OnLayoutChangeListener() { 201 @Override 202 public void onLayoutChange(View v, int left, int top, int right, int bottom, 203 int oldLeft, int oldTop, int oldRight, int oldBottom) { 204 mDateTimeGroup.setPivotX(getWidth()); 205 mDateTimeGroup.removeOnLayoutChangeListener(this); 206 } 207 }); 208 } else { 209 mDateTimeGroup.setPivotX(isRtl ? mDateTimeGroup.getWidth() : 0); 210 } 211 } 212 213 @Override 214 public int getCollapsedHeight() { 215 return getHeight(); 216 } 217 218 @Override 219 public int getExpandedHeight() { 220 return getHeight(); 221 } 222 223 @Override 224 public void setExpanded(boolean expanded) { 225 mExpanded = expanded; 226 mHeaderQsPanel.setExpanded(expanded); 227 updateEverything(); 228 } 229 230 @Override 231 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { 232 mNextAlarm = nextAlarm; 233 if (nextAlarm != null) { 234 String alarmString = KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm); 235 mAlarmStatus.setText(alarmString); 236 mAlarmStatus.setContentDescription(mContext.getString( 237 R.string.accessibility_quick_settings_alarm, alarmString)); 238 mAlarmStatusCollapsed.setContentDescription(mContext.getString( 239 R.string.accessibility_quick_settings_alarm, alarmString)); 240 } 241 if (mAlarmShowing != (nextAlarm != null)) { 242 mAlarmShowing = nextAlarm != null; 243 updateEverything(); 244 } 245 } 246 247 @Override 248 public void setExpansion(float headerExpansionFraction) { 249 mExpansionAmount = headerExpansionFraction; 250 mSecondHalfAnimator.setPosition(headerExpansionFraction); 251 if (mShowFullAlarm) { 252 mFirstHalfAnimator.setPosition(headerExpansionFraction); 253 } 254 mDateSizeAnimator.setPosition(headerExpansionFraction); 255 mAlarmTranslation.setPosition(headerExpansionFraction); 256 mSettingsAlpha.setPosition(headerExpansionFraction); 257 258 updateAlarmVisibilities(); 259 260 mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD); 261 } 262 263 @Override 264 protected void onDetachedFromWindow() { 265 setListening(false); 266 mHost.getUserInfoController().remListener(this); 267 mHost.getNetworkController().removeEmergencyListener(this); 268 super.onDetachedFromWindow(); 269 } 270 271 private void updateAlarmVisibilities() { 272 mAlarmStatus.setVisibility(mAlarmShowing && mShowFullAlarm ? View.VISIBLE : View.INVISIBLE); 273 mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.INVISIBLE); 274 } 275 276 private void updateDateTimePosition() { 277 // This one has its own because we have to rebuild it every time the alarm state changes. 278 mAlarmTranslation = new TouchAnimator.Builder() 279 .addFloat(mDateTimeAlarmGroup, "translationY", 0, mAlarmShowing 280 ? mDateTimeAlarmTranslation : mDateTimeTranslation) 281 .build(); 282 mAlarmTranslation.setPosition(mExpansionAmount); 283 } 284 285 public void setListening(boolean listening) { 286 if (listening == mListening) { 287 return; 288 } 289 mHeaderQsPanel.setListening(listening); 290 mListening = listening; 291 updateListeners(); 292 } 293 294 @Override 295 public void updateEverything() { 296 updateDateTimePosition(); 297 updateVisibilities(); 298 setClickable(false); 299 } 300 301 protected void updateVisibilities() { 302 updateAlarmVisibilities(); 303 mEmergencyOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly 304 ? View.VISIBLE : View.INVISIBLE); 305 mSettingsContainer.setVisibility(mExpanded ? View.VISIBLE : View.INVISIBLE); 306 mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility( 307 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE); 308 mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() 309 ? View.VISIBLE : View.INVISIBLE); 310 } 311 312 private void updateListeners() { 313 if (mListening) { 314 mNextAlarmController.addStateChangedCallback(this); 315 } else { 316 mNextAlarmController.removeStateChangedCallback(this); 317 } 318 } 319 320 @Override 321 public void setActivityStarter(ActivityStarter activityStarter) { 322 mActivityStarter = activityStarter; 323 } 324 325 @Override 326 public void setQSPanel(final QSPanel qsPanel) { 327 mQsPanel = qsPanel; 328 setupHost(qsPanel.getHost()); 329 if (mQsPanel != null) { 330 mMultiUserSwitch.setQsPanel(qsPanel); 331 } 332 } 333 334 public void setupHost(final QSTileHost host) { 335 mHost = host; 336 host.setHeaderView(mExpandIndicator); 337 mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this); 338 mHeaderQsPanel.setHost(host, null /* No customization in header */); 339 setUserInfoController(host.getUserInfoController()); 340 setBatteryController(host.getBatteryController()); 341 setNextAlarmController(host.getNextAlarmController()); 342 343 final boolean isAPhone = mHost.getNetworkController().hasVoiceCallingFeature(); 344 if (isAPhone) { 345 mHost.getNetworkController().addEmergencyListener(this); 346 } 347 } 348 349 @Override 350 public void onClick(View v) { 351 if (v == mSettingsButton) { 352 MetricsLogger.action(mContext, 353 MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH); 354 if (mSettingsButton.isTunerClick()) { 355 if (TunerService.isTunerEnabled(mContext)) { 356 TunerService.showResetRequest(mContext, new Runnable() { 357 @Override 358 public void run() { 359 // Relaunch settings so that the tuner disappears. 360 startSettingsActivity(); 361 } 362 }); 363 } else { 364 Toast.makeText(getContext(), R.string.tuner_toast, Toast.LENGTH_LONG).show(); 365 TunerService.setTunerEnabled(mContext, true); 366 } 367 } 368 startSettingsActivity(); 369 } else if (v == mAlarmStatus && mNextAlarm != null) { 370 PendingIntent showIntent = mNextAlarm.getShowIntent(); 371 if (showIntent != null && showIntent.isActivity()) { 372 mActivityStarter.startActivity(showIntent.getIntent(), true /* dismissShade */); 373 } 374 } 375 } 376 377 private void startSettingsActivity() { 378 mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS), 379 true /* dismissShade */); 380 } 381 382 @Override 383 public void setNextAlarmController(NextAlarmController nextAlarmController) { 384 mNextAlarmController = nextAlarmController; 385 } 386 387 @Override 388 public void setBatteryController(BatteryController batteryController) { 389 // Don't care 390 } 391 392 @Override 393 public void setUserInfoController(UserInfoController userInfoController) { 394 userInfoController.addListener(this); 395 } 396 397 @Override 398 public void setCallback(Callback qsPanelCallback) { 399 mHeaderQsPanel.setCallback(qsPanelCallback); 400 } 401 402 @Override 403 public void setEmergencyCallsOnly(boolean show) { 404 boolean changed = show != mShowEmergencyCallsOnly; 405 if (changed) { 406 mShowEmergencyCallsOnly = show; 407 if (mExpanded) { 408 updateEverything(); 409 } 410 } 411 } 412 413 @Override 414 public void onUserInfoChanged(String name, Drawable picture) { 415 mMultiUserAvatar.setImageDrawable(picture); 416 } 417 } 418