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