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