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 static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; 22 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.res.Configuration; 26 import android.database.ContentObserver; 27 import android.graphics.PorterDuff.Mode; 28 import android.graphics.drawable.Drawable; 29 import android.graphics.drawable.RippleDrawable; 30 import android.net.Uri; 31 import android.os.Build; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.UserHandle; 35 import android.os.UserManager; 36 import android.provider.Settings; 37 import android.util.AttributeSet; 38 import android.view.View; 39 import android.view.View.OnClickListener; 40 import android.view.accessibility.AccessibilityNodeInfo; 41 import android.widget.FrameLayout; 42 import android.widget.ImageView; 43 import android.widget.TextView; 44 import android.widget.Toast; 45 46 import androidx.annotation.Nullable; 47 import androidx.annotation.VisibleForTesting; 48 49 import com.android.internal.logging.MetricsLogger; 50 import com.android.internal.logging.nano.MetricsProto; 51 import com.android.keyguard.KeyguardUpdateMonitor; 52 import com.android.settingslib.Utils; 53 import com.android.settingslib.development.DevelopmentSettingsEnabler; 54 import com.android.settingslib.drawable.UserIconDrawable; 55 import com.android.systemui.Dependency; 56 import com.android.systemui.R; 57 import com.android.systemui.R.dimen; 58 import com.android.systemui.plugins.ActivityStarter; 59 import com.android.systemui.qs.TouchAnimator.Builder; 60 import com.android.systemui.statusbar.phone.MultiUserSwitch; 61 import com.android.systemui.statusbar.phone.SettingsButton; 62 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 63 import com.android.systemui.statusbar.policy.UserInfoController; 64 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; 65 import com.android.systemui.tuner.TunerService; 66 67 import javax.inject.Inject; 68 import javax.inject.Named; 69 70 public class QSFooterImpl extends FrameLayout implements QSFooter, 71 OnClickListener, OnUserInfoChangedListener { 72 73 private static final String TAG = "QSFooterImpl"; 74 75 private final ActivityStarter mActivityStarter; 76 private final UserInfoController mUserInfoController; 77 private final DeviceProvisionedController mDeviceProvisionedController; 78 private SettingsButton mSettingsButton; 79 protected View mSettingsContainer; 80 private PageIndicator mPageIndicator; 81 82 private boolean mQsDisabled; 83 private QSPanel mQsPanel; 84 85 private boolean mExpanded; 86 87 private boolean mListening; 88 89 protected MultiUserSwitch mMultiUserSwitch; 90 private ImageView mMultiUserAvatar; 91 92 protected TouchAnimator mFooterAnimator; 93 private float mExpansionAmount; 94 95 protected View mEdit; 96 protected View mEditContainer; 97 private TouchAnimator mSettingsCogAnimator; 98 99 private View mActionsContainer; 100 private View mDragHandle; 101 102 private OnClickListener mExpandClickListener; 103 104 private final ContentObserver mDeveloperSettingsObserver = new ContentObserver( 105 new Handler(mContext.getMainLooper())) { 106 @Override 107 public void onChange(boolean selfChange, Uri uri) { 108 super.onChange(selfChange, uri); 109 setBuildText(); 110 } 111 }; 112 113 @Inject QSFooterImpl(@amedVIEW_CONTEXT) Context context, AttributeSet attrs, ActivityStarter activityStarter, UserInfoController userInfoController, DeviceProvisionedController deviceProvisionedController)114 public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, 115 ActivityStarter activityStarter, UserInfoController userInfoController, 116 DeviceProvisionedController deviceProvisionedController) { 117 super(context, attrs); 118 mActivityStarter = activityStarter; 119 mUserInfoController = userInfoController; 120 mDeviceProvisionedController = deviceProvisionedController; 121 } 122 123 @VisibleForTesting QSFooterImpl(Context context, AttributeSet attrs)124 public QSFooterImpl(Context context, AttributeSet attrs) { 125 this(context, attrs, 126 Dependency.get(ActivityStarter.class), 127 Dependency.get(UserInfoController.class), 128 Dependency.get(DeviceProvisionedController.class)); 129 } 130 131 @Override onFinishInflate()132 protected void onFinishInflate() { 133 super.onFinishInflate(); 134 mEdit = findViewById(android.R.id.edit); 135 mEdit.setOnClickListener(view -> 136 mActivityStarter.postQSRunnableDismissingKeyguard(() -> 137 mQsPanel.showEdit(view))); 138 139 mPageIndicator = findViewById(R.id.footer_page_indicator); 140 141 mSettingsButton = findViewById(R.id.settings_button); 142 mSettingsContainer = findViewById(R.id.settings_button_container); 143 mSettingsButton.setOnClickListener(this); 144 145 mMultiUserSwitch = findViewById(R.id.multi_user_switch); 146 mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar); 147 148 mDragHandle = findViewById(R.id.qs_drag_handle_view); 149 mActionsContainer = findViewById(R.id.qs_footer_actions_container); 150 mEditContainer = findViewById(R.id.qs_footer_actions_edit_container); 151 152 // RenderThread is doing more harm than good when touching the header (to expand quick 153 // settings), so disable it for this view 154 ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true); 155 156 updateResources(); 157 158 addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, 159 oldBottom) -> updateAnimator(right - left)); 160 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 161 updateEverything(); 162 setBuildText(); 163 } 164 setBuildText()165 private void setBuildText() { 166 TextView v = findViewById(R.id.build); 167 if (v == null) return; 168 if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) { 169 v.setText(mContext.getString( 170 com.android.internal.R.string.bugreport_status, 171 Build.VERSION.RELEASE, 172 Build.ID)); 173 v.setVisibility(View.VISIBLE); 174 } else { 175 v.setVisibility(View.GONE); 176 } 177 } 178 updateAnimator(int width)179 private void updateAnimator(int width) { 180 int numTiles = QuickQSPanel.getNumQuickTiles(mContext); 181 int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size) 182 - mContext.getResources().getDimensionPixelSize(dimen.qs_quick_tile_padding); 183 int remaining = (width - numTiles * size) / (numTiles - 1); 184 int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space); 185 186 mSettingsCogAnimator = new Builder() 187 .addFloat(mSettingsContainer, "translationX", 188 isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0) 189 .addFloat(mSettingsButton, "rotation", -120, 0) 190 .build(); 191 192 setExpansion(mExpansionAmount); 193 } 194 195 @Override onConfigurationChanged(Configuration newConfig)196 protected void onConfigurationChanged(Configuration newConfig) { 197 super.onConfigurationChanged(newConfig); 198 updateResources(); 199 } 200 201 @Override onRtlPropertiesChanged(int layoutDirection)202 public void onRtlPropertiesChanged(int layoutDirection) { 203 super.onRtlPropertiesChanged(layoutDirection); 204 updateResources(); 205 } 206 updateResources()207 private void updateResources() { 208 updateFooterAnimator(); 209 } 210 updateFooterAnimator()211 private void updateFooterAnimator() { 212 mFooterAnimator = createFooterAnimator(); 213 } 214 215 @Nullable createFooterAnimator()216 private TouchAnimator createFooterAnimator() { 217 return new TouchAnimator.Builder() 218 .addFloat(mActionsContainer, "alpha", 0, 1) 219 .addFloat(mEditContainer, "alpha", 0, 1) 220 .addFloat(mDragHandle, "alpha", 1, 0, 0) 221 .addFloat(mPageIndicator, "alpha", 0, 1) 222 .setStartDelay(0.15f) 223 .build(); 224 } 225 226 @Override setKeyguardShowing(boolean keyguardShowing)227 public void setKeyguardShowing(boolean keyguardShowing) { 228 setExpansion(mExpansionAmount); 229 } 230 231 @Override setExpandClickListener(OnClickListener onClickListener)232 public void setExpandClickListener(OnClickListener onClickListener) { 233 mExpandClickListener = onClickListener; 234 } 235 236 @Override setExpanded(boolean expanded)237 public void setExpanded(boolean expanded) { 238 if (mExpanded == expanded) return; 239 mExpanded = expanded; 240 updateEverything(); 241 } 242 243 @Override setExpansion(float headerExpansionFraction)244 public void setExpansion(float headerExpansionFraction) { 245 mExpansionAmount = headerExpansionFraction; 246 if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction); 247 248 if (mFooterAnimator != null) { 249 mFooterAnimator.setPosition(headerExpansionFraction); 250 } 251 } 252 253 @Override onAttachedToWindow()254 protected void onAttachedToWindow() { 255 super.onAttachedToWindow(); 256 mContext.getContentResolver().registerContentObserver( 257 Settings.Global.getUriFor(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED), false, 258 mDeveloperSettingsObserver, UserHandle.USER_ALL); 259 } 260 261 @Override 262 @VisibleForTesting onDetachedFromWindow()263 public void onDetachedFromWindow() { 264 setListening(false); 265 mContext.getContentResolver().unregisterContentObserver(mDeveloperSettingsObserver); 266 super.onDetachedFromWindow(); 267 } 268 269 @Override setListening(boolean listening)270 public void setListening(boolean listening) { 271 if (listening == mListening) { 272 return; 273 } 274 mListening = listening; 275 updateListeners(); 276 } 277 278 @Override performAccessibilityAction(int action, Bundle arguments)279 public boolean performAccessibilityAction(int action, Bundle arguments) { 280 if (action == AccessibilityNodeInfo.ACTION_EXPAND) { 281 if (mExpandClickListener != null) { 282 mExpandClickListener.onClick(null); 283 return true; 284 } 285 } 286 return super.performAccessibilityAction(action, arguments); 287 } 288 289 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)290 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 291 super.onInitializeAccessibilityNodeInfo(info); 292 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); 293 } 294 295 @Override disable(int state1, int state2, boolean animate)296 public void disable(int state1, int state2, boolean animate) { 297 final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0; 298 if (disabled == mQsDisabled) return; 299 mQsDisabled = disabled; 300 updateEverything(); 301 } 302 updateEverything()303 public void updateEverything() { 304 post(() -> { 305 updateVisibilities(); 306 updateClickabilities(); 307 setClickable(false); 308 }); 309 } 310 updateClickabilities()311 private void updateClickabilities() { 312 mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE); 313 mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE); 314 mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE); 315 } 316 updateVisibilities()317 private void updateVisibilities() { 318 mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE); 319 mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility( 320 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE); 321 final boolean isDemo = UserManager.isDeviceInDemoMode(mContext); 322 mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE); 323 mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); 324 mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); 325 } 326 showUserSwitcher()327 private boolean showUserSwitcher() { 328 return mExpanded && mMultiUserSwitch.isMultiUserEnabled(); 329 } 330 updateListeners()331 private void updateListeners() { 332 if (mListening) { 333 mUserInfoController.addCallback(this); 334 } else { 335 mUserInfoController.removeCallback(this); 336 } 337 } 338 339 @Override setQSPanel(final QSPanel qsPanel)340 public void setQSPanel(final QSPanel qsPanel) { 341 mQsPanel = qsPanel; 342 if (mQsPanel != null) { 343 mMultiUserSwitch.setQsPanel(qsPanel); 344 mQsPanel.setFooterPageIndicator(mPageIndicator); 345 } 346 } 347 348 @Override onClick(View v)349 public void onClick(View v) { 350 // Don't do anything until view are unhidden 351 if (!mExpanded) { 352 return; 353 } 354 355 if (v == mSettingsButton) { 356 if (!mDeviceProvisionedController.isCurrentUserSetup()) { 357 // If user isn't setup just unlock the device and dump them back at SUW. 358 mActivityStarter.postQSRunnableDismissingKeyguard(() -> { 359 }); 360 return; 361 } 362 MetricsLogger.action(mContext, 363 mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH 364 : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH); 365 if (mSettingsButton.isTunerClick()) { 366 mActivityStarter.postQSRunnableDismissingKeyguard(() -> { 367 if (TunerService.isTunerEnabled(mContext)) { 368 TunerService.showResetRequest(mContext, () -> { 369 // Relaunch settings so that the tuner disappears. 370 startSettingsActivity(); 371 }); 372 } else { 373 Toast.makeText(getContext(), R.string.tuner_toast, 374 Toast.LENGTH_LONG).show(); 375 TunerService.setTunerEnabled(mContext, true); 376 } 377 startSettingsActivity(); 378 379 }); 380 } else { 381 startSettingsActivity(); 382 } 383 } 384 } 385 startSettingsActivity()386 private void startSettingsActivity() { 387 mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS), 388 true /* dismissShade */); 389 } 390 391 @Override onUserInfoChanged(String name, Drawable picture, String userAccount)392 public void onUserInfoChanged(String name, Drawable picture, String userAccount) { 393 if (picture != null && 394 UserManager.get(mContext).isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) && 395 !(picture instanceof UserIconDrawable)) { 396 picture = picture.getConstantState().newDrawable(mContext.getResources()).mutate(); 397 picture.setColorFilter( 398 Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground), 399 Mode.SRC_IN); 400 } 401 mMultiUserAvatar.setImageDrawable(picture); 402 } 403 } 404