1 /* 2 * Copyright (C) 2019 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.assist; 18 19 import static com.android.systemui.assist.AssistModule.ASSIST_HANDLE_THREAD_NAME; 20 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.os.Handler; 24 import android.os.SystemClock; 25 import android.provider.Settings; 26 import android.util.Log; 27 import android.view.accessibility.AccessibilityManager; 28 29 import androidx.annotation.Nullable; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.internal.app.AssistUtils; 33 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 34 import com.android.keyguard.KeyguardUpdateMonitor; 35 import com.android.systemui.Dumpable; 36 import com.android.systemui.dump.DumpManager; 37 import com.android.systemui.shared.system.QuickStepContract; 38 import com.android.systemui.statusbar.phone.NavigationModeController; 39 40 import java.io.FileDescriptor; 41 import java.io.PrintWriter; 42 import java.util.Map; 43 import java.util.concurrent.TimeUnit; 44 45 import javax.inject.Inject; 46 import javax.inject.Named; 47 import javax.inject.Provider; 48 import javax.inject.Singleton; 49 50 import dagger.Lazy; 51 52 /** 53 * A class for managing Assistant handle logic. 54 * 55 * Controls when visual handles for Assistant gesture affordance should be shown or hidden using an 56 * {@link AssistHandleBehavior}. 57 */ 58 @Singleton 59 public final class AssistHandleBehaviorController implements AssistHandleCallbacks, Dumpable { 60 61 private static final String TAG = "AssistHandleBehavior"; 62 63 private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = 0; 64 private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3); 65 private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; 66 67 /** 68 * This is the default behavior that will be used once the system is up. It will be set once the 69 * behavior dependencies are available. This ensures proper behavior lifecycle. 70 */ 71 private static final AssistHandleBehavior DEFAULT_BEHAVIOR = AssistHandleBehavior.REMINDER_EXP; 72 73 private final Context mContext; 74 private final AssistUtils mAssistUtils; 75 private final Handler mHandler; 76 private final Runnable mHideHandles = this::hideHandles; 77 private final Runnable mShowAndGo = this::showAndGoInternal; 78 private final Provider<AssistHandleViewController> mAssistHandleViewController; 79 private final DeviceConfigHelper mDeviceConfigHelper; 80 private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap; 81 private final Lazy<AccessibilityManager> mA11yManager; 82 83 private boolean mHandlesShowing = false; 84 private long mHandlesLastHiddenAt; 85 private long mShowAndGoEndsAt; 86 /** 87 * This should always be initialized as {@link AssistHandleBehavior#OFF} to ensure proper 88 * behavior lifecycle. 89 */ 90 private AssistHandleBehavior mCurrentBehavior = AssistHandleBehavior.OFF; 91 private boolean mInGesturalMode; 92 93 @Inject AssistHandleBehaviorController( Context context, AssistUtils assistUtils, @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler, Provider<AssistHandleViewController> assistHandleViewController, DeviceConfigHelper deviceConfigHelper, Map<AssistHandleBehavior, BehaviorController> behaviorMap, NavigationModeController navigationModeController, Lazy<AccessibilityManager> a11yManager, DumpManager dumpManager)94 AssistHandleBehaviorController( 95 Context context, 96 AssistUtils assistUtils, 97 @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler, 98 Provider<AssistHandleViewController> assistHandleViewController, 99 DeviceConfigHelper deviceConfigHelper, 100 Map<AssistHandleBehavior, BehaviorController> behaviorMap, 101 NavigationModeController navigationModeController, 102 Lazy<AccessibilityManager> a11yManager, 103 DumpManager dumpManager) { 104 mContext = context; 105 mAssistUtils = assistUtils; 106 mHandler = handler; 107 mAssistHandleViewController = assistHandleViewController; 108 mDeviceConfigHelper = deviceConfigHelper; 109 mBehaviorMap = behaviorMap; 110 mA11yManager = a11yManager; 111 112 mInGesturalMode = QuickStepContract.isGesturalMode( 113 navigationModeController.addListener(this::handleNavigationModeChange)); 114 115 setBehavior(getBehaviorMode()); 116 mDeviceConfigHelper.addOnPropertiesChangedListener( 117 mHandler::post, 118 (properties) -> { 119 if (properties.getKeyset().contains( 120 SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE)) { 121 setBehavior(properties.getString( 122 SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, null)); 123 } 124 }); 125 126 dumpManager.registerDumpable(TAG, this); 127 } 128 129 @Override // AssistHandleCallbacks hide()130 public void hide() { 131 clearPendingCommands(); 132 mHandler.post(mHideHandles); 133 } 134 135 @Override // AssistHandleCallbacks showAndGo()136 public void showAndGo() { 137 clearPendingCommands(); 138 mHandler.post(mShowAndGo); 139 } 140 showAndGoInternal()141 private void showAndGoInternal() { 142 maybeShowHandles(/* ignoreThreshold = */ false); 143 long showAndGoDuration = getShowAndGoDuration(); 144 mShowAndGoEndsAt = SystemClock.elapsedRealtime() + showAndGoDuration; 145 mHandler.postDelayed(mHideHandles, showAndGoDuration); 146 } 147 148 @Override // AssistHandleCallbacks showAndGoDelayed(long delayMs, boolean hideIfShowing)149 public void showAndGoDelayed(long delayMs, boolean hideIfShowing) { 150 clearPendingCommands(); 151 if (hideIfShowing) { 152 mHandler.post(mHideHandles); 153 } 154 mHandler.postDelayed(mShowAndGo, delayMs); 155 } 156 157 @Override // AssistHandleCallbacks showAndStay()158 public void showAndStay() { 159 clearPendingCommands(); 160 mHandler.post(() -> maybeShowHandles(/* ignoreThreshold = */ true)); 161 } 162 getShowAndGoRemainingTimeMs()163 public long getShowAndGoRemainingTimeMs() { 164 return Long.max(mShowAndGoEndsAt - SystemClock.elapsedRealtime(), 0); 165 } 166 areHandlesShowing()167 public boolean areHandlesShowing() { 168 return mHandlesShowing; 169 } 170 onAssistantGesturePerformed()171 void onAssistantGesturePerformed() { 172 mBehaviorMap.get(mCurrentBehavior).onAssistantGesturePerformed(); 173 } 174 onAssistHandlesRequested()175 void onAssistHandlesRequested() { 176 if (mInGesturalMode) { 177 mBehaviorMap.get(mCurrentBehavior).onAssistHandlesRequested(); 178 } 179 } 180 setBehavior(AssistHandleBehavior behavior)181 void setBehavior(AssistHandleBehavior behavior) { 182 if (mCurrentBehavior == behavior) { 183 return; 184 } 185 186 if (!mBehaviorMap.containsKey(behavior)) { 187 Log.e(TAG, "Unsupported behavior requested: " + behavior.toString()); 188 return; 189 } 190 191 if (mInGesturalMode) { 192 mBehaviorMap.get(mCurrentBehavior).onModeDeactivated(); 193 mBehaviorMap.get(behavior).onModeActivated(mContext, /* callbacks = */ this); 194 } 195 196 mCurrentBehavior = behavior; 197 } 198 setBehavior(@ullable String behavior)199 private void setBehavior(@Nullable String behavior) { 200 try { 201 setBehavior(AssistHandleBehavior.valueOf(behavior)); 202 } catch (IllegalArgumentException | NullPointerException e) { 203 Log.e(TAG, "Invalid behavior: " + behavior); 204 } 205 } 206 handlesUnblocked(boolean ignoreThreshold)207 private boolean handlesUnblocked(boolean ignoreThreshold) { 208 if (!isUserSetupComplete()) { 209 return false; 210 } 211 212 long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt; 213 boolean notThrottled = ignoreThreshold || timeSinceHidden >= getShownFrequencyThreshold(); 214 ComponentName assistantComponent = 215 mAssistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser()); 216 return notThrottled && assistantComponent != null; 217 } 218 getShownFrequencyThreshold()219 private long getShownFrequencyThreshold() { 220 return mDeviceConfigHelper.getLong( 221 SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS, 222 DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS); 223 } 224 getShowAndGoDuration()225 private long getShowAndGoDuration() { 226 long configuredTime = mDeviceConfigHelper.getLong( 227 SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS, 228 DEFAULT_SHOW_AND_GO_DURATION_MS); 229 return mA11yManager.get().getRecommendedTimeoutMillis( 230 (int) configuredTime, AccessibilityManager.FLAG_CONTENT_ICONS); 231 } 232 getBehaviorMode()233 private String getBehaviorMode() { 234 return mDeviceConfigHelper.getString( 235 SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, 236 DEFAULT_BEHAVIOR.toString()); 237 } 238 maybeShowHandles(boolean ignoreThreshold)239 private void maybeShowHandles(boolean ignoreThreshold) { 240 if (mHandlesShowing) { 241 return; 242 } 243 244 if (handlesUnblocked(ignoreThreshold)) { 245 mHandlesShowing = true; 246 AssistHandleViewController assistHandleViewController = 247 mAssistHandleViewController.get(); 248 if (assistHandleViewController == null) { 249 Log.w(TAG, "Couldn't show handles, AssistHandleViewController unavailable"); 250 } else { 251 assistHandleViewController.setAssistHintVisible(true); 252 } 253 } 254 } 255 hideHandles()256 private void hideHandles() { 257 if (!mHandlesShowing) { 258 return; 259 } 260 261 mHandlesShowing = false; 262 mHandlesLastHiddenAt = SystemClock.elapsedRealtime(); 263 AssistHandleViewController assistHandleViewController = 264 mAssistHandleViewController.get(); 265 if (assistHandleViewController == null) { 266 Log.w(TAG, "Couldn't show handles, AssistHandleViewController unavailable"); 267 } else { 268 assistHandleViewController.setAssistHintVisible(false); 269 } 270 } 271 handleNavigationModeChange(int navigationMode)272 private void handleNavigationModeChange(int navigationMode) { 273 boolean inGesturalMode = QuickStepContract.isGesturalMode(navigationMode); 274 if (mInGesturalMode == inGesturalMode) { 275 return; 276 } 277 278 mInGesturalMode = inGesturalMode; 279 if (mInGesturalMode) { 280 mBehaviorMap.get(mCurrentBehavior).onModeActivated(mContext, /* callbacks = */ this); 281 } else { 282 mBehaviorMap.get(mCurrentBehavior).onModeDeactivated(); 283 hide(); 284 } 285 } 286 clearPendingCommands()287 private void clearPendingCommands() { 288 mHandler.removeCallbacks(mHideHandles); 289 mHandler.removeCallbacks(mShowAndGo); 290 mShowAndGoEndsAt = 0; 291 } 292 isUserSetupComplete()293 private boolean isUserSetupComplete() { 294 return Settings.Secure.getInt( 295 mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; 296 } 297 298 @VisibleForTesting setInGesturalModeForTest(boolean inGesturalMode)299 void setInGesturalModeForTest(boolean inGesturalMode) { 300 mInGesturalMode = inGesturalMode; 301 } 302 303 @Override // Dumpable dump(FileDescriptor fd, PrintWriter pw, String[] args)304 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 305 pw.println("Current AssistHandleBehaviorController State:"); 306 307 pw.println(" mHandlesShowing=" + mHandlesShowing); 308 pw.println(" mHandlesLastHiddenAt=" + mHandlesLastHiddenAt); 309 pw.println(" mInGesturalMode=" + mInGesturalMode); 310 311 pw.println(" Phenotype Flags:"); 312 pw.println(" " 313 + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS + "(a11y modded)" 314 + "=" 315 + getShowAndGoDuration()); 316 pw.println(" " 317 + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS 318 + "=" 319 + getShownFrequencyThreshold()); 320 pw.println(" " 321 + SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE 322 + "=" 323 + getBehaviorMode()); 324 325 pw.println(" mCurrentBehavior=" + mCurrentBehavior.toString()); 326 mBehaviorMap.get(mCurrentBehavior).dump(pw, " "); 327 } 328 329 interface BehaviorController { onModeActivated(Context context, AssistHandleCallbacks callbacks)330 void onModeActivated(Context context, AssistHandleCallbacks callbacks); onModeDeactivated()331 default void onModeDeactivated() {} onAssistantGesturePerformed()332 default void onAssistantGesturePerformed() {} onAssistHandlesRequested()333 default void onAssistHandlesRequested() {} dump(PrintWriter pw, String prefix)334 default void dump(PrintWriter pw, String prefix) {} 335 } 336 } 337