1 /*
2  * Copyright (C) 2021 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.floatingmenu;
18 
19 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
20 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
21 
22 import android.content.Context;
23 import android.hardware.display.DisplayManager;
24 import android.os.UserHandle;
25 import android.text.TextUtils;
26 import android.view.Display;
27 import android.view.WindowManager;
28 import android.view.accessibility.AccessibilityManager;
29 
30 import androidx.annotation.MainThread;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.keyguard.KeyguardUpdateMonitor;
34 import com.android.keyguard.KeyguardUpdateMonitorCallback;
35 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
36 import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode;
37 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
38 import com.android.systemui.dagger.SysUISingleton;
39 import com.android.systemui.settings.DisplayTracker;
40 import com.android.systemui.util.settings.SecureSettings;
41 
42 import javax.inject.Inject;
43 
44 /** A controller to handle the lifecycle of accessibility floating menu. */
45 @MainThread
46 @SysUISingleton
47 public class AccessibilityFloatingMenuController implements
48         AccessibilityButtonModeObserver.ModeChangedListener,
49         AccessibilityButtonTargetsObserver.TargetsChangedListener {
50 
51     private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
52     private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
53     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
54 
55     private Context mContext;
56     private final WindowManager mWindowManager;
57     private final DisplayManager mDisplayManager;
58     private final AccessibilityManager mAccessibilityManager;
59 
60     private final SecureSettings mSecureSettings;
61     private final DisplayTracker mDisplayTracker;
62     @VisibleForTesting
63     IAccessibilityFloatingMenu mFloatingMenu;
64     private int mBtnMode;
65     private String mBtnTargets;
66     private boolean mIsKeyguardVisible;
67 
68     @VisibleForTesting
69     final KeyguardUpdateMonitorCallback mKeyguardCallback = new KeyguardUpdateMonitorCallback() {
70 
71         @Override
72         public void onUserUnlocked() {
73             handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
74         }
75 
76         @Override
77         public void onKeyguardVisibilityChanged(boolean visible) {
78             mIsKeyguardVisible = visible;
79             handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
80         }
81 
82         @Override
83         public void onUserSwitching(int userId) {
84             destroyFloatingMenu();
85         }
86 
87         @Override
88         public void onUserSwitchComplete(int userId) {
89             mContext = mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
90             mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
91             mBtnTargets =
92                     mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
93             handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
94         }
95     };
96 
97     @Inject
AccessibilityFloatingMenuController(Context context, WindowManager windowManager, DisplayManager displayManager, AccessibilityManager accessibilityManager, AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver, AccessibilityButtonModeObserver accessibilityButtonModeObserver, KeyguardUpdateMonitor keyguardUpdateMonitor, SecureSettings secureSettings, DisplayTracker displayTracker)98     public AccessibilityFloatingMenuController(Context context,
99             WindowManager windowManager,
100             DisplayManager displayManager,
101             AccessibilityManager accessibilityManager,
102             AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
103             AccessibilityButtonModeObserver accessibilityButtonModeObserver,
104             KeyguardUpdateMonitor keyguardUpdateMonitor,
105             SecureSettings secureSettings,
106             DisplayTracker displayTracker) {
107         mContext = context;
108         mWindowManager = windowManager;
109         mDisplayManager = displayManager;
110         mAccessibilityManager = accessibilityManager;
111         mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
112         mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
113         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
114         mSecureSettings = secureSettings;
115         mDisplayTracker = displayTracker;
116 
117         mIsKeyguardVisible = false;
118     }
119 
120     /**
121      * Handles visibility of the accessibility floating menu when accessibility button mode changes.
122      *
123      * @param mode Current accessibility button mode.
124      */
125     @Override
onAccessibilityButtonModeChanged(@ccessibilityButtonMode int mode)126     public void onAccessibilityButtonModeChanged(@AccessibilityButtonMode int mode) {
127         mBtnMode = mode;
128         handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
129     }
130 
131     /**
132      * Handles visibility of the accessibility floating menu when accessibility button targets
133      * changes.
134      * List should come from {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
135      * @param targets Current accessibility button list.
136      */
137     @Override
onAccessibilityButtonTargetsChanged(String targets)138     public void onAccessibilityButtonTargetsChanged(String targets) {
139         mBtnTargets = targets;
140         handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
141     }
142 
143     /** Initializes the AccessibilityFloatingMenuController configurations. */
init()144     public void init() {
145         mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
146         mBtnTargets = mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
147         registerContentObservers();
148     }
149 
registerContentObservers()150     private void registerContentObservers() {
151         mAccessibilityButtonModeObserver.addListener(this);
152         mAccessibilityButtonTargetsObserver.addListener(this);
153         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
154     }
155 
156     /**
157      * Handles the accessibility floating menu visibility with the given values.
158      *
159      * @param keyguardVisible the keyguard visibility status. Not show the
160      *                        {@link MenuView} when keyguard appears.
161      * @param mode accessibility button mode {@link AccessibilityButtonMode}
162      * @param targets accessibility button list; it should comes from
163      *                {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
164      */
handleFloatingMenuVisibility(boolean keyguardVisible, @AccessibilityButtonMode int mode, String targets)165     private void handleFloatingMenuVisibility(boolean keyguardVisible,
166             @AccessibilityButtonMode int mode, String targets) {
167         if (keyguardVisible) {
168             destroyFloatingMenu();
169             return;
170         }
171 
172         if (shouldShowFloatingMenu(mode, targets)) {
173             showFloatingMenu();
174         } else {
175             destroyFloatingMenu();
176         }
177     }
178 
shouldShowFloatingMenu(@ccessibilityButtonMode int mode, String targets)179     private boolean shouldShowFloatingMenu(@AccessibilityButtonMode int mode, String targets) {
180         return mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU && !TextUtils.isEmpty(targets);
181     }
182 
showFloatingMenu()183     private void showFloatingMenu() {
184         if (mFloatingMenu == null) {
185             final Display defaultDisplay = mDisplayManager.getDisplay(
186                     mDisplayTracker.getDefaultDisplayId());
187             final Context windowContext = mContext.createWindowContext(defaultDisplay,
188                     TYPE_NAVIGATION_BAR_PANEL, /* options= */ null);
189             mFloatingMenu = new MenuViewLayerController(windowContext, mWindowManager,
190                     mAccessibilityManager, mSecureSettings);
191         }
192 
193         mFloatingMenu.show();
194     }
195 
destroyFloatingMenu()196     private void destroyFloatingMenu() {
197         if (mFloatingMenu == null) {
198             return;
199         }
200 
201         mFloatingMenu.hide();
202         mFloatingMenu = null;
203     }
204 }
205