1 /*
2  * Copyright (C) 2020 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.wm.shell.onehanded;
18 
19 import static android.os.UserHandle.USER_CURRENT;
20 import static android.os.UserHandle.myUserId;
21 import static android.view.Display.DEFAULT_DISPLAY;
22 
23 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
24 import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
25 import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
26 import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
27 import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
28 
29 import android.annotation.BinderThread;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.om.IOverlayManager;
33 import android.content.om.OverlayInfo;
34 import android.content.res.Configuration;
35 import android.database.ContentObserver;
36 import android.graphics.Rect;
37 import android.os.Handler;
38 import android.os.RemoteException;
39 import android.os.ServiceManager;
40 import android.os.SystemProperties;
41 import android.provider.Settings;
42 import android.util.Slog;
43 import android.view.Surface;
44 import android.view.WindowManager;
45 import android.view.accessibility.AccessibilityManager;
46 import android.window.WindowContainerTransaction;
47 
48 import androidx.annotation.NonNull;
49 import androidx.annotation.VisibleForTesting;
50 
51 import com.android.internal.logging.UiEventLogger;
52 import com.android.wm.shell.R;
53 import com.android.wm.shell.common.DisplayChangeController;
54 import com.android.wm.shell.common.DisplayController;
55 import com.android.wm.shell.common.DisplayLayout;
56 import com.android.wm.shell.common.RemoteCallable;
57 import com.android.wm.shell.common.ShellExecutor;
58 import com.android.wm.shell.common.TaskStackListenerCallback;
59 import com.android.wm.shell.common.TaskStackListenerImpl;
60 import com.android.wm.shell.common.annotations.ExternalThread;
61 
62 import java.io.PrintWriter;
63 
64 /**
65  * Manages and manipulates the one handed states, transitions, and gesture for phones.
66  */
67 public class OneHandedController implements RemoteCallable<OneHandedController>,
68         DisplayChangeController.OnDisplayChangingListener {
69     private static final String TAG = "OneHandedController";
70 
71     private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
72             "persist.debug.one_handed_offset_percentage";
73     private static final String ONE_HANDED_MODE_GESTURAL_OVERLAY =
74             "com.android.internal.systemui.onehanded.gestural";
75     private static final int OVERLAY_ENABLED_DELAY_MS = 250;
76     private static final int DISPLAY_AREA_READY_RETRY_MS = 10;
77 
78     public static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
79 
80     private volatile boolean mIsOneHandedEnabled;
81     private volatile boolean mIsSwipeToNotificationEnabled;
82     private boolean mIsShortcutEnabled;
83     private boolean mTaskChangeToExit;
84     private boolean mLockedDisabled;
85     private boolean mKeyguardShowing;
86     private int mUserId;
87     private float mOffSetFraction;
88 
89     private Context mContext;
90 
91     private final AccessibilityManager mAccessibilityManager;
92     private final DisplayController mDisplayController;
93     private final OneHandedSettingsUtil mOneHandedSettingsUtil;
94     private final OneHandedAccessibilityUtil mOneHandedAccessibilityUtil;
95     private final OneHandedTimeoutHandler mTimeoutHandler;
96     private final OneHandedTouchHandler mTouchHandler;
97     private final OneHandedState mState;
98     private final OneHandedTutorialHandler mTutorialHandler;
99     private final TaskStackListenerImpl mTaskStackListener;
100     private final IOverlayManager mOverlayManager;
101     private final ShellExecutor mMainExecutor;
102     private final Handler mMainHandler;
103     private final OneHandedImpl mImpl = new OneHandedImpl();
104 
105     private OneHandedEventCallback mEventCallback;
106     private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
107     private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
108     private OneHandedUiEventLogger mOneHandedUiEventLogger;
109 
110     private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
111             new DisplayController.OnDisplaysChangedListener() {
112                 @Override
113                 public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
114                     if (displayId != DEFAULT_DISPLAY || !isInitialized()) {
115                         return;
116                     }
117                     updateDisplayLayout(displayId);
118                 }
119 
120                 @Override
121                 public void onDisplayAdded(int displayId) {
122                     if (displayId != DEFAULT_DISPLAY || !isInitialized()) {
123                         return;
124                     }
125                     updateDisplayLayout(displayId);
126                 }
127             };
128 
129     private final ContentObserver mActivatedObserver;
130     private final ContentObserver mEnabledObserver;
131     private final ContentObserver mSwipeToNotificationEnabledObserver;
132     private final ContentObserver mShortcutEnabledObserver;
133 
134     private AccessibilityManager.AccessibilityStateChangeListener
135             mAccessibilityStateChangeListener =
136             new AccessibilityManager.AccessibilityStateChangeListener() {
137                 @Override
138                 public void onAccessibilityStateChanged(boolean enabled) {
139                     if (!isInitialized()) {
140                         return;
141                     }
142                     if (enabled) {
143                         final int mOneHandedTimeout = mOneHandedSettingsUtil
144                                 .getSettingsOneHandedModeTimeout(
145                                         mContext.getContentResolver(), mUserId);
146                         final int timeout = mAccessibilityManager
147                                 .getRecommendedTimeoutMillis(mOneHandedTimeout * 1000
148                                         /* align with A11y timeout millis */,
149                                         AccessibilityManager.FLAG_CONTENT_CONTROLS);
150                         mTimeoutHandler.setTimeout(timeout / 1000);
151                     } else {
152                         mTimeoutHandler.setTimeout(mOneHandedSettingsUtil
153                                 .getSettingsOneHandedModeTimeout(
154                                         mContext.getContentResolver(), mUserId));
155                     }
156                 }
157             };
158 
159     private final OneHandedTransitionCallback mTransitionCallBack =
160             new OneHandedTransitionCallback() {
161                 @Override
162                 public void onStartFinished(Rect bounds) {
163                     mState.setState(STATE_ACTIVE);
164                     notifyShortcutStateChanged(STATE_ACTIVE);
165                 }
166 
167                 @Override
168                 public void onStopFinished(Rect bounds) {
169                     mState.setState(STATE_NONE);
170                     notifyShortcutStateChanged(STATE_NONE);
171                     mBackgroundPanelOrganizer.onStopFinished();
172                 }
173             };
174 
175     private final TaskStackListenerCallback mTaskStackListenerCallback =
176             new TaskStackListenerCallback() {
177                 @Override
178                 public void onTaskCreated(int taskId, ComponentName componentName) {
179                     stopOneHanded(OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_APP_TAPS_OUT);
180                 }
181 
182                 @Override
183                 public void onTaskMovedToFront(int taskId) {
184                     stopOneHanded(OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_APP_TAPS_OUT);
185                 }
186             };
187 
isInitialized()188     private boolean isInitialized() {
189         if (mDisplayAreaOrganizer == null || mDisplayController == null
190                 || mOneHandedSettingsUtil == null) {
191             Slog.w(TAG, "Components may not initialized yet!");
192             return false;
193         }
194         return true;
195     }
196 
197     /**
198      * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
199      */
create( Context context, WindowManager windowManager, DisplayController displayController, DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger, ShellExecutor mainExecutor, Handler mainHandler)200     public static OneHandedController create(
201             Context context, WindowManager windowManager, DisplayController displayController,
202             DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
203             UiEventLogger uiEventLogger, ShellExecutor mainExecutor, Handler mainHandler) {
204         OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
205         OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
206         OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
207         OneHandedState transitionState = new OneHandedState();
208         OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
209                 settingsUtil, windowManager);
210         OneHandedAnimationController animationController =
211                 new OneHandedAnimationController(context);
212         OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler,
213                 mainExecutor);
214         OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer =
215                 new OneHandedBackgroundPanelOrganizer(context, displayLayout, settingsUtil,
216                         mainExecutor);
217         OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
218                 context, displayLayout, settingsUtil, animationController, tutorialHandler,
219                 oneHandedBackgroundPanelOrganizer, mainExecutor);
220         OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
221         IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
222                 ServiceManager.getService(Context.OVERLAY_SERVICE));
223         return new OneHandedController(context, displayController,
224                 oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
225                 settingsUtil, accessibilityUtil, timeoutHandler, transitionState,
226                 oneHandedUiEventsLogger, overlayManager, taskStackListener, mainExecutor,
227                 mainHandler);
228     }
229 
230     @VisibleForTesting
OneHandedController(Context context, DisplayController displayController, OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer, OneHandedDisplayAreaOrganizer displayAreaOrganizer, OneHandedTouchHandler touchHandler, OneHandedTutorialHandler tutorialHandler, OneHandedSettingsUtil settingsUtil, OneHandedAccessibilityUtil oneHandedAccessibilityUtil, OneHandedTimeoutHandler timeoutHandler, OneHandedState state, OneHandedUiEventLogger uiEventsLogger, IOverlayManager overlayManager, TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor, Handler mainHandler)231     OneHandedController(Context context,
232             DisplayController displayController,
233             OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer,
234             OneHandedDisplayAreaOrganizer displayAreaOrganizer,
235             OneHandedTouchHandler touchHandler,
236             OneHandedTutorialHandler tutorialHandler,
237             OneHandedSettingsUtil settingsUtil,
238             OneHandedAccessibilityUtil oneHandedAccessibilityUtil,
239             OneHandedTimeoutHandler timeoutHandler,
240             OneHandedState state,
241             OneHandedUiEventLogger uiEventsLogger,
242             IOverlayManager overlayManager,
243             TaskStackListenerImpl taskStackListener,
244             ShellExecutor mainExecutor,
245             Handler mainHandler) {
246         mContext = context;
247         mOneHandedSettingsUtil = settingsUtil;
248         mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil;
249         mBackgroundPanelOrganizer = backgroundPanelOrganizer;
250         mDisplayAreaOrganizer = displayAreaOrganizer;
251         mDisplayController = displayController;
252         mTouchHandler = touchHandler;
253         mState = state;
254         mTutorialHandler = tutorialHandler;
255         mOverlayManager = overlayManager;
256         mMainExecutor = mainExecutor;
257         mMainHandler = mainHandler;
258         mOneHandedUiEventLogger = uiEventsLogger;
259         mTaskStackListener = taskStackListener;
260 
261         mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
262         final float offsetPercentageConfig = context.getResources().getFraction(
263                 R.fraction.config_one_handed_offset, 1, 1);
264         final int sysPropPercentageConfig = SystemProperties.getInt(
265                 ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f));
266         mUserId = myUserId();
267         mOffSetFraction = sysPropPercentageConfig / 100.0f;
268         mIsOneHandedEnabled = mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
269                 context.getContentResolver(), mUserId);
270         mIsSwipeToNotificationEnabled =
271                 mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
272                         context.getContentResolver(), mUserId);
273         mTimeoutHandler = timeoutHandler;
274 
275         mActivatedObserver = getObserver(this::onActivatedActionChanged);
276         mEnabledObserver = getObserver(this::onEnabledSettingChanged);
277         mSwipeToNotificationEnabledObserver =
278                 getObserver(this::onSwipeToNotificationEnabledChanged);
279         mShortcutEnabledObserver = getObserver(this::onShortcutEnabledChanged);
280 
281         mDisplayController.addDisplayChangingController(this);
282         setupCallback();
283         registerSettingObservers(mUserId);
284         setupTimeoutListener();
285         setupGesturalOverlay();
286         updateSettings();
287 
288         mAccessibilityManager = AccessibilityManager.getInstance(context);
289         mAccessibilityManager.addAccessibilityStateChangeListener(
290                 mAccessibilityStateChangeListener);
291 
292         mState.addSListeners(mBackgroundPanelOrganizer);
293         mState.addSListeners(mTutorialHandler);
294     }
295 
asOneHanded()296     public OneHanded asOneHanded() {
297         return mImpl;
298     }
299 
300     @Override
getContext()301     public Context getContext() {
302         return mContext;
303     }
304 
305     @Override
getRemoteCallExecutor()306     public ShellExecutor getRemoteCallExecutor() {
307         return mMainExecutor;
308     }
309 
310     /**
311      * Set one handed enabled or disabled when user update settings
312      */
setOneHandedEnabled(boolean enabled)313     void setOneHandedEnabled(boolean enabled) {
314         mIsOneHandedEnabled = enabled;
315         updateOneHandedEnabled();
316     }
317 
318     /**
319      * Set one handed enabled or disabled by when user update settings
320      */
setTaskChangeToExit(boolean enabled)321     void setTaskChangeToExit(boolean enabled) {
322         if (enabled) {
323             mTaskStackListener.addListener(mTaskStackListenerCallback);
324         } else {
325             mTaskStackListener.removeListener(mTaskStackListenerCallback);
326         }
327         mTaskChangeToExit = enabled;
328     }
329 
330     /**
331      * Sets whether to enable swipe bottom to notification gesture when user update settings.
332      */
setSwipeToNotificationEnabled(boolean enabled)333     void setSwipeToNotificationEnabled(boolean enabled) {
334         mIsSwipeToNotificationEnabled = enabled;
335     }
336 
337     @VisibleForTesting
notifyShortcutStateChanged(@neHandedState.State int state)338     void notifyShortcutStateChanged(@OneHandedState.State int state) {
339         if (!isShortcutEnabled()) {
340             return;
341         }
342         mOneHandedSettingsUtil.setOneHandedModeActivated(
343                 mContext.getContentResolver(), state == STATE_ACTIVE ? 1 : 0, mUserId);
344     }
345 
346     @VisibleForTesting
startOneHanded()347     void startOneHanded() {
348         if (isLockedDisabled() || mKeyguardShowing) {
349             Slog.d(TAG, "Temporary lock disabled");
350             return;
351         }
352 
353         if (!mDisplayAreaOrganizer.isReady()) {
354             // Must wait until DisplayAreaOrganizer is ready for transitioning.
355             mMainExecutor.executeDelayed(this::startOneHanded, DISPLAY_AREA_READY_RETRY_MS);
356             return;
357         }
358 
359         if (mState.isTransitioning() || mState.isInOneHanded()) {
360             return;
361         }
362 
363         final int currentRotation = mDisplayAreaOrganizer.getDisplayLayout().rotation();
364         if (currentRotation != Surface.ROTATION_0 && currentRotation != Surface.ROTATION_180) {
365             Slog.w(TAG, "One handed mode only support portrait mode");
366             return;
367         }
368 
369         mState.setState(STATE_ENTERING);
370         final int yOffSet = Math.round(
371                 mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction);
372         mOneHandedAccessibilityUtil.announcementForScreenReader(
373                 mOneHandedAccessibilityUtil.getOneHandedStartDescription());
374         mBackgroundPanelOrganizer.onStart();
375         mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
376         mTimeoutHandler.resetTimer();
377         mOneHandedUiEventLogger.writeEvent(
378                 OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_IN);
379     }
380 
381     @VisibleForTesting
stopOneHanded()382     void stopOneHanded() {
383         stopOneHanded(OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
384     }
385 
stopOneHanded(int uiEvent)386     private void stopOneHanded(int uiEvent) {
387         if (mState.isTransitioning() || mState.getState() == STATE_NONE) {
388             return;
389         }
390         mState.setState(STATE_EXITING);
391         mOneHandedAccessibilityUtil.announcementForScreenReader(
392                 mOneHandedAccessibilityUtil.getOneHandedStopDescription());
393         mDisplayAreaOrganizer.scheduleOffset(0, 0);
394         mTimeoutHandler.removeTimer();
395         mOneHandedUiEventLogger.writeEvent(uiEvent);
396     }
397 
registerEventCallback(OneHandedEventCallback callback)398     void registerEventCallback(OneHandedEventCallback callback) {
399         mEventCallback = callback;
400     }
401 
402     @VisibleForTesting
registerTransitionCallback(OneHandedTransitionCallback callback)403     void registerTransitionCallback(OneHandedTransitionCallback callback) {
404         mDisplayAreaOrganizer.registerTransitionCallback(callback);
405     }
406 
setupCallback()407     private void setupCallback() {
408         mTouchHandler.registerTouchEventListener(() ->
409                 stopOneHanded(OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_OVERSPACE_OUT));
410         mDisplayAreaOrganizer.registerTransitionCallback(mTouchHandler);
411         mDisplayAreaOrganizer.registerTransitionCallback(mTutorialHandler);
412         mDisplayAreaOrganizer.registerTransitionCallback(mTransitionCallBack);
413         if (mTaskChangeToExit) {
414             mTaskStackListener.addListener(mTaskStackListenerCallback);
415         }
416     }
417 
registerSettingObservers(int newUserId)418     private void registerSettingObservers(int newUserId) {
419         mOneHandedSettingsUtil.registerSettingsKeyObserver(
420                 Settings.Secure.ONE_HANDED_MODE_ACTIVATED,
421                 mContext.getContentResolver(), mActivatedObserver, newUserId);
422         mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED,
423                 mContext.getContentResolver(), mEnabledObserver, newUserId);
424         mOneHandedSettingsUtil.registerSettingsKeyObserver(
425                 Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
426                 mContext.getContentResolver(), mSwipeToNotificationEnabledObserver, newUserId);
427         mOneHandedSettingsUtil.registerSettingsKeyObserver(
428                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
429                 mContext.getContentResolver(), mShortcutEnabledObserver, newUserId);
430         mOneHandedSettingsUtil.registerSettingsKeyObserver(
431                 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
432                 mContext.getContentResolver(), mShortcutEnabledObserver, newUserId);
433     }
434 
unregisterSettingObservers()435     private void unregisterSettingObservers() {
436         mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
437                 mEnabledObserver);
438         mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
439                 mSwipeToNotificationEnabledObserver);
440         mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
441                 mShortcutEnabledObserver);
442     }
443 
updateSettings()444     private void updateSettings() {
445         setOneHandedEnabled(mOneHandedSettingsUtil
446                 .getSettingsOneHandedModeEnabled(mContext.getContentResolver(), mUserId));
447         mTimeoutHandler.setTimeout(mOneHandedSettingsUtil
448                 .getSettingsOneHandedModeTimeout(mContext.getContentResolver(), mUserId));
449         setTaskChangeToExit(mOneHandedSettingsUtil
450                 .getSettingsTapsAppToExit(mContext.getContentResolver(), mUserId));
451         setSwipeToNotificationEnabled(mOneHandedSettingsUtil
452                 .getSettingsSwipeToNotificationEnabled(mContext.getContentResolver(), mUserId));
453         onShortcutEnabledChanged();
454     }
455 
updateDisplayLayout(int displayId)456     private void updateDisplayLayout(int displayId) {
457         final DisplayLayout newDisplayLayout = mDisplayController.getDisplayLayout(displayId);
458         mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
459         mTutorialHandler.onDisplayChanged(newDisplayLayout);
460         mBackgroundPanelOrganizer.onDisplayChanged(newDisplayLayout);
461     }
462 
getObserver(Runnable onChangeRunnable)463     private ContentObserver getObserver(Runnable onChangeRunnable) {
464         return new ContentObserver(mMainHandler) {
465             @Override
466             public void onChange(boolean selfChange) {
467                 onChangeRunnable.run();
468             }
469         };
470     }
471 
472     @VisibleForTesting
473     void notifyExpandNotification() {
474         if (mEventCallback != null) {
475             mMainExecutor.execute(() -> mEventCallback.notifyExpandNotification());
476         }
477     }
478 
479     @VisibleForTesting
480     void onActivatedActionChanged() {
481         if (!isShortcutEnabled()) {
482             Slog.w(TAG, "Shortcut not enabled, skip onActivatedActionChanged()");
483             return;
484         }
485 
486         if (!isOneHandedEnabled()) {
487             final boolean success = mOneHandedSettingsUtil.setOneHandedModeEnabled(
488                     mContext.getContentResolver(), 1 /* Enabled for shortcut */, mUserId);
489             Slog.d(TAG, "Auto enabled One-handed mode by shortcut trigger, success=" + success);
490         }
491 
492         if (isSwipeToNotificationEnabled()) {
493             notifyExpandNotification();
494             return;
495         }
496 
497         final boolean isActivated = mState.getState() == STATE_ACTIVE;
498         final boolean requestActivated = mOneHandedSettingsUtil.getOneHandedModeActivated(
499                 mContext.getContentResolver(), mUserId);
500         // When gesture trigger action, we will update settings and introduce observer callback
501         // again, then the following logic will just ignore the second redundant callback.
502         if (isActivated ^ requestActivated) {
503             if (requestActivated) {
504                 startOneHanded();
505             } else {
506                 stopOneHanded();
507             }
508         }
509     }
510 
511     @VisibleForTesting
512     void onEnabledSettingChanged() {
513         final boolean enabled = mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
514                 mContext.getContentResolver(), mUserId);
515         mOneHandedUiEventLogger.writeEvent(enabled
516                 ? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_ENABLED_ON
517                 : OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_ENABLED_OFF);
518 
519         setOneHandedEnabled(enabled);
520 
521         // Also checks swipe to notification settings since they all need gesture overlay.
522         setEnabledGesturalOverlay(
523                 enabled || mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
524                         mContext.getContentResolver(), mUserId), true /* DelayExecute */);
525     }
526 
527     @VisibleForTesting
528     void onSwipeToNotificationEnabledChanged() {
529         final boolean enabled =
530                 mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
531                         mContext.getContentResolver(), mUserId);
532         setSwipeToNotificationEnabled(enabled);
533         notifyShortcutStateChanged(mState.getState());
534 
535         mOneHandedUiEventLogger.writeEvent(enabled
536                 ? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_SHOW_NOTIFICATION_ENABLED_ON
537                 : OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_SHOW_NOTIFICATION_ENABLED_OFF);
538     }
539 
540     void onShortcutEnabledChanged() {
541         mIsShortcutEnabled = mOneHandedSettingsUtil.getShortcutEnabled(
542                 mContext.getContentResolver(), mUserId);
543 
544         mOneHandedUiEventLogger.writeEvent(mIsShortcutEnabled
545                 ? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_SHORTCUT_ENABLED_ON
546                 : OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_SHORTCUT_ENABLED_OFF);
547     }
548 
549     private void setupTimeoutListener() {
550         mTimeoutHandler.registerTimeoutListener(timeoutTime -> stopOneHanded(
551                 OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_TIMEOUT_OUT));
552     }
553 
554     @VisibleForTesting
555     boolean isLockedDisabled() {
556         return mLockedDisabled;
557     }
558 
559     @VisibleForTesting
560     boolean isOneHandedEnabled() {
561         return mIsOneHandedEnabled;
562     }
563 
564     @VisibleForTesting
565     boolean isShortcutEnabled() {
566         return mIsShortcutEnabled;
567     }
568 
569     @VisibleForTesting
570     boolean isSwipeToNotificationEnabled() {
571         return mIsSwipeToNotificationEnabled;
572     }
573 
574     private void updateOneHandedEnabled() {
575         if (mState.getState() == STATE_ENTERING || mState.getState() == STATE_ACTIVE) {
576             mMainExecutor.execute(() -> stopOneHanded());
577         }
578 
579         // If setting is pull screen, notify shortcut one_handed_mode_activated to reset
580         // and align status with current mState when one-handed gesture enabled.
581         if (isOneHandedEnabled() && !isSwipeToNotificationEnabled()) {
582             notifyShortcutStateChanged(mState.getState());
583         }
584 
585         mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled);
586 
587         if (!mIsOneHandedEnabled) {
588             mDisplayAreaOrganizer.unregisterOrganizer();
589             mBackgroundPanelOrganizer.unregisterOrganizer();
590             // Do NOT register + unRegister DA in the same call
591             return;
592         }
593 
594         if (mDisplayAreaOrganizer.getDisplayAreaTokenMap().isEmpty()) {
595             mDisplayAreaOrganizer.registerOrganizer(
596                     OneHandedDisplayAreaOrganizer.FEATURE_ONE_HANDED);
597         }
598 
599         if (!mBackgroundPanelOrganizer.isRegistered()) {
600             mBackgroundPanelOrganizer.registerOrganizer(
601                     OneHandedBackgroundPanelOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL);
602         }
603     }
604 
605     private void setupGesturalOverlay() {
606         if (!mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
607                 mContext.getContentResolver(), mUserId)) {
608             return;
609         }
610 
611         OverlayInfo info = null;
612         try {
613             mOverlayManager.setHighestPriority(ONE_HANDED_MODE_GESTURAL_OVERLAY, USER_CURRENT);
614             info = mOverlayManager.getOverlayInfo(ONE_HANDED_MODE_GESTURAL_OVERLAY, USER_CURRENT);
615         } catch (RemoteException e) { /* Do nothing */ }
616 
617         if (info != null && !info.isEnabled()) {
618             // Enable the default gestural one handed overlay.
619             setEnabledGesturalOverlay(true /* enabled */, false /* delayExecute */);
620         }
621     }
622 
623     @VisibleForTesting
624     private void setEnabledGesturalOverlay(boolean enabled, boolean delayExecute) {
625         if (mState.isTransitioning() || delayExecute) {
626             // Enabled overlay package may affect the current animation(e.g:Settings switch),
627             // so we delay 250ms to enabled overlay after switch animation finish, only delay once.
628             mMainExecutor.executeDelayed(() -> setEnabledGesturalOverlay(enabled, false),
629                     OVERLAY_ENABLED_DELAY_MS);
630             return;
631         }
632         try {
633             mOverlayManager.setEnabled(ONE_HANDED_MODE_GESTURAL_OVERLAY, enabled, USER_CURRENT);
634         } catch (RemoteException e) {
635             throw e.rethrowFromSystemServer();
636         }
637     }
638 
639     @VisibleForTesting
640     void setLockedDisabled(boolean locked, boolean enabled) {
641         final boolean isFeatureEnabled = mIsOneHandedEnabled || mIsSwipeToNotificationEnabled;
642 
643         if (enabled == isFeatureEnabled) {
644             return;
645         }
646 
647         mLockedDisabled = locked && !enabled;
648     }
649 
650     private void onConfigChanged(Configuration newConfig) {
651         if (mTutorialHandler == null || mBackgroundPanelOrganizer == null) {
652             return;
653         }
654         if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
655             return;
656         }
657         mBackgroundPanelOrganizer.onConfigurationChanged();
658         mTutorialHandler.onConfigurationChanged();
659     }
660 
661     private void onKeyguardVisibilityChanged(boolean showing) {
662         mKeyguardShowing = showing;
663     }
664 
665     private void onUserSwitch(int newUserId) {
666         unregisterSettingObservers();
667         mUserId = newUserId;
668         registerSettingObservers(newUserId);
669         updateSettings();
670         updateOneHandedEnabled();
671     }
672 
673     public void dump(@NonNull PrintWriter pw) {
674         final String innerPrefix = "  ";
675         pw.println();
676         pw.println(TAG);
677         pw.print(innerPrefix + "mOffSetFraction=");
678         pw.println(mOffSetFraction);
679         pw.print(innerPrefix + "mLockedDisabled=");
680         pw.println(mLockedDisabled);
681         pw.print(innerPrefix + "mUserId=");
682         pw.println(mUserId);
683         pw.print(innerPrefix + "isShortcutEnabled=");
684         pw.println(isShortcutEnabled());
685         pw.print(innerPrefix + "mIsSwipeToNotificationEnabled=");
686         pw.println(mIsSwipeToNotificationEnabled);
687 
688         if (mBackgroundPanelOrganizer != null) {
689             mBackgroundPanelOrganizer.dump(pw);
690         }
691 
692         if (mDisplayAreaOrganizer != null) {
693             mDisplayAreaOrganizer.dump(pw);
694         }
695 
696         if (mTouchHandler != null) {
697             mTouchHandler.dump(pw);
698         }
699 
700         if (mTimeoutHandler != null) {
701             mTimeoutHandler.dump(pw);
702         }
703 
704         if (mState != null) {
705             mState.dump(pw);
706         }
707 
708         if (mTutorialHandler != null) {
709             mTutorialHandler.dump(pw);
710         }
711 
712         if (mOneHandedAccessibilityUtil != null) {
713             mOneHandedAccessibilityUtil.dump(pw);
714         }
715 
716         mOneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver(), mUserId);
717 
718         if (mOverlayManager != null) {
719             OverlayInfo info = null;
720             try {
721                 info = mOverlayManager.getOverlayInfo(ONE_HANDED_MODE_GESTURAL_OVERLAY,
722                         USER_CURRENT);
723             } catch (RemoteException e) { /* Do nothing */ }
724 
725             if (info != null && !info.isEnabled()) {
726                 pw.print(innerPrefix + "OverlayInfo=");
727                 pw.println(info);
728             }
729         }
730     }
731 
732     /**
733      * Handles rotation based on OnDisplayChangingListener callback
734      */
735     @Override
736     public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
737             WindowContainerTransaction wct) {
738         if (!isInitialized()) {
739             return;
740         }
741 
742         if (!mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(mContext.getContentResolver(),
743                 mUserId) || mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
744                 mContext.getContentResolver(), mUserId)) {
745             return;
746         }
747 
748         mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct);
749         mOneHandedUiEventLogger.writeEvent(
750                 OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT);
751     }
752 
753     /**
754      * The interface for calls from outside the Shell, within the host process.
755      */
756     @ExternalThread
757     private class OneHandedImpl implements OneHanded {
758         private IOneHandedImpl mIOneHanded;
759 
760         @Override
761         public IOneHanded createExternalInterface() {
762             if (mIOneHanded != null) {
763                 mIOneHanded.invalidate();
764             }
765             mIOneHanded = new IOneHandedImpl(OneHandedController.this);
766             return mIOneHanded;
767         }
768 
769         @Override
770         public boolean isOneHandedEnabled() {
771             // This is volatile so return directly
772             return mIsOneHandedEnabled;
773         }
774 
775         @Override
776         public boolean isSwipeToNotificationEnabled() {
777             // This is volatile so return directly
778             return mIsSwipeToNotificationEnabled;
779         }
780 
781         @Override
782         public void startOneHanded() {
783             mMainExecutor.execute(() -> {
784                 OneHandedController.this.startOneHanded();
785             });
786         }
787 
788         @Override
789         public void stopOneHanded() {
790             mMainExecutor.execute(() -> {
791                 OneHandedController.this.stopOneHanded();
792             });
793         }
794 
795         @Override
796         public void stopOneHanded(int event) {
797             mMainExecutor.execute(() -> {
798                 OneHandedController.this.stopOneHanded(event);
799             });
800         }
801 
802         @Override
803         public void setLockedDisabled(boolean locked, boolean enabled) {
804             mMainExecutor.execute(() -> {
805                 OneHandedController.this.setLockedDisabled(locked, enabled);
806             });
807         }
808 
809         @Override
810         public void registerEventCallback(OneHandedEventCallback callback) {
811             mMainExecutor.execute(() -> {
812                 OneHandedController.this.registerEventCallback(callback);
813             });
814         }
815 
816         @Override
817         public void registerTransitionCallback(OneHandedTransitionCallback callback) {
818             mMainExecutor.execute(() -> {
819                 OneHandedController.this.registerTransitionCallback(callback);
820             });
821         }
822 
823         @Override
824         public void onConfigChanged(Configuration newConfig) {
825             mMainExecutor.execute(() -> {
826                 OneHandedController.this.onConfigChanged(newConfig);
827             });
828         }
829 
830         @Override
831         public void onUserSwitch(int userId) {
832             mMainExecutor.execute(() -> {
833                 OneHandedController.this.onUserSwitch(userId);
834             });
835         }
836 
837         @Override
838         public void onKeyguardVisibilityChanged(boolean showing) {
839             mMainExecutor.execute(() -> {
840                 OneHandedController.this.onKeyguardVisibilityChanged(showing);
841             });
842         }
843     }
844 
845     /**
846      * The interface for calls from outside the host process.
847      */
848     @BinderThread
849     private static class IOneHandedImpl extends IOneHanded.Stub {
850         private OneHandedController mController;
851 
852         IOneHandedImpl(OneHandedController controller) {
853             mController = controller;
854         }
855 
856         /**
857          * Invalidates this instance, preventing future calls from updating the controller.
858          */
859         void invalidate() {
860             mController = null;
861         }
862 
863         @Override
864         public void startOneHanded() {
865             executeRemoteCallWithTaskPermission(mController, "startOneHanded",
866                     (controller) -> {
867                         controller.startOneHanded();
868                     });
869         }
870 
871         @Override
872         public void stopOneHanded() {
873             executeRemoteCallWithTaskPermission(mController, "stopOneHanded",
874                     (controller) -> {
875                         controller.stopOneHanded();
876                     });
877         }
878     }
879 }
880