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