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.navigationbar;
18 
19 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
20 
21 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
22 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
23 
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.database.ContentObserver;
27 import android.inputmethodservice.InputMethodService;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.UserHandle;
33 import android.provider.Settings;
34 import android.view.View;
35 import android.view.WindowInsets;
36 import android.view.accessibility.AccessibilityManager;
37 
38 import androidx.annotation.NonNull;
39 
40 import com.android.systemui.Dumpable;
41 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
42 import com.android.systemui.assist.AssistManager;
43 import com.android.systemui.dagger.SysUISingleton;
44 import com.android.systemui.dump.DumpManager;
45 import com.android.systemui.recents.OverviewProxyService;
46 import com.android.systemui.settings.UserTracker;
47 import com.android.systemui.shared.system.QuickStepContract;
48 import com.android.systemui.statusbar.phone.StatusBar;
49 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
50 
51 import java.io.FileDescriptor;
52 import java.io.PrintWriter;
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.Optional;
56 
57 import javax.inject.Inject;
58 
59 import dagger.Lazy;
60 
61 /**
62  * Extracts shared elements between navbar and taskbar delegate to de-dupe logic and help them
63  * experience the joys of friendship.
64  * The events are then passed through
65  *
66  * Currently consolidates
67  * * A11y
68  * * Assistant
69  */
70 @SysUISingleton
71 public final class NavBarHelper implements
72         AccessibilityButtonModeObserver.ModeChangedListener,
73         OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
74         Dumpable {
75     private final AccessibilityManager mAccessibilityManager;
76     private final Lazy<AssistManager> mAssistManagerLazy;
77     private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
78     private final UserTracker mUserTracker;
79     private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
80     private final List<NavbarTaskbarStateUpdater> mA11yEventListeners = new ArrayList<>();
81     private final Context mContext;
82     private ContentResolver mContentResolver;
83     private boolean mAssistantAvailable;
84     private boolean mLongPressHomeEnabled;
85     private boolean mAssistantTouchGestureEnabled;
86     private int mNavBarMode;
87 
88     private final ContentObserver mAssistContentObserver = new ContentObserver(
89             new Handler(Looper.getMainLooper())) {
90         @Override
91         public void onChange(boolean selfChange, Uri uri) {
92             updateAssitantAvailability();
93         }
94     };
95 
96     /**
97      * @param context This is not display specific, then again neither is any of the code in
98      *                this class. Once there's display specific code, we may want to create an
99      *                instance of this class per navbar vs having it be a singleton.
100      */
101     @Inject
NavBarHelper(Context context, AccessibilityManager accessibilityManager, AccessibilityManagerWrapper accessibilityManagerWrapper, AccessibilityButtonModeObserver accessibilityButtonModeObserver, OverviewProxyService overviewProxyService, Lazy<AssistManager> assistManagerLazy, Lazy<Optional<StatusBar>> statusBarOptionalLazy, NavigationModeController navigationModeController, UserTracker userTracker, DumpManager dumpManager)102     public NavBarHelper(Context context, AccessibilityManager accessibilityManager,
103             AccessibilityManagerWrapper accessibilityManagerWrapper,
104             AccessibilityButtonModeObserver accessibilityButtonModeObserver,
105             OverviewProxyService overviewProxyService,
106             Lazy<AssistManager> assistManagerLazy,
107             Lazy<Optional<StatusBar>> statusBarOptionalLazy,
108             NavigationModeController navigationModeController,
109             UserTracker userTracker,
110             DumpManager dumpManager) {
111         mContext = context;
112         mContentResolver = mContext.getContentResolver();
113         mAccessibilityManager = accessibilityManager;
114         mAssistManagerLazy = assistManagerLazy;
115         mStatusBarOptionalLazy = statusBarOptionalLazy;
116         mUserTracker = userTracker;
117         accessibilityManagerWrapper.addCallback(
118                 accessibilityManager1 -> NavBarHelper.this.dispatchA11yEventUpdate());
119         mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
120 
121         mAccessibilityButtonModeObserver.addListener(this);
122         mNavBarMode = navigationModeController.addListener(this);
123         overviewProxyService.addCallback(this);
124         dumpManager.registerDumpable(this);
125     }
126 
init()127     public void init() {
128         mContentResolver.registerContentObserver(
129                 Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
130                 false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
131         mContentResolver.registerContentObserver(
132                 Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED),
133                 false, mAssistContentObserver, UserHandle.USER_ALL);
134         mContentResolver.registerContentObserver(
135                 Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED),
136                 false, mAssistContentObserver, UserHandle.USER_ALL);
137         updateAssitantAvailability();
138     }
139 
destroy()140     public void destroy() {
141         mContentResolver.unregisterContentObserver(mAssistContentObserver);
142     }
143 
144     /**
145      * @param listener Will immediately get callbacks based on current state
146      */
registerNavTaskStateUpdater(NavbarTaskbarStateUpdater listener)147     public void registerNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) {
148         mA11yEventListeners.add(listener);
149         listener.updateAccessibilityServicesState();
150         listener.updateAssistantAvailable(mAssistantAvailable);
151     }
152 
removeNavTaskStateUpdater(NavbarTaskbarStateUpdater listener)153     public void removeNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) {
154         mA11yEventListeners.remove(listener);
155     }
156 
dispatchA11yEventUpdate()157     private void dispatchA11yEventUpdate() {
158         for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) {
159             listener.updateAccessibilityServicesState();
160         }
161     }
162 
dispatchAssistantEventUpdate(boolean assistantAvailable)163     private void dispatchAssistantEventUpdate(boolean assistantAvailable) {
164         for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) {
165             listener.updateAssistantAvailable(assistantAvailable);
166         }
167     }
168 
169     @Override
onAccessibilityButtonModeChanged(int mode)170     public void onAccessibilityButtonModeChanged(int mode) {
171         dispatchA11yEventUpdate();
172     }
173 
174     /**
175      * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and
176      * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE}
177      *
178      * @return the a11y button clickable and long_clickable states, or 0 if there is no
179      *         a11y button in the navbar
180      */
getA11yButtonState()181     public int getA11yButtonState() {
182         // AccessibilityManagerService resolves services for the current user since the local
183         // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
184         final List<String> a11yButtonTargets =
185                 mAccessibilityManager.getAccessibilityShortcutTargets(
186                         AccessibilityManager.ACCESSIBILITY_BUTTON);
187         final int requestingServices = a11yButtonTargets.size();
188 
189         // If accessibility button is floating menu mode, click and long click state should be
190         // disabled.
191         if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
192                 == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
193             return 0;
194         }
195 
196         return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
197                 | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
198     }
199 
200     @Override
onConnectionChanged(boolean isConnected)201     public void onConnectionChanged(boolean isConnected) {
202         if (isConnected) {
203             updateAssitantAvailability();
204         }
205     }
206 
updateAssitantAvailability()207     private void updateAssitantAvailability() {
208         boolean assistantAvailableForUser = mAssistManagerLazy.get()
209                 .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
210         boolean longPressDefault = mContext.getResources().getBoolean(
211                 com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault);
212         mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver,
213                 Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0,
214                 mUserTracker.getUserId()) != 0;
215         boolean gestureDefault = mContext.getResources().getBoolean(
216                 com.android.internal.R.bool.config_assistTouchGestureEnabledDefault);
217         mAssistantTouchGestureEnabled = Settings.Secure.getIntForUser(mContentResolver,
218                 Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0,
219                 mUserTracker.getUserId()) != 0;
220 
221         mAssistantAvailable = assistantAvailableForUser
222                 && mAssistantTouchGestureEnabled
223                 && QuickStepContract.isGesturalMode(mNavBarMode);
224         dispatchAssistantEventUpdate(mAssistantAvailable);
225     }
226 
getLongPressHomeEnabled()227     public boolean getLongPressHomeEnabled() {
228         return mLongPressHomeEnabled;
229     }
230 
231     @Override
startAssistant(Bundle bundle)232     public void startAssistant(Bundle bundle) {
233         mAssistManagerLazy.get().startAssist(bundle);
234     }
235 
236     @Override
onNavigationModeChanged(int mode)237     public void onNavigationModeChanged(int mode) {
238         mNavBarMode = mode;
239         updateAssitantAvailability();
240     }
241 
242     /**
243      * @return Whether the IME is shown on top of the screen given the {@code vis} flag of
244      * {@link InputMethodService} and the keyguard states.
245      */
isImeShown(int vis)246     public boolean isImeShown(int vis) {
247         View shadeWindowView = mStatusBarOptionalLazy.get().get().getNotificationShadeWindowView();
248         boolean isKeyguardShowing = mStatusBarOptionalLazy.get().get().isKeyguardShowing();
249         boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow()
250                 && shadeWindowView.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
251         return imeVisibleOnShade
252                 || (!isKeyguardShowing && (vis & InputMethodService.IME_VISIBLE) != 0);
253     }
254 
255     /**
256      * Callbacks will get fired once immediately after registering via
257      * {@link #registerNavTaskStateUpdater(NavbarTaskbarStateUpdater)}
258      */
259     public interface NavbarTaskbarStateUpdater {
updateAccessibilityServicesState()260         void updateAccessibilityServicesState();
updateAssistantAvailable(boolean available)261         void updateAssistantAvailable(boolean available);
262     }
263 
264     @Override
dump(@onNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args)265     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
266         pw.println("NavbarTaskbarFriendster");
267         pw.println("  longPressHomeEnabled=" + mLongPressHomeEnabled);
268         pw.println("  mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled);
269         pw.println("  mAssistantAvailable=" + mAssistantAvailable);
270         pw.println("  mNavBarMode=" + mNavBarMode);
271     }
272 }
273