1 package com.android.systemui.assist;
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;
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;
52 * Class to manage everything related to assist in SystemUI.
54 public class AssistManager implements ConfigurationChangedReceiver {
57 * Controls the UI for showing Assistant invocation progress.
59 public interface UiController {
61 * Updates the invocation progress.
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.
69 void onInvocationProgress(int type, float progress);
72 * Called when an invocation gesture completes.
74 * @param velocity the speed of the invocation gesture, in pixels per millisecond. For
77 void onGestureCompletion(float velocity);
80 * Called with the Bundle from VoiceInteractionSessionListener.onSetUiHints.
82 void processBundle(Bundle hints);
90 private static final String TAG = "AssistManager";
92 // Note that VERBOSE logging may leak PII (e.g. transcription contents).
93 private static final boolean VERBOSE = false;
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";
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;
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;
112 private static final long TIMEOUT_SERVICE = 2500;
113 private static final long TIMEOUT_ACTIVITY = 1000;
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;
123 private AssistOrbContainer mView;
124 private final DeviceProvisionedController mDeviceProvisionedController;
125 protected final AssistUtils mAssistUtils;
126 private final boolean mShouldEnableOrb;
128 private IVoiceInteractionSessionShowCallback mShowCallback =
129 new IVoiceInteractionSessionShowCallback.Stub() {
132 public void onFailed() throws RemoteException {
133 mView.post(mHideRunnable);
137 public void onShown() throws RemoteException {
138 mView.post(mHideRunnable);
142 private Runnable mHideRunnable = new Runnable() {
145 mView.removeCallbacks(this);
146 mView.show(false /* show */, true /* animate */);
150 public AssistManager(DeviceProvisionedController controller, Context 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);
158 new AssistHandleBehaviorController(context, mAssistUtils, new Handler());
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();
167 mUiController = new DefaultUiController(mContext);
169 OverviewProxyService overviewProxy = Dependency.get(OverviewProxyService.class);
170 overviewProxy.addCallback(new OverviewProxyService.OverviewProxyListener() {
172 public void onAssistantProgress(float progress) {
173 // Progress goes from 0 to 1 to indicate how close the assist gesture is to
175 onInvocationProgress(INVOCATION_TYPE_GESTURE, progress);
179 public void onAssistantGestureCompletion(float velocity) {
180 onGestureCompletion(velocity);
185 protected void registerVoiceInteractionSessionListener() {
186 mAssistUtils.registerVoiceInteractionSessionListener(
187 new IVoiceInteractionSessionListener.Stub() {
189 public void onVoiceSessionShown() throws RemoteException {
191 Log.v(TAG, "Voice open");
196 public void onVoiceSessionHidden() throws RemoteException {
198 Log.v(TAG, "Voice closed");
203 public void onSetUiHints(Bundle hints) {
205 Log.v(TAG, "UI hints received");
211 public void onConfigurationChanged(Configuration newConfiguration) {
212 if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
215 boolean visible = false;
217 visible = mView.isShowing();
218 mWindowManager.removeView(mView);
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);
230 mView.show(true /* show */, false /* animate */);
234 protected boolean shouldShowOrb() {
238 public void startAssist(Bundle args) {
239 final ComponentName assistComponent = getAssistInfo();
240 if (assistComponent == null) {
244 final boolean isService = assistComponent.equals(getVoiceInteractorComponentName());
245 if (!isService || (!isVoiceSessionRunning() && shouldShowOrb())) {
246 showOrb(assistComponent, isService);
247 mView.postDelayed(mHideRunnable, isService
255 int invocationType = args.getInt(INVOCATION_TYPE_KEY, 0);
256 if (invocationType == INVOCATION_TYPE_GESTURE) {
257 mHandleController.onAssistantGesturePerformed();
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);
270 /** Called when the user is performing an assistant invocation action (e.g. Active Edge) */
271 public void onInvocationProgress(int type, float progress) {
272 mUiController.onInvocationProgress(type, progress);
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
280 public void onGestureCompletion(float velocity) {
281 mUiController.onGestureCompletion(velocity);
284 public void hideAssist() {
285 mAssistUtils.hideCurrentSession();
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;
305 private void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
306 maybeSwapSearchIcon(assistComponent, isService);
307 if (mShouldEnableOrb) {
308 mView.show(true /* show */, true /* animate */);
312 private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
315 startVoiceInteractor(args);
317 startAssistActivity(args, assistComponent);
321 private void startAssistActivity(Bundle args, @NonNull ComponentName assistComponent) {
322 if (!mDeviceProvisionedController.isDeviceProvisioned()) {
326 // Close Recent Apps if needed
327 SysUiServiceProvider.getComponent(mContext, CommandQueue.class).animateCollapsePanels(
328 CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
331 boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
332 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
334 final SearchManager searchManager =
335 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
336 if (searchManager == null) {
339 final Intent intent = searchManager.getAssistIntent(structureEnabled);
340 if (intent == null) {
343 intent.setComponent(assistComponent);
344 intent.putExtras(args);
346 if (structureEnabled) {
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() {
357 mContext.startActivityAsUser(intent, opts.toBundle(),
358 new UserHandle(UserHandle.USER_CURRENT));
361 } catch (ActivityNotFoundException e) {
362 Log.w(TAG, "Activity not found for " + intent.getAction());
366 private void startVoiceInteractor(Bundle args) {
367 mAssistUtils.showSessionForActiveService(args,
368 VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, mShowCallback, null);
371 public void launchVoiceAssistFromKeyguard() {
372 mAssistUtils.launchVoiceAssistFromKeyguard();
375 public boolean canVoiceAssistBeLaunchedFromKeyguard() {
376 return mAssistUtils.activeServiceSupportsLaunchFromKeyguard();
379 public ComponentName getVoiceInteractorComponentName() {
380 return mAssistUtils.getActiveServiceComponentName();
383 private boolean isVoiceSessionRunning() {
384 return mAssistUtils.isSessionRunning();
387 private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) {
388 replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME,
392 public void replaceDrawable(ImageView v, ComponentName component, String name,
394 if (component != null) {
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));
412 } catch (PackageManager.NameNotFoundException e) {
414 Log.v(TAG, "Assistant component "
415 + component.flattenToShortString() + " not found");
417 } catch (Resources.NotFoundException nfe) {
418 Log.w(TAG, "Failed to swap drawable from "
419 + component.flattenToShortString(), nfe);
422 v.setImageDrawable(null);
425 protected AssistHandleBehaviorController getHandleBehaviorController() {
426 return mHandleController;
430 public ComponentName getAssistInfoForUser(int userId) {
431 return mAssistUtils.getAssistComponentForUser(userId);
435 private ComponentName getAssistInfo() {
436 return getAssistInfoForUser(KeyguardUpdateMonitor.getCurrentUser());
439 public void showDisclosure() {
440 mAssistDisclosure.postShow();
443 public void onLockscreenShown() {
444 mAssistUtils.onLockscreenShown();
447 /** Returns the logging flags for the given Assistant invocation type. */
448 public int toLoggingSubType(int invocationType) {
449 return toLoggingSubType(invocationType, mPhoneStateMonitor.getPhoneState());
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;