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