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