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.qs; 18 19 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE; 20 21 import android.app.ActivityManager; 22 import android.app.AlarmManager; 23 import android.app.PendingIntent; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.res.Configuration; 27 import android.content.res.Resources; 28 import android.graphics.PorterDuff.Mode; 29 import android.graphics.drawable.Drawable; 30 import android.graphics.drawable.RippleDrawable; 31 import android.os.UserManager; 32 import android.provider.AlarmClock; 33 import android.support.annotation.Nullable; 34 import android.support.annotation.VisibleForTesting; 35 import android.util.AttributeSet; 36 import android.view.View; 37 import android.view.View.OnClickListener; 38 import android.widget.FrameLayout; 39 import android.widget.ImageView; 40 import android.widget.LinearLayout; 41 import android.widget.TextView; 42 import android.widget.Toast; 43 44 import com.android.internal.logging.MetricsLogger; 45 import com.android.internal.logging.nano.MetricsProto; 46 import com.android.keyguard.KeyguardStatusView; 47 import com.android.settingslib.Utils; 48 import com.android.systemui.Dependency; 49 import com.android.systemui.FontSizeUtils; 50 import com.android.systemui.R; 51 import com.android.systemui.R.dimen; 52 import com.android.systemui.R.id; 53 import com.android.systemui.plugins.ActivityStarter; 54 import com.android.systemui.qs.TouchAnimator.Builder; 55 import com.android.systemui.qs.TouchAnimator.Listener; 56 import com.android.systemui.qs.TouchAnimator.ListenerAdapter; 57 import com.android.systemui.statusbar.phone.ExpandableIndicator; 58 import com.android.systemui.statusbar.phone.MultiUserSwitch; 59 import com.android.systemui.statusbar.phone.SettingsButton; 60 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 61 import com.android.systemui.statusbar.policy.NetworkController; 62 import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener; 63 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; 64 import com.android.systemui.statusbar.policy.NextAlarmController; 65 import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; 66 import com.android.systemui.statusbar.policy.UserInfoController; 67 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; 68 import com.android.systemui.tuner.TunerService; 69 70 public class QSFooter extends FrameLayout implements 71 NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener, 72 SignalCallback { 73 private static final float EXPAND_INDICATOR_THRESHOLD = .93f; 74 75 private ActivityStarter mActivityStarter; 76 private NextAlarmController mNextAlarmController; 77 private UserInfoController mUserInfoController; 78 private SettingsButton mSettingsButton; 79 protected View mSettingsContainer; 80 81 private TextView mAlarmStatus; 82 private View mAlarmStatusCollapsed; 83 private View mDate; 84 85 private QSPanel mQsPanel; 86 87 private boolean mExpanded; 88 private boolean mAlarmShowing; 89 90 protected ExpandableIndicator mExpandIndicator; 91 92 private boolean mListening; 93 private AlarmManager.AlarmClockInfo mNextAlarm; 94 95 private boolean mShowEmergencyCallsOnly; 96 protected MultiUserSwitch mMultiUserSwitch; 97 private ImageView mMultiUserAvatar; 98 private boolean mAlwaysShowMultiUserSwitch; 99 100 protected TouchAnimator mSettingsAlpha; 101 private float mExpansionAmount; 102 103 protected View mEdit; 104 private boolean mShowEditIcon; 105 private TouchAnimator mAnimator; 106 private View mDateTimeGroup; 107 private boolean mKeyguardShowing; 108 private TouchAnimator mAlarmAnimator; 109 QSFooter(Context context, AttributeSet attrs)110 public QSFooter(Context context, AttributeSet attrs) { 111 super(context, attrs); 112 } 113 114 @Override onFinishInflate()115 protected void onFinishInflate() { 116 super.onFinishInflate(); 117 Resources res = getResources(); 118 119 mShowEditIcon = res.getBoolean(R.bool.config_showQuickSettingsEditingIcon); 120 121 mEdit = findViewById(android.R.id.edit); 122 mEdit.setVisibility(mShowEditIcon ? VISIBLE : GONE); 123 124 if (mShowEditIcon) { 125 findViewById(android.R.id.edit).setOnClickListener(view -> 126 Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> 127 mQsPanel.showEdit(view))); 128 } 129 130 mDateTimeGroup = findViewById(id.date_time_alarm_group); 131 mDate = findViewById(R.id.date); 132 133 mExpandIndicator = findViewById(R.id.expand_indicator); 134 mExpandIndicator.setVisibility( 135 res.getBoolean(R.bool.config_showQuickSettingsExpandIndicator) 136 ? VISIBLE : GONE); 137 138 mSettingsButton = findViewById(R.id.settings_button); 139 mSettingsContainer = findViewById(R.id.settings_button_container); 140 mSettingsButton.setOnClickListener(this); 141 142 mAlarmStatusCollapsed = findViewById(R.id.alarm_status_collapsed); 143 mAlarmStatus = findViewById(R.id.alarm_status); 144 mDateTimeGroup.setOnClickListener(this); 145 146 mMultiUserSwitch = findViewById(R.id.multi_user_switch); 147 mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar); 148 mAlwaysShowMultiUserSwitch = res.getBoolean(R.bool.config_alwaysShowMultiUserSwitcher); 149 150 // RenderThread is doing more harm than good when touching the header (to expand quick 151 // settings), so disable it for this view 152 ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true); 153 ((RippleDrawable) mExpandIndicator.getBackground()).setForceSoftware(true); 154 155 updateResources(); 156 157 mNextAlarmController = Dependency.get(NextAlarmController.class); 158 mUserInfoController = Dependency.get(UserInfoController.class); 159 mActivityStarter = Dependency.get(ActivityStarter.class); 160 addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, 161 oldBottom) -> updateAnimator(right - left)); 162 } 163 updateAnimator(int width)164 private void updateAnimator(int width) { 165 int numTiles = QuickQSPanel.getNumQuickTiles(mContext); 166 int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size) 167 - mContext.getResources().getDimensionPixelSize(dimen.qs_quick_tile_padding); 168 int remaining = (width - numTiles * size) / (numTiles - 1); 169 int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space); 170 171 mAnimator = new Builder() 172 .addFloat(mSettingsContainer, "translationX", -(remaining - defSpace), 0) 173 .addFloat(mSettingsButton, "rotation", -120, 0) 174 .build(); 175 if (mAlarmShowing) { 176 mAlarmAnimator = new Builder().addFloat(mDate, "alpha", 1, 0) 177 .addFloat(mDateTimeGroup, "translationX", 0, -mDate.getWidth()) 178 .addFloat(mAlarmStatus, "alpha", 0, 1) 179 .setListener(new ListenerAdapter() { 180 @Override 181 public void onAnimationAtStart() { 182 mAlarmStatus.setVisibility(View.GONE); 183 } 184 185 @Override 186 public void onAnimationStarted() { 187 mAlarmStatus.setVisibility(View.VISIBLE); 188 } 189 }).build(); 190 } else { 191 mAlarmAnimator = null; 192 mAlarmStatus.setVisibility(View.GONE); 193 mDate.setAlpha(1); 194 mDateTimeGroup.setTranslationX(0); 195 } 196 setExpansion(mExpansionAmount); 197 } 198 199 @Override onConfigurationChanged(Configuration newConfig)200 protected void onConfigurationChanged(Configuration newConfig) { 201 super.onConfigurationChanged(newConfig); 202 updateResources(); 203 } 204 205 @Override onRtlPropertiesChanged(int layoutDirection)206 public void onRtlPropertiesChanged(int layoutDirection) { 207 super.onRtlPropertiesChanged(layoutDirection); 208 updateResources(); 209 } 210 updateResources()211 private void updateResources() { 212 FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size); 213 214 updateSettingsAnimator(); 215 } 216 updateSettingsAnimator()217 private void updateSettingsAnimator() { 218 mSettingsAlpha = createSettingsAlphaAnimator(); 219 220 final boolean isRtl = isLayoutRtl(); 221 if (isRtl && mDate.getWidth() == 0) { 222 mDate.addOnLayoutChangeListener(new OnLayoutChangeListener() { 223 @Override 224 public void onLayoutChange(View v, int left, int top, int right, int bottom, 225 int oldLeft, int oldTop, int oldRight, int oldBottom) { 226 mDate.setPivotX(getWidth()); 227 mDate.removeOnLayoutChangeListener(this); 228 } 229 }); 230 } else { 231 mDate.setPivotX(isRtl ? mDate.getWidth() : 0); 232 } 233 } 234 235 @Nullable createSettingsAlphaAnimator()236 private TouchAnimator createSettingsAlphaAnimator() { 237 // If the settings icon is not shown and the user switcher is always shown, then there 238 // is nothing to animate. 239 if (!mShowEditIcon && mAlwaysShowMultiUserSwitch) { 240 return null; 241 } 242 243 TouchAnimator.Builder animatorBuilder = new TouchAnimator.Builder(); 244 animatorBuilder.setStartDelay(QSAnimator.EXPANDED_TILE_DELAY); 245 246 if (mShowEditIcon) { 247 animatorBuilder.addFloat(mEdit, "alpha", 0, 1); 248 } 249 250 if (!mAlwaysShowMultiUserSwitch) { 251 animatorBuilder.addFloat(mMultiUserSwitch, "alpha", 0, 1); 252 } 253 254 return animatorBuilder.build(); 255 } 256 setKeyguardShowing(boolean keyguardShowing)257 public void setKeyguardShowing(boolean keyguardShowing) { 258 mKeyguardShowing = keyguardShowing; 259 setExpansion(mExpansionAmount); 260 } 261 setExpanded(boolean expanded)262 public void setExpanded(boolean expanded) { 263 if (mExpanded == expanded) return; 264 mExpanded = expanded; 265 updateEverything(); 266 } 267 268 @Override onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm)269 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { 270 mNextAlarm = nextAlarm; 271 if (nextAlarm != null) { 272 String alarmString = KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm); 273 mAlarmStatus.setText(alarmString); 274 mAlarmStatus.setContentDescription(mContext.getString( 275 R.string.accessibility_quick_settings_alarm, alarmString)); 276 mAlarmStatusCollapsed.setContentDescription(mContext.getString( 277 R.string.accessibility_quick_settings_alarm, alarmString)); 278 } 279 if (mAlarmShowing != (nextAlarm != null)) { 280 mAlarmShowing = nextAlarm != null; 281 updateAnimator(getWidth()); 282 updateEverything(); 283 } 284 } 285 setExpansion(float headerExpansionFraction)286 public void setExpansion(float headerExpansionFraction) { 287 mExpansionAmount = headerExpansionFraction; 288 if (mAnimator != null) mAnimator.setPosition(headerExpansionFraction); 289 if (mAlarmAnimator != null) mAlarmAnimator.setPosition( 290 mKeyguardShowing ? 0 : headerExpansionFraction); 291 292 if (mSettingsAlpha != null) { 293 mSettingsAlpha.setPosition(headerExpansionFraction); 294 } 295 296 updateAlarmVisibilities(); 297 298 mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD); 299 } 300 301 @Override 302 @VisibleForTesting onDetachedFromWindow()303 public void onDetachedFromWindow() { 304 setListening(false); 305 super.onDetachedFromWindow(); 306 } 307 updateAlarmVisibilities()308 private void updateAlarmVisibilities() { 309 mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE); 310 } 311 setListening(boolean listening)312 public void setListening(boolean listening) { 313 if (listening == mListening) { 314 return; 315 } 316 mListening = listening; 317 updateListeners(); 318 } 319 getExpandView()320 public View getExpandView() { 321 return findViewById(R.id.expand_indicator); 322 } 323 updateEverything()324 public void updateEverything() { 325 post(() -> { 326 updateVisibilities(); 327 setClickable(false); 328 }); 329 } 330 updateVisibilities()331 private void updateVisibilities() { 332 updateAlarmVisibilities(); 333 mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility( 334 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE); 335 final boolean isDemo = UserManager.isDeviceInDemoMode(mContext); 336 337 mMultiUserSwitch.setVisibility((mExpanded || mAlwaysShowMultiUserSwitch) 338 && mMultiUserSwitch.hasMultipleUsers() && !isDemo 339 ? View.VISIBLE : View.INVISIBLE); 340 341 if (mShowEditIcon) { 342 mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); 343 } 344 } 345 updateListeners()346 private void updateListeners() { 347 if (mListening) { 348 mNextAlarmController.addCallback(this); 349 mUserInfoController.addCallback(this); 350 if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) { 351 Dependency.get(NetworkController.class).addEmergencyListener(this); 352 Dependency.get(NetworkController.class).addCallback(this); 353 } 354 } else { 355 mNextAlarmController.removeCallback(this); 356 mUserInfoController.removeCallback(this); 357 Dependency.get(NetworkController.class).removeEmergencyListener(this); 358 Dependency.get(NetworkController.class).removeCallback(this); 359 } 360 } 361 setQSPanel(final QSPanel qsPanel)362 public void setQSPanel(final QSPanel qsPanel) { 363 mQsPanel = qsPanel; 364 if (mQsPanel != null) { 365 mMultiUserSwitch.setQsPanel(qsPanel); 366 } 367 } 368 369 @Override onClick(View v)370 public void onClick(View v) { 371 if (v == mSettingsButton) { 372 if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) { 373 // If user isn't setup just unlock the device and dump them back at SUW. 374 mActivityStarter.postQSRunnableDismissingKeyguard(() -> { }); 375 return; 376 } 377 MetricsLogger.action(mContext, 378 mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH 379 : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH); 380 if (mSettingsButton.isTunerClick()) { 381 Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> { 382 if (TunerService.isTunerEnabled(mContext)) { 383 TunerService.showResetRequest(mContext, () -> { 384 // Relaunch settings so that the tuner disappears. 385 startSettingsActivity(); 386 }); 387 } else { 388 Toast.makeText(getContext(), R.string.tuner_toast, 389 Toast.LENGTH_LONG).show(); 390 TunerService.setTunerEnabled(mContext, true); 391 } 392 startSettingsActivity(); 393 394 }); 395 } else { 396 startSettingsActivity(); 397 } 398 } else if (v == mDateTimeGroup) { 399 Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE, 400 mNextAlarm != null); 401 if (mNextAlarm != null) { 402 PendingIntent showIntent = mNextAlarm.getShowIntent(); 403 mActivityStarter.startPendingIntentDismissingKeyguard(showIntent); 404 } else { 405 mActivityStarter.postStartActivityDismissingKeyguard(new Intent( 406 AlarmClock.ACTION_SHOW_ALARMS), 0); 407 } 408 } 409 } 410 startSettingsActivity()411 private void startSettingsActivity() { 412 mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS), 413 true /* dismissShade */); 414 } 415 416 @Override setEmergencyCallsOnly(boolean show)417 public void setEmergencyCallsOnly(boolean show) { 418 boolean changed = show != mShowEmergencyCallsOnly; 419 if (changed) { 420 mShowEmergencyCallsOnly = show; 421 if (mExpanded) { 422 updateEverything(); 423 } 424 } 425 } 426 427 @Override onUserInfoChanged(String name, Drawable picture, String userAccount)428 public void onUserInfoChanged(String name, Drawable picture, String userAccount) { 429 if (picture != null && 430 UserManager.get(mContext).isGuestUser(ActivityManager.getCurrentUser())) { 431 picture = picture.getConstantState().newDrawable().mutate(); 432 picture.setColorFilter( 433 Utils.getColorAttr(mContext, android.R.attr.colorForeground), 434 Mode.SRC_IN); 435 } 436 mMultiUserAvatar.setImageDrawable(picture); 437 } 438 } 439