• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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