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.content.pm.ActivityInfo; 18 import android.content.pm.PackageManager; 19 import android.content.res.Configuration; 20 import android.content.res.Resources; 21 import android.graphics.PixelFormat; 22 import android.metrics.LogMaker; 23 import android.os.AsyncTask; 24 import android.os.Binder; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.RemoteException; 28 import android.os.SystemClock; 29 import android.os.UserHandle; 30 import android.provider.Settings; 31 import android.service.voice.VoiceInteractionSession; 32 import android.util.Log; 33 import android.view.Gravity; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.view.WindowManager; 38 import android.widget.ImageView; 39 40 import com.android.internal.app.AssistUtils; 41 import com.android.internal.app.IVoiceInteractionSessionListener; 42 import com.android.internal.app.IVoiceInteractionSessionShowCallback; 43 import com.android.internal.logging.MetricsLogger; 44 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 45 import com.android.keyguard.KeyguardUpdateMonitor; 46 import com.android.settingslib.applications.InterestingConfigChanges; 47 import com.android.systemui.R; 48 import com.android.systemui.assist.ui.DefaultUiController; 49 import com.android.systemui.model.SysUiState; 50 import com.android.systemui.recents.OverviewProxyService; 51 import com.android.systemui.statusbar.CommandQueue; 52 import com.android.systemui.statusbar.policy.ConfigurationController; 53 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 54 55 import javax.inject.Inject; 56 import javax.inject.Singleton; 57 58 import dagger.Lazy; 59 60 /** 61 * Class to manage everything related to assist in SystemUI. 62 */ 63 @Singleton 64 public class AssistManager { 65 66 /** 67 * Controls the UI for showing Assistant invocation progress. 68 */ 69 public interface UiController { 70 /** 71 * Updates the invocation progress. 72 * 73 * @param type one of INVOCATION_TYPE_GESTURE, INVOCATION_TYPE_ACTIVE_EDGE, 74 * INVOCATION_TYPE_VOICE, INVOCATION_TYPE_QUICK_SEARCH_BAR, 75 * INVOCATION_HOME_BUTTON_LONG_PRESS 76 * @param progress a float between 0 and 1 inclusive. 0 represents the beginning of the 77 * gesture; 1 represents the end. 78 */ onInvocationProgress(int type, float progress)79 void onInvocationProgress(int type, float progress); 80 81 /** 82 * Called when an invocation gesture completes. 83 * 84 * @param velocity the speed of the invocation gesture, in pixels per millisecond. For 85 * drags, this is 0. 86 */ onGestureCompletion(float velocity)87 void onGestureCompletion(float velocity); 88 89 /** 90 * Hides any SysUI for the assistant, but _does not_ close the assistant itself. 91 */ hide()92 void hide(); 93 } 94 95 private static final String TAG = "AssistManager"; 96 97 // Note that VERBOSE logging may leak PII (e.g. transcription contents). 98 private static final boolean VERBOSE = false; 99 100 private static final String ASSIST_ICON_METADATA_NAME = 101 "com.android.systemui.action_assist_icon"; 102 private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms"; 103 private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state"; 104 public static final String INVOCATION_TYPE_KEY = "invocation_type"; 105 protected static final String ACTION_KEY = "action"; 106 protected static final String SHOW_ASSIST_HANDLES_ACTION = "show_assist_handles"; 107 protected static final String SET_ASSIST_GESTURE_CONSTRAINED_ACTION = 108 "set_assist_gesture_constrained"; 109 protected static final String CONSTRAINED_KEY = "should_constrain"; 110 111 public static final int INVOCATION_TYPE_GESTURE = 1; 112 public static final int INVOCATION_TYPE_OTHER = 2; 113 public static final int INVOCATION_TYPE_VOICE = 3; 114 public static final int INVOCATION_TYPE_QUICK_SEARCH_BAR = 4; 115 public static final int INVOCATION_HOME_BUTTON_LONG_PRESS = 5; 116 117 public static final int DISMISS_REASON_INVOCATION_CANCELLED = 1; 118 public static final int DISMISS_REASON_TAP = 2; 119 public static final int DISMISS_REASON_BACK = 3; 120 public static final int DISMISS_REASON_TIMEOUT = 4; 121 122 private static final long TIMEOUT_SERVICE = 2500; 123 private static final long TIMEOUT_ACTIVITY = 1000; 124 125 protected final Context mContext; 126 private final WindowManager mWindowManager; 127 private final AssistDisclosure mAssistDisclosure; 128 private final InterestingConfigChanges mInterestingConfigChanges; 129 private final PhoneStateMonitor mPhoneStateMonitor; 130 private final AssistHandleBehaviorController mHandleController; 131 private final UiController mUiController; 132 protected final Lazy<SysUiState> mSysUiState; 133 protected final AssistLogger mAssistLogger; 134 135 private AssistOrbContainer mView; 136 private final DeviceProvisionedController mDeviceProvisionedController; 137 private final CommandQueue mCommandQueue; 138 protected final AssistUtils mAssistUtils; 139 private final boolean mShouldEnableOrb; 140 141 private IVoiceInteractionSessionShowCallback mShowCallback = 142 new IVoiceInteractionSessionShowCallback.Stub() { 143 144 @Override 145 public void onFailed() throws RemoteException { 146 mView.post(mHideRunnable); 147 } 148 149 @Override 150 public void onShown() throws RemoteException { 151 mView.post(mHideRunnable); 152 } 153 }; 154 155 private Runnable mHideRunnable = new Runnable() { 156 @Override 157 public void run() { 158 mView.removeCallbacks(this); 159 mView.show(false /* show */, true /* animate */); 160 } 161 }; 162 163 private ConfigurationController.ConfigurationListener mConfigurationListener = 164 new ConfigurationController.ConfigurationListener() { 165 @Override 166 public void onConfigChanged(Configuration newConfig) { 167 if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { 168 return; 169 } 170 boolean visible = false; 171 if (mView != null) { 172 visible = mView.isShowing(); 173 mWindowManager.removeView(mView); 174 } 175 176 mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate( 177 R.layout.assist_orb, null); 178 mView.setVisibility(View.GONE); 179 mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 180 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE 181 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 182 WindowManager.LayoutParams lp = getLayoutParams(); 183 mWindowManager.addView(mView, lp); 184 if (visible) { 185 mView.show(true /* show */, false /* animate */); 186 } 187 } 188 }; 189 190 @Inject AssistManager( DeviceProvisionedController controller, Context context, AssistUtils assistUtils, AssistHandleBehaviorController handleController, CommandQueue commandQueue, PhoneStateMonitor phoneStateMonitor, OverviewProxyService overviewProxyService, ConfigurationController configurationController, Lazy<SysUiState> sysUiState, DefaultUiController defaultUiController, AssistLogger assistLogger)191 public AssistManager( 192 DeviceProvisionedController controller, 193 Context context, 194 AssistUtils assistUtils, 195 AssistHandleBehaviorController handleController, 196 CommandQueue commandQueue, 197 PhoneStateMonitor phoneStateMonitor, 198 OverviewProxyService overviewProxyService, 199 ConfigurationController configurationController, 200 Lazy<SysUiState> sysUiState, 201 DefaultUiController defaultUiController, 202 AssistLogger assistLogger) { 203 mContext = context; 204 mDeviceProvisionedController = controller; 205 mCommandQueue = commandQueue; 206 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 207 mAssistUtils = assistUtils; 208 mAssistDisclosure = new AssistDisclosure(context, new Handler()); 209 mPhoneStateMonitor = phoneStateMonitor; 210 mHandleController = handleController; 211 mAssistLogger = assistLogger; 212 213 configurationController.addCallback(mConfigurationListener); 214 215 registerVoiceInteractionSessionListener(); 216 mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION 217 | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE 218 | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS); 219 mConfigurationListener.onConfigChanged(context.getResources().getConfiguration()); 220 mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic(); 221 222 mUiController = defaultUiController; 223 224 mSysUiState = sysUiState; 225 226 overviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() { 227 @Override 228 public void onAssistantProgress(float progress) { 229 // Progress goes from 0 to 1 to indicate how close the assist gesture is to 230 // completion. 231 onInvocationProgress(INVOCATION_TYPE_GESTURE, progress); 232 } 233 234 @Override 235 public void onAssistantGestureCompletion(float velocity) { 236 onGestureCompletion(velocity); 237 } 238 }); 239 } 240 registerVoiceInteractionSessionListener()241 protected void registerVoiceInteractionSessionListener() { 242 mAssistUtils.registerVoiceInteractionSessionListener( 243 new IVoiceInteractionSessionListener.Stub() { 244 @Override 245 public void onVoiceSessionShown() throws RemoteException { 246 if (VERBOSE) { 247 Log.v(TAG, "Voice open"); 248 } 249 mAssistLogger.reportAssistantSessionEvent( 250 AssistantSessionEvent.ASSISTANT_SESSION_UPDATE); 251 } 252 253 @Override 254 public void onVoiceSessionHidden() throws RemoteException { 255 if (VERBOSE) { 256 Log.v(TAG, "Voice closed"); 257 } 258 mAssistLogger.reportAssistantSessionEvent( 259 AssistantSessionEvent.ASSISTANT_SESSION_CLOSE); 260 } 261 262 @Override 263 public void onSetUiHints(Bundle hints) { 264 if (VERBOSE) { 265 Log.v(TAG, "UI hints received"); 266 } 267 268 String action = hints.getString(ACTION_KEY); 269 if (SHOW_ASSIST_HANDLES_ACTION.equals(action)) { 270 requestAssistHandles(); 271 } else if (SET_ASSIST_GESTURE_CONSTRAINED_ACTION.equals(action)) { 272 mSysUiState.get() 273 .setFlag( 274 SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED, 275 hints.getBoolean(CONSTRAINED_KEY, false)) 276 .commitUpdate(DEFAULT_DISPLAY); 277 } 278 } 279 }); 280 } 281 shouldShowOrb()282 protected boolean shouldShowOrb() { 283 return false; 284 } 285 startAssist(Bundle args)286 public void startAssist(Bundle args) { 287 final ComponentName assistComponent = getAssistInfo(); 288 if (assistComponent == null) { 289 return; 290 } 291 292 final boolean isService = assistComponent.equals(getVoiceInteractorComponentName()); 293 if (!isService || (!isVoiceSessionRunning() && shouldShowOrb())) { 294 showOrb(assistComponent, isService); 295 mView.postDelayed(mHideRunnable, isService 296 ? TIMEOUT_SERVICE 297 : TIMEOUT_ACTIVITY); 298 } 299 300 if (args == null) { 301 args = new Bundle(); 302 } 303 int legacyInvocationType = args.getInt(INVOCATION_TYPE_KEY, 0); 304 if (legacyInvocationType == INVOCATION_TYPE_GESTURE) { 305 mHandleController.onAssistantGesturePerformed(); 306 } 307 int legacyDeviceState = mPhoneStateMonitor.getPhoneState(); 308 args.putInt(INVOCATION_PHONE_STATE_KEY, legacyDeviceState); 309 args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.elapsedRealtime()); 310 mAssistLogger.reportAssistantInvocationEventFromLegacy( 311 legacyInvocationType, 312 /* isInvocationComplete = */ true, 313 assistComponent, 314 legacyDeviceState); 315 logStartAssistLegacy(legacyInvocationType, legacyDeviceState); 316 startAssistInternal(args, assistComponent, isService); 317 } 318 319 /** Called when the user is performing an assistant invocation action (e.g. Active Edge) */ onInvocationProgress(int type, float progress)320 public void onInvocationProgress(int type, float progress) { 321 mUiController.onInvocationProgress(type, progress); 322 } 323 324 /** 325 * Called when the user has invoked the assistant with the incoming velocity, in pixels per 326 * millisecond. For invocations without a velocity (e.g. slow drag), the velocity is set to 327 * zero. 328 */ onGestureCompletion(float velocity)329 public void onGestureCompletion(float velocity) { 330 mUiController.onGestureCompletion(velocity); 331 } 332 requestAssistHandles()333 protected void requestAssistHandles() { 334 mHandleController.onAssistHandlesRequested(); 335 } 336 hideAssist()337 public void hideAssist() { 338 mAssistUtils.hideCurrentSession(); 339 } 340 getLayoutParams()341 private WindowManager.LayoutParams getLayoutParams() { 342 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 343 ViewGroup.LayoutParams.MATCH_PARENT, 344 mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height), 345 WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING, 346 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 347 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 348 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 349 PixelFormat.TRANSLUCENT); 350 lp.token = new Binder(); 351 lp.gravity = Gravity.BOTTOM | Gravity.START; 352 lp.setTitle("AssistPreviewPanel"); 353 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 354 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 355 return lp; 356 } 357 showOrb(@onNull ComponentName assistComponent, boolean isService)358 private void showOrb(@NonNull ComponentName assistComponent, boolean isService) { 359 maybeSwapSearchIcon(assistComponent, isService); 360 if (mShouldEnableOrb) { 361 mView.show(true /* show */, true /* animate */); 362 } 363 } 364 startAssistInternal(Bundle args, @NonNull ComponentName assistComponent, boolean isService)365 private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent, 366 boolean isService) { 367 if (isService) { 368 startVoiceInteractor(args); 369 } else { 370 startAssistActivity(args, assistComponent); 371 } 372 } 373 startAssistActivity(Bundle args, @NonNull ComponentName assistComponent)374 private void startAssistActivity(Bundle args, @NonNull ComponentName assistComponent) { 375 if (!mDeviceProvisionedController.isDeviceProvisioned()) { 376 return; 377 } 378 379 // Close Recent Apps if needed 380 mCommandQueue.animateCollapsePanels( 381 CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, 382 false /* force */); 383 384 boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(), 385 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0; 386 387 final SearchManager searchManager = 388 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); 389 if (searchManager == null) { 390 return; 391 } 392 final Intent intent = searchManager.getAssistIntent(structureEnabled); 393 if (intent == null) { 394 return; 395 } 396 intent.setComponent(assistComponent); 397 intent.putExtras(args); 398 399 if (structureEnabled && AssistUtils.isDisclosureEnabled(mContext)) { 400 showDisclosure(); 401 } 402 403 try { 404 final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 405 R.anim.search_launch_enter, R.anim.search_launch_exit); 406 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 407 AsyncTask.execute(new Runnable() { 408 @Override 409 public void run() { 410 mContext.startActivityAsUser(intent, opts.toBundle(), 411 new UserHandle(UserHandle.USER_CURRENT)); 412 } 413 }); 414 } catch (ActivityNotFoundException e) { 415 Log.w(TAG, "Activity not found for " + intent.getAction()); 416 } 417 } 418 startVoiceInteractor(Bundle args)419 private void startVoiceInteractor(Bundle args) { 420 mAssistUtils.showSessionForActiveService(args, 421 VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, mShowCallback, null); 422 } 423 launchVoiceAssistFromKeyguard()424 public void launchVoiceAssistFromKeyguard() { 425 mAssistUtils.launchVoiceAssistFromKeyguard(); 426 } 427 canVoiceAssistBeLaunchedFromKeyguard()428 public boolean canVoiceAssistBeLaunchedFromKeyguard() { 429 // TODO(b/140051519) 430 return whitelistIpcs(() -> mAssistUtils.activeServiceSupportsLaunchFromKeyguard()); 431 } 432 getVoiceInteractorComponentName()433 public ComponentName getVoiceInteractorComponentName() { 434 return mAssistUtils.getActiveServiceComponentName(); 435 } 436 isVoiceSessionRunning()437 private boolean isVoiceSessionRunning() { 438 return mAssistUtils.isSessionRunning(); 439 } 440 maybeSwapSearchIcon(@onNull ComponentName assistComponent, boolean isService)441 private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) { 442 replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME, 443 isService); 444 } 445 replaceDrawable(ImageView v, ComponentName component, String name, boolean isService)446 public void replaceDrawable(ImageView v, ComponentName component, String name, 447 boolean isService) { 448 if (component != null) { 449 try { 450 PackageManager packageManager = mContext.getPackageManager(); 451 // Look for the search icon specified in the activity meta-data 452 Bundle metaData = isService 453 ? packageManager.getServiceInfo( 454 component, PackageManager.GET_META_DATA).metaData 455 : packageManager.getActivityInfo( 456 component, PackageManager.GET_META_DATA).metaData; 457 if (metaData != null) { 458 int iconResId = metaData.getInt(name); 459 if (iconResId != 0) { 460 Resources res = packageManager.getResourcesForApplication( 461 component.getPackageName()); 462 v.setImageDrawable(res.getDrawable(iconResId)); 463 return; 464 } 465 } 466 } catch (PackageManager.NameNotFoundException e) { 467 if (VERBOSE) { 468 Log.v(TAG, "Assistant component " 469 + component.flattenToShortString() + " not found"); 470 } 471 } catch (Resources.NotFoundException nfe) { 472 Log.w(TAG, "Failed to swap drawable from " 473 + component.flattenToShortString(), nfe); 474 } 475 } 476 v.setImageDrawable(null); 477 } 478 getHandleBehaviorController()479 protected AssistHandleBehaviorController getHandleBehaviorController() { 480 return mHandleController; 481 } 482 483 @Nullable getAssistInfoForUser(int userId)484 public ComponentName getAssistInfoForUser(int userId) { 485 return mAssistUtils.getAssistComponentForUser(userId); 486 } 487 488 @Nullable getAssistInfo()489 private ComponentName getAssistInfo() { 490 return getAssistInfoForUser(KeyguardUpdateMonitor.getCurrentUser()); 491 } 492 showDisclosure()493 public void showDisclosure() { 494 mAssistDisclosure.postShow(); 495 } 496 onLockscreenShown()497 public void onLockscreenShown() { 498 AsyncTask.execute(new Runnable() { 499 @Override 500 public void run() { 501 mAssistUtils.onLockscreenShown(); 502 } 503 }); 504 } 505 getAssistHandleShowAndGoRemainingDurationMs()506 public long getAssistHandleShowAndGoRemainingDurationMs() { 507 return mHandleController.getShowAndGoRemainingTimeMs(); 508 } 509 510 /** Returns the logging flags for the given Assistant invocation type. */ toLoggingSubType(int invocationType)511 public int toLoggingSubType(int invocationType) { 512 return toLoggingSubType(invocationType, mPhoneStateMonitor.getPhoneState()); 513 } 514 logStartAssistLegacy(int invocationType, int phoneState)515 protected void logStartAssistLegacy(int invocationType, int phoneState) { 516 MetricsLogger.action( 517 new LogMaker(MetricsEvent.ASSISTANT) 518 .setType(MetricsEvent.TYPE_OPEN) 519 .setSubtype(toLoggingSubType(invocationType, phoneState))); 520 } 521 toLoggingSubType(int invocationType, int phoneState)522 protected final int toLoggingSubType(int invocationType, int phoneState) { 523 // Note that this logic will break if the number of Assistant invocation types exceeds 7. 524 // There are currently 5 invocation types, but we will be migrating to the new logging 525 // framework in the next update. 526 int subType = mHandleController.areHandlesShowing() ? 0 : 1; 527 subType |= invocationType << 1; 528 subType |= phoneState << 4; 529 return subType; 530 } 531 } 532