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