1 /* 2 * Copyright (C) 2022 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.accessibility; 18 19 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; 20 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; 21 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; 22 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 23 24 import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE; 25 import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE; 26 27 import android.annotation.IntDef; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.pm.ActivityInfo; 33 import android.database.ContentObserver; 34 import android.graphics.Insets; 35 import android.graphics.PixelFormat; 36 import android.graphics.Rect; 37 import android.os.Bundle; 38 import android.os.UserHandle; 39 import android.provider.Settings; 40 import android.util.MathUtils; 41 import android.view.Gravity; 42 import android.view.MotionEvent; 43 import android.view.View; 44 import android.view.View.AccessibilityDelegate; 45 import android.view.ViewGroup; 46 import android.view.WindowInsets; 47 import android.view.WindowManager; 48 import android.view.WindowManager.LayoutParams; 49 import android.view.WindowMetrics; 50 import android.view.accessibility.AccessibilityManager; 51 import android.view.accessibility.AccessibilityNodeInfo; 52 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 53 import android.widget.Button; 54 import android.widget.ImageButton; 55 import android.widget.LinearLayout; 56 import android.widget.SeekBar; 57 import android.widget.Switch; 58 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.internal.graphics.SfVsyncFrameCallbackProvider; 61 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView; 62 import com.android.systemui.res.R; 63 import com.android.systemui.util.settings.SecureSettings; 64 65 import java.lang.annotation.Retention; 66 import java.lang.annotation.RetentionPolicy; 67 import java.util.Collections; 68 69 /** 70 * Class to set value about WindowManificationSettings. 71 */ 72 class WindowMagnificationSettings implements MagnificationGestureDetector.OnGestureListener { 73 private static final String TAG = "WindowMagnificationSettings"; 74 private final Context mContext; 75 private final AccessibilityManager mAccessibilityManager; 76 private final WindowManager mWindowManager; 77 private final SecureSettings mSecureSettings; 78 79 private final Runnable mWindowInsetChangeRunnable; 80 private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; 81 82 @VisibleForTesting 83 final LayoutParams mParams; 84 @VisibleForTesting 85 final Rect mDraggableWindowBounds = new Rect(); 86 private boolean mIsVisible = false; 87 private final MagnificationGestureDetector mGestureDetector; 88 private boolean mSingleTapDetected = false; 89 90 private SeekBarWithIconButtonsView mZoomSeekbar; 91 private LinearLayout mAllowDiagonalScrollingView; 92 private Switch mAllowDiagonalScrollingSwitch; 93 private LinearLayout mPanelView; 94 private LinearLayout mSettingView; 95 private ImageButton mSmallButton; 96 private ImageButton mMediumButton; 97 private ImageButton mLargeButton; 98 private Button mDoneButton; 99 private Button mEditButton; 100 private ImageButton mFullScreenButton; 101 private int mLastSelectedButtonIndex = MagnificationSize.DEFAULT; 102 103 private boolean mAllowDiagonalScrolling = false; 104 105 /** 106 * Amount by which magnification scale changes compared to seekbar in settings. 107 * magnitude = 10 means, for every 1 scale increase, 10 progress increase in seekbar. 108 */ 109 private int mSeekBarMagnitude; 110 private float mScale = SCALE_MIN_VALUE; 111 112 private WindowMagnificationSettingsCallback mCallback; 113 114 private ContentObserver mMagnificationCapabilityObserver; 115 116 @Retention(RetentionPolicy.SOURCE) 117 @IntDef({ 118 MagnificationSize.CUSTOM, 119 MagnificationSize.SMALL, 120 MagnificationSize.MEDIUM, 121 MagnificationSize.LARGE, 122 MagnificationSize.FULLSCREEN, 123 MagnificationSize.DEFAULT 124 }) 125 /** Denotes the Magnification size type. */ 126 public @interface MagnificationSize { 127 int CUSTOM = 0; 128 int SMALL = 1; 129 int MEDIUM = 2; 130 int LARGE = 3; 131 int FULLSCREEN = 4; 132 int DEFAULT = MEDIUM; 133 } 134 135 @VisibleForTesting WindowMagnificationSettings(Context context, WindowMagnificationSettingsCallback callback, SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SecureSettings secureSettings, WindowManager windowManager)136 WindowMagnificationSettings(Context context, WindowMagnificationSettingsCallback callback, 137 SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SecureSettings secureSettings, 138 WindowManager windowManager) { 139 mContext = context; 140 mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); 141 mWindowManager = windowManager; 142 mSfVsyncFrameProvider = sfVsyncFrameProvider; 143 mCallback = callback; 144 mSecureSettings = secureSettings; 145 146 mAllowDiagonalScrolling = mSecureSettings.getIntForUser( 147 Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 1, 148 UserHandle.USER_CURRENT) == 1; 149 150 mParams = createLayoutParams(context); 151 mWindowInsetChangeRunnable = this::onWindowInsetChanged; 152 153 inflateView(); 154 155 mGestureDetector = new MagnificationGestureDetector(context, 156 context.getMainThreadHandler(), this); 157 158 mMagnificationCapabilityObserver = new ContentObserver( 159 mContext.getMainThreadHandler()) { 160 @Override 161 public void onChange(boolean selfChange) { 162 mSettingView.post(() -> { 163 updateUIControlsIfNeeded(); 164 }); 165 } 166 }; 167 } 168 169 private class ZoomSeekbarChangeListener implements 170 SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener { 171 @Override onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)172 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 173 // Notify the service to update the magnifier scale only when the progress changed is 174 // triggered by user interaction on seekbar. 175 if (!fromUser) { 176 return; 177 } 178 final float scale = transformProgressToScale(progress); 179 // We don't need to update the persisted scale when the seekbar progress is 180 // changing. The update should be triggered when the changing is ended. 181 mCallback.onMagnifierScale(scale, /* updatePersistence= */ false); 182 } 183 184 @Override onStartTrackingTouch(SeekBar seekBar)185 public void onStartTrackingTouch(SeekBar seekBar) { 186 // Do nothing 187 } 188 189 @Override onStopTrackingTouch(SeekBar seekBar)190 public void onStopTrackingTouch(SeekBar seekBar) { 191 // Do nothing 192 } 193 194 @Override onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control)195 public void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control) { 196 // Update the Settings persisted scale only when user interaction with seekbar ends. 197 final int progress = seekBar.getProgress(); 198 final float scale = transformProgressToScale(progress); 199 mCallback.onMagnifierScale(scale, /* updatePersistence= */ true); 200 } 201 transformProgressToScale(float progress)202 private float transformProgressToScale(float progress) { 203 return (progress / (float) mSeekBarMagnitude) + SCALE_MIN_VALUE; 204 } 205 } 206 207 private final AccessibilityDelegate mPanelDelegate = new AccessibilityDelegate() { 208 @Override 209 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 210 super.onInitializeAccessibilityNodeInfo(host, info); 211 212 info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up, 213 mContext.getString(R.string.accessibility_control_move_up))); 214 info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down, 215 mContext.getString(R.string.accessibility_control_move_down))); 216 info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left, 217 mContext.getString(R.string.accessibility_control_move_left))); 218 info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right, 219 mContext.getString(R.string.accessibility_control_move_right))); 220 } 221 222 @Override 223 public boolean performAccessibilityAction(View host, int action, Bundle args) { 224 if (performA11yAction(host, action)) { 225 return true; 226 } 227 return super.performAccessibilityAction(host, action, args); 228 } 229 230 private boolean performA11yAction(View view, int action) { 231 final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); 232 if (action == R.id.accessibility_action_move_up) { 233 moveButton(0, -windowBounds.height()); 234 } else if (action == R.id.accessibility_action_move_down) { 235 moveButton(0, windowBounds.height()); 236 } else if (action == R.id.accessibility_action_move_left) { 237 moveButton(-windowBounds.width(), 0); 238 } else if (action == R.id.accessibility_action_move_right) { 239 moveButton(windowBounds.width(), 0); 240 } else { 241 return false; 242 } 243 return true; 244 } 245 }; 246 onTouch(View v, MotionEvent event)247 private boolean onTouch(View v, MotionEvent event) { 248 if (!mIsVisible) { 249 return false; 250 } 251 return mGestureDetector.onTouch(v, event); 252 } 253 254 private View.OnClickListener mButtonClickListener = new View.OnClickListener() { 255 @Override 256 public void onClick(View view) { 257 int id = view.getId(); 258 if (id == R.id.magnifier_small_button) { 259 setMagnifierSize(MagnificationSize.SMALL); 260 } else if (id == R.id.magnifier_medium_button) { 261 setMagnifierSize(MagnificationSize.MEDIUM); 262 } else if (id == R.id.magnifier_large_button) { 263 setMagnifierSize(MagnificationSize.LARGE); 264 } else if (id == R.id.magnifier_full_button) { 265 setMagnifierSize(MagnificationSize.FULLSCREEN); 266 } else if (id == R.id.magnifier_edit_button) { 267 editMagnifierSizeMode(true); 268 } else if (id == R.id.magnifier_done_button) { 269 hideSettingPanel(); 270 } 271 } 272 }; 273 274 @Override onSingleTap(View view)275 public boolean onSingleTap(View view) { 276 mSingleTapDetected = true; 277 return true; 278 } 279 280 @Override onDrag(View v, float offsetX, float offsetY)281 public boolean onDrag(View v, float offsetX, float offsetY) { 282 moveButton(offsetX, offsetY); 283 return true; 284 } 285 286 @Override onStart(float x, float y)287 public boolean onStart(float x, float y) { 288 return true; 289 } 290 291 @Override onFinish(float xOffset, float yOffset)292 public boolean onFinish(float xOffset, float yOffset) { 293 if (!mSingleTapDetected) { 294 showSettingPanel(); 295 } 296 mSingleTapDetected = false; 297 return true; 298 } 299 300 @VisibleForTesting getSettingView()301 public ViewGroup getSettingView() { 302 return mSettingView; 303 } 304 moveButton(float offsetX, float offsetY)305 private void moveButton(float offsetX, float offsetY) { 306 mSfVsyncFrameProvider.postFrameCallback(l -> { 307 mParams.x += offsetX; 308 mParams.y += offsetY; 309 updateButtonViewLayoutIfNeeded(); 310 }); 311 } 312 hideSettingPanel()313 public void hideSettingPanel() { 314 hideSettingPanel(true); 315 } 316 hideSettingPanel(boolean resetPosition)317 public void hideSettingPanel(boolean resetPosition) { 318 if (!mIsVisible) { 319 return; 320 } 321 322 // Unregister observer before removing view 323 mSecureSettings.unregisterContentObserverSync(mMagnificationCapabilityObserver); 324 mWindowManager.removeView(mSettingView); 325 mIsVisible = false; 326 if (resetPosition) { 327 mParams.x = 0; 328 mParams.y = 0; 329 } 330 331 mContext.unregisterReceiver(mScreenOffReceiver); 332 mCallback.onSettingsPanelVisibilityChanged(/* shown= */ false); 333 } 334 toggleSettingsPanelVisibility()335 public void toggleSettingsPanelVisibility() { 336 if (!mIsVisible) { 337 showSettingPanel(); 338 } else { 339 hideSettingPanel(); 340 } 341 } 342 showSettingPanel()343 public void showSettingPanel() { 344 showSettingPanel(true); 345 } 346 isSettingPanelShowing()347 public boolean isSettingPanelShowing() { 348 return mIsVisible; 349 } 350 setScaleSeekbar(float scale)351 public void setScaleSeekbar(float scale) { 352 int index = (int) ((scale - SCALE_MIN_VALUE) * mSeekBarMagnitude); 353 if (index < 0) { 354 index = 0; 355 } else if (index > mZoomSeekbar.getMax()) { 356 index = mZoomSeekbar.getMax(); 357 } 358 mZoomSeekbar.setProgress(index); 359 } 360 transitToMagnificationMode(int mode)361 private void transitToMagnificationMode(int mode) { 362 mCallback.onModeSwitch(mode); 363 } 364 365 /** 366 * Shows the panel for magnification settings. 367 * When the panel is going to be visible by calling this method, the layout position can be 368 * reset depending on the flag. 369 * 370 * @param resetPosition if the panel position needs to be reset 371 */ showSettingPanel(boolean resetPosition)372 private void showSettingPanel(boolean resetPosition) { 373 if (!mIsVisible) { 374 updateUIControlsIfNeeded(); 375 setScaleSeekbar(getMagnificationScale()); 376 if (resetPosition) { 377 mDraggableWindowBounds.set(getDraggableWindowBounds()); 378 mParams.x = mDraggableWindowBounds.right; 379 mParams.y = mDraggableWindowBounds.bottom; 380 } 381 382 mWindowManager.addView(mSettingView, mParams); 383 384 mSecureSettings.registerContentObserverForUserSync( 385 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY, 386 mMagnificationCapabilityObserver, 387 UserHandle.USER_CURRENT); 388 389 // Exclude magnification switch button from system gesture area. 390 setSystemGestureExclusion(); 391 mIsVisible = true; 392 mCallback.onSettingsPanelVisibilityChanged(/* shown= */ true); 393 } 394 mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); 395 } 396 getMagnificationMode()397 private int getMagnificationMode() { 398 // If current capability is window mode, we would like the default value of the mode to 399 // be WINDOW, otherwise, the default value would be FULLSCREEN. 400 int defaultValue = 401 (getMagnificationCapability() == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) 402 ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW 403 : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; 404 405 return mSecureSettings.getIntForUser( 406 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 407 defaultValue, 408 UserHandle.USER_CURRENT); 409 } 410 getMagnificationCapability()411 private int getMagnificationCapability() { 412 return mSecureSettings.getIntForUser( 413 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY, 414 ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, 415 UserHandle.USER_CURRENT); 416 } 417 418 @VisibleForTesting isDiagonalScrollingEnabled()419 boolean isDiagonalScrollingEnabled() { 420 return mAllowDiagonalScrolling; 421 } 422 423 /** 424 * Only called from outside to notify the controlling magnifier scale changed 425 * 426 * @param scale The new controlling magnifier scale 427 */ setMagnificationScale(float scale)428 public void setMagnificationScale(float scale) { 429 mScale = scale; 430 431 if (isSettingPanelShowing()) { 432 setScaleSeekbar(scale); 433 } 434 } 435 getMagnificationScale()436 private float getMagnificationScale() { 437 return mScale; 438 } 439 updateUIControlsIfNeeded()440 private void updateUIControlsIfNeeded() { 441 int capability = getMagnificationCapability(); 442 int selectedButtonIndex = mLastSelectedButtonIndex; 443 WindowMagnificationFrameSizePrefs windowMagnificationFrameSizePrefs = 444 new WindowMagnificationFrameSizePrefs(mContext); 445 switch (capability) { 446 case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW: 447 mEditButton.setVisibility(View.VISIBLE); 448 mAllowDiagonalScrollingView.setVisibility(View.VISIBLE); 449 mFullScreenButton.setVisibility(View.GONE); 450 if (selectedButtonIndex == MagnificationSize.FULLSCREEN) { 451 selectedButtonIndex = 452 windowMagnificationFrameSizePrefs.getIndexForCurrentDensity(); 453 } 454 break; 455 456 case ACCESSIBILITY_MAGNIFICATION_MODE_ALL: 457 int mode = getMagnificationMode(); 458 mFullScreenButton.setVisibility(View.VISIBLE); 459 if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { 460 // set the edit button visibility to View.INVISIBLE to keep the height, to 461 // prevent the size title from too close to the size buttons 462 mEditButton.setVisibility(View.INVISIBLE); 463 mAllowDiagonalScrollingView.setVisibility(View.GONE); 464 // force the fullscreen button showing 465 selectedButtonIndex = MagnificationSize.FULLSCREEN; 466 } else { // mode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW 467 mEditButton.setVisibility(View.VISIBLE); 468 mAllowDiagonalScrollingView.setVisibility(View.VISIBLE); 469 selectedButtonIndex = 470 windowMagnificationFrameSizePrefs.getIndexForCurrentDensity(); 471 } 472 break; 473 474 case ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN: 475 // We will never fall into this case since we never show settings panel when 476 // capability equals to ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN. 477 // Currently, the case follows the UI controls when capability equals to 478 // ACCESSIBILITY_MAGNIFICATION_MODE_ALL and mode equals to 479 // ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, but we could also consider to 480 // remove the whole icon button selections int the future since they are no use 481 // for fullscreen only capability. 482 483 mFullScreenButton.setVisibility(View.VISIBLE); 484 // set the edit button visibility to View.INVISIBLE to keep the height, to 485 // prevent the size title from too close to the size buttons 486 mEditButton.setVisibility(View.INVISIBLE); 487 mAllowDiagonalScrollingView.setVisibility(View.GONE); 488 // force the fullscreen button showing 489 selectedButtonIndex = MagnificationSize.FULLSCREEN; 490 break; 491 492 default: 493 break; 494 } 495 496 updateSelectedButton(selectedButtonIndex); 497 mSettingView.requestLayout(); 498 } 499 500 private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 501 @Override 502 public void onReceive(Context context, Intent intent) { 503 hideSettingPanel(); 504 } 505 }; 506 inflateView()507 void inflateView() { 508 mSettingView = (LinearLayout) View.inflate(mContext, 509 R.layout.window_magnification_settings_view, null); 510 511 mSettingView.setFocusable(true); 512 mSettingView.setFocusableInTouchMode(true); 513 mSettingView.setOnTouchListener(this::onTouch); 514 515 mSettingView.setAccessibilityDelegate(mPanelDelegate); 516 517 mPanelView = mSettingView.findViewById(R.id.magnifier_panel_view); 518 mSmallButton = mSettingView.findViewById(R.id.magnifier_small_button); 519 mMediumButton = mSettingView.findViewById(R.id.magnifier_medium_button); 520 mLargeButton = mSettingView.findViewById(R.id.magnifier_large_button); 521 mDoneButton = mSettingView.findViewById(R.id.magnifier_done_button); 522 mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button); 523 mFullScreenButton = mSettingView.findViewById(R.id.magnifier_full_button); 524 525 mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider); 526 mZoomSeekbar.setMax((int) (mZoomSeekbar.getChangeMagnitude() 527 * (SCALE_MAX_VALUE - SCALE_MIN_VALUE))); 528 mSeekBarMagnitude = mZoomSeekbar.getChangeMagnitude(); 529 setScaleSeekbar(mScale); 530 mZoomSeekbar.setOnSeekBarWithIconButtonsChangeListener(new ZoomSeekbarChangeListener()); 531 532 mAllowDiagonalScrollingView = 533 (LinearLayout) mSettingView.findViewById(R.id.magnifier_horizontal_lock_view); 534 mAllowDiagonalScrollingSwitch = 535 (Switch) mSettingView.findViewById(R.id.magnifier_horizontal_lock_switch); 536 mAllowDiagonalScrollingSwitch.setChecked(mAllowDiagonalScrolling); 537 mAllowDiagonalScrollingSwitch.setOnCheckedChangeListener((view, checked) -> { 538 toggleDiagonalScrolling(); 539 }); 540 541 mSmallButton.setOnClickListener(mButtonClickListener); 542 mMediumButton.setOnClickListener(mButtonClickListener); 543 mLargeButton.setOnClickListener(mButtonClickListener); 544 mDoneButton.setOnClickListener(mButtonClickListener); 545 mFullScreenButton.setOnClickListener(mButtonClickListener); 546 mEditButton.setOnClickListener(mButtonClickListener); 547 548 mSettingView.setOnApplyWindowInsetsListener((v, insets) -> { 549 // Adds a pending post check to avoiding redundant calculation because this callback 550 // is sent frequently when the switch icon window dragged by the users. 551 if (mSettingView.isAttachedToWindow() 552 && !mSettingView.getHandler().hasCallbacks(mWindowInsetChangeRunnable)) { 553 mSettingView.getHandler().post(mWindowInsetChangeRunnable); 554 } 555 return v.onApplyWindowInsets(insets); 556 }); 557 558 updateSelectedButton(mLastSelectedButtonIndex); 559 } 560 onConfigurationChanged(int configDiff)561 void onConfigurationChanged(int configDiff) { 562 if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0 563 || (configDiff & ActivityInfo.CONFIG_ASSETS_PATHS) != 0 564 || (configDiff & ActivityInfo.CONFIG_FONT_SCALE) != 0 565 || (configDiff & ActivityInfo.CONFIG_LOCALE) != 0 566 || (configDiff & ActivityInfo.CONFIG_DENSITY) != 0 567 || (configDiff & ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT) != 0) { 568 // We listen to following config changes to trigger layout inflation: 569 // CONFIG_UI_MODE: theme change 570 // CONFIG_ASSETS_PATHS: wallpaper change 571 // CONFIG_FONT_SCALE: font size change 572 // CONFIG_LOCALE: language change 573 // CONFIG_DENSITY: display size change 574 // CONFIG_FONT_WEIGHT_ADJUSTMENT: bold text setting change 575 mParams.width = getPanelWidth(mContext); 576 mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext); 577 578 boolean showSettingPanelAfterConfigChange = mIsVisible; 579 hideSettingPanel(/* resetPosition= */ false); 580 inflateView(); 581 if (showSettingPanelAfterConfigChange) { 582 showSettingPanel(/* resetPosition= */ false); 583 } 584 return; 585 } 586 587 if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0 588 || (configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) { 589 mDraggableWindowBounds.set(getDraggableWindowBounds()); 590 // reset the panel position to the right-bottom corner 591 mParams.x = mDraggableWindowBounds.right; 592 mParams.y = mDraggableWindowBounds.bottom; 593 updateButtonViewLayoutIfNeeded(); 594 } 595 } 596 onWindowInsetChanged()597 private void onWindowInsetChanged() { 598 final Rect newBounds = getDraggableWindowBounds(); 599 if (mDraggableWindowBounds.equals(newBounds)) { 600 return; 601 } 602 mDraggableWindowBounds.set(newBounds); 603 } 604 605 @VisibleForTesting updateButtonViewLayoutIfNeeded()606 void updateButtonViewLayoutIfNeeded() { 607 if (mIsVisible) { 608 mParams.x = MathUtils.constrain(mParams.x, mDraggableWindowBounds.left, 609 mDraggableWindowBounds.right); 610 mParams.y = MathUtils.constrain(mParams.y, mDraggableWindowBounds.top, 611 mDraggableWindowBounds.bottom); 612 mWindowManager.updateViewLayout(mSettingView, mParams); 613 } 614 } 615 editMagnifierSizeMode(boolean enable)616 public void editMagnifierSizeMode(boolean enable) { 617 setEditMagnifierSizeMode(enable); 618 updateSelectedButton(MagnificationSize.CUSTOM); 619 hideSettingPanel(); 620 } 621 setMagnifierSize(@agnificationSize int index)622 private void setMagnifierSize(@MagnificationSize int index) { 623 if (index == MagnificationSize.FULLSCREEN) { 624 // transit to fullscreen magnifier if needed 625 transitToMagnificationMode(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); 626 } else if (index != MagnificationSize.CUSTOM) { 627 // update the window magnifier size 628 mCallback.onSetMagnifierSize(index); 629 // transit to window magnifier if needed 630 transitToMagnificationMode(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); 631 } else { 632 return; 633 } 634 635 updateSelectedButton(index); 636 } 637 toggleDiagonalScrolling()638 private void toggleDiagonalScrolling() { 639 boolean enabled = mSecureSettings.getIntForUser( 640 Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 1, 641 UserHandle.USER_CURRENT) == 1; 642 setDiagonalScrolling(!enabled); 643 } 644 645 @VisibleForTesting setDiagonalScrolling(boolean enabled)646 void setDiagonalScrolling(boolean enabled) { 647 mSecureSettings.putIntForUser( 648 Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, enabled ? 1 : 0, 649 UserHandle.USER_CURRENT); 650 651 mCallback.onSetDiagonalScrolling(enabled); 652 } 653 setEditMagnifierSizeMode(boolean enable)654 private void setEditMagnifierSizeMode(boolean enable) { 655 mCallback.onEditMagnifierSizeMode(enable); 656 } 657 getPanelWidth(Context context)658 private int getPanelWidth(Context context) { 659 // The magnification settings panel width is limited to the minimum of 660 // 1. display width 661 // 2. panel done button width + left and right padding 662 // So we can directly calculate the proper panel width at runtime 663 int displayWidth = mWindowManager.getCurrentWindowMetrics().getBounds().width(); 664 int contentWidth = context.getResources() 665 .getDimensionPixelSize(R.dimen.magnification_setting_button_done_width); 666 int padding = context.getResources() 667 .getDimensionPixelSize(R.dimen.magnification_setting_background_padding); 668 return Math.min(displayWidth, contentWidth + 2 * padding); 669 } 670 createLayoutParams(Context context)671 private LayoutParams createLayoutParams(Context context) { 672 final LayoutParams params = new LayoutParams( 673 getPanelWidth(context), 674 LayoutParams.WRAP_CONTENT, 675 LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 676 LayoutParams.FLAG_NOT_FOCUSABLE, 677 PixelFormat.TRANSPARENT); 678 params.gravity = Gravity.TOP | Gravity.START; 679 params.accessibilityTitle = getAccessibilityWindowTitle(context); 680 params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 681 return params; 682 } 683 getDraggableWindowBounds()684 private Rect getDraggableWindowBounds() { 685 final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); 686 final Insets windowInsets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility( 687 WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()); 688 // re-measure the settings panel view so that we can get the correct view size to inset 689 int unspecificSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 690 mSettingView.measure(unspecificSpec, unspecificSpec); 691 692 final Rect boundRect = new Rect(windowMetrics.getBounds()); 693 boundRect.offsetTo(0, 0); 694 boundRect.inset(0, 0, mSettingView.getMeasuredWidth(), mSettingView.getMeasuredHeight()); 695 boundRect.inset(windowInsets); 696 return boundRect; 697 } 698 getAccessibilityWindowTitle(Context context)699 private static String getAccessibilityWindowTitle(Context context) { 700 return context.getString(com.android.internal.R.string.android_system_label); 701 } 702 setSystemGestureExclusion()703 private void setSystemGestureExclusion() { 704 mSettingView.post(() -> { 705 mSettingView.setSystemGestureExclusionRects( 706 Collections.singletonList( 707 new Rect(0, 0, mSettingView.getWidth(), mSettingView.getHeight()))); 708 }); 709 } 710 updateSelectedButton(@agnificationSize int index)711 void updateSelectedButton(@MagnificationSize int index) { 712 // Clear the state of last selected button 713 if (mLastSelectedButtonIndex == MagnificationSize.SMALL) { 714 mSmallButton.setSelected(false); 715 } else if (mLastSelectedButtonIndex == MagnificationSize.MEDIUM) { 716 mMediumButton.setSelected(false); 717 } else if (mLastSelectedButtonIndex == MagnificationSize.LARGE) { 718 mLargeButton.setSelected(false); 719 } else if (mLastSelectedButtonIndex == MagnificationSize.FULLSCREEN) { 720 mFullScreenButton.setSelected(false); 721 } 722 723 // Set the state for selected button 724 if (index == MagnificationSize.SMALL) { 725 mSmallButton.setSelected(true); 726 } else if (index == MagnificationSize.MEDIUM) { 727 mMediumButton.setSelected(true); 728 } else if (index == MagnificationSize.LARGE) { 729 mLargeButton.setSelected(true); 730 } else if (index == MagnificationSize.FULLSCREEN) { 731 mFullScreenButton.setSelected(true); 732 } 733 734 mLastSelectedButtonIndex = index; 735 } 736 } 737