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