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