1 package com.android.systemui.assist; 2 3 import static android.view.Display.DEFAULT_DISPLAY; 4 5 import static com.android.systemui.DejankUtils.whitelistIpcs; 6 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED; 7 8 import android.annotation.NonNull; 9 import android.annotation.Nullable; 10 import android.app.ActivityManager; 11 import android.app.ActivityOptions; 12 import android.app.SearchManager; 13 import android.content.ActivityNotFoundException; 14 import android.content.ComponentName; 15 import android.content.Context; 16 import android.content.Intent; 17 import android.metrics.LogMaker; 18 import android.os.AsyncTask; 19 import android.os.Bundle; 20 import android.os.Handler; 21 import android.os.RemoteException; 22 import android.os.SystemClock; 23 import android.os.UserHandle; 24 import android.provider.Settings; 25 import android.service.voice.VoiceInteractionSession; 26 import android.util.Log; 27 28 import com.android.internal.app.AssistUtils; 29 import com.android.internal.app.IVoiceInteractionSessionListener; 30 import com.android.internal.app.IVoiceInteractionSessionShowCallback; 31 import com.android.internal.logging.MetricsLogger; 32 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 33 import com.android.keyguard.KeyguardUpdateMonitor; 34 import com.android.systemui.R; 35 import com.android.systemui.assist.ui.DefaultUiController; 36 import com.android.systemui.dagger.SysUISingleton; 37 import com.android.systemui.model.SysUiState; 38 import com.android.systemui.recents.OverviewProxyService; 39 import com.android.systemui.statusbar.CommandQueue; 40 import com.android.systemui.statusbar.policy.ConfigurationController; 41 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 42 43 import javax.inject.Inject; 44 45 import dagger.Lazy; 46 47 /** 48 * Class to manage everything related to assist in SystemUI. 49 */ 50 @SysUISingleton 51 public class AssistManager { 52 53 /** 54 * Controls the UI for showing Assistant invocation progress. 55 */ 56 public interface UiController { 57 /** 58 * Updates the invocation progress. 59 * 60 * @param type one of INVOCATION_TYPE_GESTURE, INVOCATION_TYPE_ACTIVE_EDGE, 61 * INVOCATION_TYPE_VOICE, INVOCATION_TYPE_QUICK_SEARCH_BAR, 62 * INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS 63 * @param progress a float between 0 and 1 inclusive. 0 represents the beginning of the 64 * gesture; 1 represents the end. 65 */ onInvocationProgress(int type, float progress)66 void onInvocationProgress(int type, float progress); 67 68 /** 69 * Called when an invocation gesture completes. 70 * 71 * @param velocity the speed of the invocation gesture, in pixels per millisecond. For 72 * drags, this is 0. 73 */ onGestureCompletion(float velocity)74 void onGestureCompletion(float velocity); 75 76 /** 77 * Hides any SysUI for the assistant, but _does not_ close the assistant itself. 78 */ hide()79 void hide(); 80 } 81 82 private static final String TAG = "AssistManager"; 83 84 // Note that VERBOSE logging may leak PII (e.g. transcription contents). 85 private static final boolean VERBOSE = false; 86 87 private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms"; 88 private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state"; 89 protected static final String ACTION_KEY = "action"; 90 protected static final String SET_ASSIST_GESTURE_CONSTRAINED_ACTION = 91 "set_assist_gesture_constrained"; 92 protected static final String CONSTRAINED_KEY = "should_constrain"; 93 94 public static final String INVOCATION_TYPE_KEY = "invocation_type"; 95 public static final int INVOCATION_TYPE_UNKNOWN = 96 AssistUtils.INVOCATION_TYPE_UNKNOWN; 97 public static final int INVOCATION_TYPE_GESTURE = 98 AssistUtils.INVOCATION_TYPE_GESTURE; 99 public static final int INVOCATION_TYPE_OTHER = 100 AssistUtils.INVOCATION_TYPE_PHYSICAL_GESTURE; 101 public static final int INVOCATION_TYPE_VOICE = 102 AssistUtils.INVOCATION_TYPE_VOICE; 103 public static final int INVOCATION_TYPE_QUICK_SEARCH_BAR = 104 AssistUtils.INVOCATION_TYPE_QUICK_SEARCH_BAR; 105 public static final int INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS = 106 AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS; 107 public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS = 108 AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS; 109 110 public static final int DISMISS_REASON_INVOCATION_CANCELLED = 1; 111 public static final int DISMISS_REASON_TAP = 2; 112 public static final int DISMISS_REASON_BACK = 3; 113 public static final int DISMISS_REASON_TIMEOUT = 4; 114 115 private static final long TIMEOUT_SERVICE = 2500; 116 private static final long TIMEOUT_ACTIVITY = 1000; 117 118 protected final Context mContext; 119 private final AssistDisclosure mAssistDisclosure; 120 private final PhoneStateMonitor mPhoneStateMonitor; 121 private final UiController mUiController; 122 protected final Lazy<SysUiState> mSysUiState; 123 protected final AssistLogger mAssistLogger; 124 125 private final DeviceProvisionedController mDeviceProvisionedController; 126 private final CommandQueue mCommandQueue; 127 private final AssistOrbController mOrbController; 128 protected final AssistUtils mAssistUtils; 129 130 private IVoiceInteractionSessionShowCallback mShowCallback = 131 new IVoiceInteractionSessionShowCallback.Stub() { 132 133 @Override 134 public void onFailed() throws RemoteException { 135 mOrbController.postHide(); 136 } 137 138 @Override 139 public void onShown() throws RemoteException { 140 mOrbController.postHide(); 141 } 142 }; 143 144 @Inject AssistManager( DeviceProvisionedController controller, Context context, AssistUtils assistUtils, CommandQueue commandQueue, PhoneStateMonitor phoneStateMonitor, OverviewProxyService overviewProxyService, ConfigurationController configurationController, Lazy<SysUiState> sysUiState, DefaultUiController defaultUiController, AssistLogger assistLogger)145 public AssistManager( 146 DeviceProvisionedController controller, 147 Context context, 148 AssistUtils assistUtils, 149 CommandQueue commandQueue, 150 PhoneStateMonitor phoneStateMonitor, 151 OverviewProxyService overviewProxyService, 152 ConfigurationController configurationController, 153 Lazy<SysUiState> sysUiState, 154 DefaultUiController defaultUiController, 155 AssistLogger assistLogger) { 156 mContext = context; 157 mDeviceProvisionedController = controller; 158 mCommandQueue = commandQueue; 159 mAssistUtils = assistUtils; 160 mAssistDisclosure = new AssistDisclosure(context, new Handler()); 161 mPhoneStateMonitor = phoneStateMonitor; 162 mAssistLogger = assistLogger; 163 164 mOrbController = new AssistOrbController(configurationController, context); 165 166 registerVoiceInteractionSessionListener(); 167 168 mUiController = defaultUiController; 169 170 mSysUiState = sysUiState; 171 172 overviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() { 173 @Override 174 public void onAssistantProgress(float progress) { 175 // Progress goes from 0 to 1 to indicate how close the assist gesture is to 176 // completion. 177 onInvocationProgress(INVOCATION_TYPE_GESTURE, progress); 178 } 179 180 @Override 181 public void onAssistantGestureCompletion(float velocity) { 182 onGestureCompletion(velocity); 183 } 184 }); 185 } 186 registerVoiceInteractionSessionListener()187 protected void registerVoiceInteractionSessionListener() { 188 mAssistUtils.registerVoiceInteractionSessionListener( 189 new IVoiceInteractionSessionListener.Stub() { 190 @Override 191 public void onVoiceSessionShown() throws RemoteException { 192 if (VERBOSE) { 193 Log.v(TAG, "Voice open"); 194 } 195 mAssistLogger.reportAssistantSessionEvent( 196 AssistantSessionEvent.ASSISTANT_SESSION_UPDATE); 197 } 198 199 @Override 200 public void onVoiceSessionHidden() throws RemoteException { 201 if (VERBOSE) { 202 Log.v(TAG, "Voice closed"); 203 } 204 mAssistLogger.reportAssistantSessionEvent( 205 AssistantSessionEvent.ASSISTANT_SESSION_CLOSE); 206 } 207 208 @Override 209 public void onSetUiHints(Bundle hints) { 210 if (VERBOSE) { 211 Log.v(TAG, "UI hints received"); 212 } 213 214 String action = hints.getString(ACTION_KEY); 215 if (SET_ASSIST_GESTURE_CONSTRAINED_ACTION.equals(action)) { 216 mSysUiState.get() 217 .setFlag( 218 SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED, 219 hints.getBoolean(CONSTRAINED_KEY, false)) 220 .commitUpdate(DEFAULT_DISPLAY); 221 } 222 } 223 }); 224 } 225 shouldShowOrb()226 protected boolean shouldShowOrb() { 227 return !ActivityManager.isLowRamDeviceStatic(); 228 } 229 startAssist(Bundle args)230 public void startAssist(Bundle args) { 231 final ComponentName assistComponent = getAssistInfo(); 232 if (assistComponent == null) { 233 return; 234 } 235 236 final boolean isService = assistComponent.equals(getVoiceInteractorComponentName()); 237 if (!isService || (!isVoiceSessionRunning() && shouldShowOrb())) { 238 mOrbController.showOrb(assistComponent, isService); 239 mOrbController.postHideDelayed(isService ? TIMEOUT_SERVICE : TIMEOUT_ACTIVITY); 240 } 241 242 if (args == null) { 243 args = new Bundle(); 244 } 245 int legacyInvocationType = args.getInt(INVOCATION_TYPE_KEY, 0); 246 int legacyDeviceState = mPhoneStateMonitor.getPhoneState(); 247 args.putInt(INVOCATION_PHONE_STATE_KEY, legacyDeviceState); 248 args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.elapsedRealtime()); 249 mAssistLogger.reportAssistantInvocationEventFromLegacy( 250 legacyInvocationType, 251 /* isInvocationComplete = */ true, 252 assistComponent, 253 legacyDeviceState); 254 logStartAssistLegacy(legacyInvocationType, legacyDeviceState); 255 startAssistInternal(args, assistComponent, isService); 256 } 257 258 /** Called when the user is performing an assistant invocation action (e.g. Active Edge) */ onInvocationProgress(int type, float progress)259 public void onInvocationProgress(int type, float progress) { 260 mUiController.onInvocationProgress(type, progress); 261 } 262 263 /** 264 * Called when the user has invoked the assistant with the incoming velocity, in pixels per 265 * millisecond. For invocations without a velocity (e.g. slow drag), the velocity is set to 266 * zero. 267 */ onGestureCompletion(float velocity)268 public void onGestureCompletion(float velocity) { 269 mUiController.onGestureCompletion(velocity); 270 } 271 hideAssist()272 public void hideAssist() { 273 mAssistUtils.hideCurrentSession(); 274 } 275 startAssistInternal(Bundle args, @NonNull ComponentName assistComponent, boolean isService)276 private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent, 277 boolean isService) { 278 if (isService) { 279 startVoiceInteractor(args); 280 } else { 281 startAssistActivity(args, assistComponent); 282 } 283 } 284 startAssistActivity(Bundle args, @NonNull ComponentName assistComponent)285 private void startAssistActivity(Bundle args, @NonNull ComponentName assistComponent) { 286 if (!mDeviceProvisionedController.isDeviceProvisioned()) { 287 return; 288 } 289 290 // Close Recent Apps if needed 291 mCommandQueue.animateCollapsePanels( 292 CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, 293 false /* force */); 294 295 boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(), 296 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0; 297 298 final SearchManager searchManager = 299 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); 300 if (searchManager == null) { 301 return; 302 } 303 final Intent intent = searchManager.getAssistIntent(structureEnabled); 304 if (intent == null) { 305 return; 306 } 307 intent.setComponent(assistComponent); 308 intent.putExtras(args); 309 310 if (structureEnabled && AssistUtils.isDisclosureEnabled(mContext)) { 311 showDisclosure(); 312 } 313 314 try { 315 final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 316 R.anim.search_launch_enter, R.anim.search_launch_exit); 317 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 318 AsyncTask.execute(new Runnable() { 319 @Override 320 public void run() { 321 mContext.startActivityAsUser(intent, opts.toBundle(), 322 new UserHandle(UserHandle.USER_CURRENT)); 323 } 324 }); 325 } catch (ActivityNotFoundException e) { 326 Log.w(TAG, "Activity not found for " + intent.getAction()); 327 } 328 } 329 startVoiceInteractor(Bundle args)330 private void startVoiceInteractor(Bundle args) { 331 mAssistUtils.showSessionForActiveService(args, 332 VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, mShowCallback, null); 333 } 334 launchVoiceAssistFromKeyguard()335 public void launchVoiceAssistFromKeyguard() { 336 mAssistUtils.launchVoiceAssistFromKeyguard(); 337 } 338 canVoiceAssistBeLaunchedFromKeyguard()339 public boolean canVoiceAssistBeLaunchedFromKeyguard() { 340 // TODO(b/140051519) 341 return whitelistIpcs(() -> mAssistUtils.activeServiceSupportsLaunchFromKeyguard()); 342 } 343 getVoiceInteractorComponentName()344 public ComponentName getVoiceInteractorComponentName() { 345 return mAssistUtils.getActiveServiceComponentName(); 346 } 347 isVoiceSessionRunning()348 private boolean isVoiceSessionRunning() { 349 return mAssistUtils.isSessionRunning(); 350 } 351 352 @Nullable getAssistInfoForUser(int userId)353 public ComponentName getAssistInfoForUser(int userId) { 354 return mAssistUtils.getAssistComponentForUser(userId); 355 } 356 357 @Nullable getAssistInfo()358 private ComponentName getAssistInfo() { 359 return getAssistInfoForUser(KeyguardUpdateMonitor.getCurrentUser()); 360 } 361 showDisclosure()362 public void showDisclosure() { 363 mAssistDisclosure.postShow(); 364 } 365 onLockscreenShown()366 public void onLockscreenShown() { 367 AsyncTask.execute(new Runnable() { 368 @Override 369 public void run() { 370 mAssistUtils.onLockscreenShown(); 371 } 372 }); 373 } 374 375 /** Returns the logging flags for the given Assistant invocation type. */ toLoggingSubType(int invocationType)376 public int toLoggingSubType(int invocationType) { 377 return toLoggingSubType(invocationType, mPhoneStateMonitor.getPhoneState()); 378 } 379 logStartAssistLegacy(int invocationType, int phoneState)380 protected void logStartAssistLegacy(int invocationType, int phoneState) { 381 MetricsLogger.action( 382 new LogMaker(MetricsEvent.ASSISTANT) 383 .setType(MetricsEvent.TYPE_OPEN) 384 .setSubtype(toLoggingSubType(invocationType, phoneState))); 385 } 386 toLoggingSubType(int invocationType, int phoneState)387 protected final int toLoggingSubType(int invocationType, int phoneState) { 388 // Note that this logic will break if the number of Assistant invocation types exceeds 7. 389 // There are currently 5 invocation types, but we will be migrating to the new logging 390 // framework in the next update. 391 int subType = 0; 392 subType |= invocationType << 1; 393 subType |= phoneState << 4; 394 return subType; 395 } 396 } 397