1 /* 2 * Copyright (C) 2017 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 android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; 20 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.UserInfo; 24 import android.content.res.ColorStateList; 25 import android.content.res.Configuration; 26 import android.graphics.PorterDuff.Mode; 27 import android.graphics.drawable.Drawable; 28 import android.graphics.drawable.RippleDrawable; 29 import android.os.Bundle; 30 import android.os.UserManager; 31 import android.support.annotation.Nullable; 32 import android.support.annotation.VisibleForTesting; 33 import android.text.TextUtils; 34 import android.util.AttributeSet; 35 import android.view.View; 36 import android.view.View.OnClickListener; 37 import android.view.accessibility.AccessibilityNodeInfo; 38 import android.widget.FrameLayout; 39 import android.widget.ImageView; 40 import android.widget.LinearLayout; 41 import android.widget.Toast; 42 43 import com.android.internal.logging.MetricsLogger; 44 import com.android.internal.logging.nano.MetricsProto; 45 import com.android.keyguard.CarrierText; 46 import com.android.keyguard.KeyguardUpdateMonitor; 47 import com.android.settingslib.Utils; 48 import com.android.settingslib.drawable.UserIconDrawable; 49 import com.android.settingslib.graph.SignalDrawable; 50 import com.android.systemui.Dependency; 51 import com.android.systemui.R; 52 import com.android.systemui.R.dimen; 53 import com.android.systemui.plugins.ActivityStarter; 54 import com.android.systemui.qs.TouchAnimator.Builder; 55 import com.android.systemui.statusbar.phone.MultiUserSwitch; 56 import com.android.systemui.statusbar.phone.SettingsButton; 57 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 58 import com.android.systemui.statusbar.policy.NetworkController; 59 import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener; 60 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; 61 import com.android.systemui.statusbar.policy.UserInfoController; 62 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; 63 import com.android.systemui.tuner.TunerService; 64 65 public class QSFooterImpl extends FrameLayout implements QSFooter, 66 OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback { 67 68 private ActivityStarter mActivityStarter; 69 private UserInfoController mUserInfoController; 70 private SettingsButton mSettingsButton; 71 protected View mSettingsContainer; 72 private PageIndicator mPageIndicator; 73 private CarrierText mCarrierText; 74 75 private boolean mQsDisabled; 76 private QSPanel mQsPanel; 77 78 private boolean mExpanded; 79 80 private boolean mListening; 81 82 private boolean mShowEmergencyCallsOnly; 83 private View mDivider; 84 protected MultiUserSwitch mMultiUserSwitch; 85 private ImageView mMultiUserAvatar; 86 87 protected TouchAnimator mFooterAnimator; 88 private float mExpansionAmount; 89 90 protected View mEdit; 91 private TouchAnimator mSettingsCogAnimator; 92 93 private View mActionsContainer; 94 private View mDragHandle; 95 private View mMobileGroup; 96 private ImageView mMobileSignal; 97 private ImageView mMobileRoaming; 98 private final int mColorForeground; 99 private final CellSignalState mInfo = new CellSignalState(); 100 private OnClickListener mExpandClickListener; 101 102 public QSFooterImpl(Context context, AttributeSet attrs) { 103 super(context, attrs); 104 mColorForeground = Utils.getColorAttr(context, android.R.attr.colorForeground); 105 } 106 107 @Override 108 protected void onFinishInflate() { 109 super.onFinishInflate(); 110 mDivider = findViewById(R.id.qs_footer_divider); 111 mEdit = findViewById(android.R.id.edit); 112 mEdit.setOnClickListener(view -> 113 Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> 114 mQsPanel.showEdit(view))); 115 116 mPageIndicator = findViewById(R.id.footer_page_indicator); 117 118 mSettingsButton = findViewById(R.id.settings_button); 119 mSettingsContainer = findViewById(R.id.settings_button_container); 120 mSettingsButton.setOnClickListener(this); 121 122 mMobileGroup = findViewById(R.id.mobile_combo); 123 mMobileSignal = findViewById(R.id.mobile_signal); 124 mMobileRoaming = findViewById(R.id.mobile_roaming); 125 mCarrierText = findViewById(R.id.qs_carrier_text); 126 mCarrierText.setDisplayFlags( 127 CarrierText.FLAG_HIDE_AIRPLANE_MODE | CarrierText.FLAG_HIDE_MISSING_SIM); 128 129 mMultiUserSwitch = findViewById(R.id.multi_user_switch); 130 mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar); 131 132 mDragHandle = findViewById(R.id.qs_drag_handle_view); 133 mActionsContainer = findViewById(R.id.qs_footer_actions_container); 134 135 // RenderThread is doing more harm than good when touching the header (to expand quick 136 // settings), so disable it for this view 137 ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true); 138 139 updateResources(); 140 141 mUserInfoController = Dependency.get(UserInfoController.class); 142 mActivityStarter = Dependency.get(ActivityStarter.class); 143 addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, 144 oldBottom) -> updateAnimator(right - left)); 145 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 146 } 147 148 private void updateAnimator(int width) { 149 int numTiles = QuickQSPanel.getNumQuickTiles(mContext); 150 int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size) 151 - mContext.getResources().getDimensionPixelSize(dimen.qs_quick_tile_padding); 152 int remaining = (width - numTiles * size) / (numTiles - 1); 153 int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space); 154 155 mSettingsCogAnimator = new Builder() 156 .addFloat(mSettingsContainer, "translationX", 157 isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0) 158 .addFloat(mSettingsButton, "rotation", -120, 0) 159 .build(); 160 161 setExpansion(mExpansionAmount); 162 } 163 164 @Override 165 protected void onConfigurationChanged(Configuration newConfig) { 166 super.onConfigurationChanged(newConfig); 167 updateResources(); 168 } 169 170 @Override 171 public void onRtlPropertiesChanged(int layoutDirection) { 172 super.onRtlPropertiesChanged(layoutDirection); 173 updateResources(); 174 } 175 176 private void updateResources() { 177 updateFooterAnimator(); 178 179 // Update the width and weight of the actions container as the page indicator can sometimes 180 // show and the layout needs to center it between the carrier text and actions container. 181 LinearLayout.LayoutParams params = 182 (LinearLayout.LayoutParams) mActionsContainer.getLayoutParams(); 183 params.width = mContext.getResources().getInteger(R.integer.qs_footer_actions_width); 184 params.weight = mContext.getResources().getInteger(R.integer.qs_footer_actions_weight); 185 mActionsContainer.setLayoutParams(params); 186 } 187 188 private void updateFooterAnimator() { 189 mFooterAnimator = createFooterAnimator(); 190 } 191 192 @Nullable 193 private TouchAnimator createFooterAnimator() { 194 return new TouchAnimator.Builder() 195 .addFloat(mDivider, "alpha", 0, 1) 196 .addFloat(mCarrierText, "alpha", 0, 0, 1) 197 .addFloat(mMobileGroup, "alpha", 0, 1) 198 .addFloat(mActionsContainer, "alpha", 0, 1) 199 .addFloat(mDragHandle, "alpha", 1, 0, 0) 200 .addFloat(mPageIndicator, "alpha", 0, 1) 201 .setStartDelay(0.15f) 202 .build(); 203 } 204 205 @Override 206 public void setKeyguardShowing(boolean keyguardShowing) { 207 setExpansion(mExpansionAmount); 208 } 209 210 @Override 211 public void setExpandClickListener(OnClickListener onClickListener) { 212 mExpandClickListener = onClickListener; 213 } 214 215 @Override 216 public void setExpanded(boolean expanded) { 217 if (mExpanded == expanded) return; 218 mExpanded = expanded; 219 updateEverything(); 220 } 221 222 @Override 223 public void setExpansion(float headerExpansionFraction) { 224 mExpansionAmount = headerExpansionFraction; 225 if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction); 226 227 if (mFooterAnimator != null) { 228 mFooterAnimator.setPosition(headerExpansionFraction); 229 } 230 } 231 232 @Override 233 @VisibleForTesting 234 public void onDetachedFromWindow() { 235 setListening(false); 236 super.onDetachedFromWindow(); 237 } 238 239 @Override 240 public void setListening(boolean listening) { 241 if (listening == mListening) { 242 return; 243 } 244 mListening = listening; 245 updateListeners(); 246 } 247 248 @Override 249 public boolean performAccessibilityAction(int action, Bundle arguments) { 250 if (action == AccessibilityNodeInfo.ACTION_EXPAND) { 251 if (mExpandClickListener != null) { 252 mExpandClickListener.onClick(null); 253 return true; 254 } 255 } 256 return super.performAccessibilityAction(action, arguments); 257 } 258 259 @Override 260 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 261 super.onInitializeAccessibilityNodeInfo(info); 262 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); 263 } 264 265 @Override 266 public void disable(int state1, int state2, boolean animate) { 267 final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0; 268 if (disabled == mQsDisabled) return; 269 mQsDisabled = disabled; 270 updateEverything(); 271 } 272 273 public void updateEverything() { 274 post(() -> { 275 updateVisibilities(); 276 setClickable(false); 277 }); 278 } 279 280 private void updateVisibilities() { 281 mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE); 282 mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility( 283 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE); 284 final boolean isDemo = UserManager.isDeviceInDemoMode(mContext); 285 mMultiUserSwitch.setVisibility(showUserSwitcher(isDemo) ? View.VISIBLE : View.INVISIBLE); 286 mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); 287 mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); 288 } 289 290 private boolean showUserSwitcher(boolean isDemo) { 291 if (!mExpanded || isDemo || !UserManager.supportsMultipleUsers()) { 292 return false; 293 } 294 UserManager userManager = UserManager.get(mContext); 295 if (userManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)) { 296 return false; 297 } 298 int switchableUserCount = 0; 299 for (UserInfo user : userManager.getUsers(true)) { 300 if (user.supportsSwitchToByUser()) { 301 ++switchableUserCount; 302 if (switchableUserCount > 1) { 303 return true; 304 } 305 } 306 } 307 return getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user); 308 } 309 310 private void updateListeners() { 311 if (mListening) { 312 mUserInfoController.addCallback(this); 313 if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) { 314 Dependency.get(NetworkController.class).addEmergencyListener(this); 315 Dependency.get(NetworkController.class).addCallback(this); 316 } 317 } else { 318 mUserInfoController.removeCallback(this); 319 Dependency.get(NetworkController.class).removeEmergencyListener(this); 320 Dependency.get(NetworkController.class).removeCallback(this); 321 } 322 } 323 324 @Override 325 public void setQSPanel(final QSPanel qsPanel) { 326 mQsPanel = qsPanel; 327 if (mQsPanel != null) { 328 mMultiUserSwitch.setQsPanel(qsPanel); 329 mQsPanel.setFooterPageIndicator(mPageIndicator); 330 } 331 } 332 333 @Override 334 public void onClick(View v) { 335 // Don't do anything until view are unhidden 336 if (!mExpanded) { 337 return; 338 } 339 340 if (v == mSettingsButton) { 341 if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) { 342 // If user isn't setup just unlock the device and dump them back at SUW. 343 mActivityStarter.postQSRunnableDismissingKeyguard(() -> { }); 344 return; 345 } 346 MetricsLogger.action(mContext, 347 mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH 348 : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH); 349 if (mSettingsButton.isTunerClick()) { 350 Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> { 351 if (TunerService.isTunerEnabled(mContext)) { 352 TunerService.showResetRequest(mContext, () -> { 353 // Relaunch settings so that the tuner disappears. 354 startSettingsActivity(); 355 }); 356 } else { 357 Toast.makeText(getContext(), R.string.tuner_toast, 358 Toast.LENGTH_LONG).show(); 359 TunerService.setTunerEnabled(mContext, true); 360 } 361 startSettingsActivity(); 362 363 }); 364 } else { 365 startSettingsActivity(); 366 } 367 } 368 } 369 370 private void startSettingsActivity() { 371 mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS), 372 true /* dismissShade */); 373 } 374 375 @Override 376 public void setEmergencyCallsOnly(boolean show) { 377 boolean changed = show != mShowEmergencyCallsOnly; 378 if (changed) { 379 mShowEmergencyCallsOnly = show; 380 if (mExpanded) { 381 updateEverything(); 382 } 383 } 384 } 385 386 @Override 387 public void onUserInfoChanged(String name, Drawable picture, String userAccount) { 388 if (picture != null && 389 UserManager.get(mContext).isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) && 390 !(picture instanceof UserIconDrawable)) { 391 picture = picture.getConstantState().newDrawable(mContext.getResources()).mutate(); 392 picture.setColorFilter( 393 Utils.getColorAttr(mContext, android.R.attr.colorForeground), 394 Mode.SRC_IN); 395 } 396 mMultiUserAvatar.setImageDrawable(picture); 397 } 398 399 private void handleUpdateState() { 400 mMobileGroup.setVisibility(mInfo.visible ? View.VISIBLE : View.GONE); 401 if (mInfo.visible) { 402 mMobileRoaming.setVisibility(mInfo.roaming ? View.VISIBLE : View.GONE); 403 mMobileRoaming.setImageTintList(ColorStateList.valueOf(mColorForeground)); 404 SignalDrawable d = new SignalDrawable(mContext); 405 d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground)); 406 mMobileSignal.setImageDrawable(d); 407 mMobileSignal.setImageLevel(mInfo.mobileSignalIconId); 408 409 StringBuilder contentDescription = new StringBuilder(); 410 if (mInfo.contentDescription != null) { 411 contentDescription.append(mInfo.contentDescription).append(", "); 412 } 413 if (mInfo.roaming) { 414 contentDescription 415 .append(mContext.getString(R.string.data_connection_roaming)) 416 .append(", "); 417 } 418 // TODO: show mobile data off/no internet text for 5 seconds before carrier text 419 if (TextUtils.equals(mInfo.typeContentDescription, 420 mContext.getString(R.string.data_connection_no_internet)) 421 || TextUtils.equals(mInfo.typeContentDescription, 422 mContext.getString(R.string.cell_data_off_content_description))) { 423 contentDescription.append(mInfo.typeContentDescription); 424 } 425 mMobileSignal.setContentDescription(contentDescription); 426 } 427 } 428 429 @Override 430 public void setMobileDataIndicators(NetworkController.IconState statusIcon, 431 NetworkController.IconState qsIcon, int statusType, 432 int qsType, boolean activityIn, boolean activityOut, 433 String typeContentDescription, 434 String description, boolean isWide, int subId, boolean roaming) { 435 mInfo.visible = statusIcon.visible; 436 mInfo.mobileSignalIconId = statusIcon.icon; 437 mInfo.contentDescription = statusIcon.contentDescription; 438 mInfo.typeContentDescription = typeContentDescription; 439 mInfo.roaming = roaming; 440 handleUpdateState(); 441 } 442 443 @Override 444 public void setNoSims(boolean hasNoSims, boolean simDetected) { 445 if (hasNoSims) { 446 mInfo.visible = false; 447 } 448 handleUpdateState(); 449 } 450 451 private final class CellSignalState { 452 boolean visible; 453 int mobileSignalIconId; 454 public String contentDescription; 455 String typeContentDescription; 456 boolean roaming; 457 } 458 } 459