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