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.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.metrics.LogMaker;
17 import android.os.AsyncTask;
18 import android.os.Bundle;
19 import android.os.Handler;
20 import android.os.RemoteException;
21 import android.os.SystemClock;
22 import android.os.UserHandle;
23 import android.provider.Settings;
24 import android.service.voice.VoiceInteractionSession;
25 import android.util.Log;
26 
27 import com.android.internal.app.AssistUtils;
28 import com.android.internal.app.IVoiceInteractionSessionListener;
29 import com.android.internal.logging.MetricsLogger;
30 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
31 import com.android.keyguard.KeyguardUpdateMonitor;
32 import com.android.systemui.R;
33 import com.android.systemui.assist.ui.DefaultUiController;
34 import com.android.systemui.dagger.SysUISingleton;
35 import com.android.systemui.dagger.qualifiers.Main;
36 import com.android.systemui.model.SysUiState;
37 import com.android.systemui.recents.OverviewProxyService;
38 import com.android.systemui.statusbar.CommandQueue;
39 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
40 
41 import javax.inject.Inject;
42 
43 import dagger.Lazy;
44 
45 /**
46  * Class to manage everything related to assist in SystemUI.
47  */
48 @SysUISingleton
49 public class AssistManager {
50 
51     /**
52      * Controls the UI for showing Assistant invocation progress.
53      */
54     public interface UiController {
55         /**
56          * Updates the invocation progress.
57          *
58          * @param type     one of INVOCATION_TYPE_GESTURE, INVOCATION_TYPE_ACTIVE_EDGE,
59          *                 INVOCATION_TYPE_VOICE, INVOCATION_TYPE_QUICK_SEARCH_BAR,
60          *                 INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS
61          * @param progress a float between 0 and 1 inclusive. 0 represents the beginning of the
62          *                 gesture; 1 represents the end.
63          */
onInvocationProgress(int type, float progress)64         void onInvocationProgress(int type, float progress);
65 
66         /**
67          * Called when an invocation gesture completes.
68          *
69          * @param velocity the speed of the invocation gesture, in pixels per millisecond. For
70          *                 drags, this is 0.
71          */
onGestureCompletion(float velocity)72         void onGestureCompletion(float velocity);
73 
74         /**
75          * Hides any SysUI for the assistant, but _does not_ close the assistant itself.
76          */
hide()77         void hide();
78     }
79 
80     private static final String TAG = "AssistManager";
81 
82     // Note that VERBOSE logging may leak PII (e.g. transcription contents).
83     private static final boolean VERBOSE = false;
84 
85     private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms";
86     private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state";
87     protected static final String ACTION_KEY = "action";
88     protected static final String SET_ASSIST_GESTURE_CONSTRAINED_ACTION =
89             "set_assist_gesture_constrained";
90     protected static final String CONSTRAINED_KEY = "should_constrain";
91 
92     public static final String INVOCATION_TYPE_KEY = "invocation_type";
93     public static final int INVOCATION_TYPE_UNKNOWN =
94             AssistUtils.INVOCATION_TYPE_UNKNOWN;
95     public static final int INVOCATION_TYPE_GESTURE =
96             AssistUtils.INVOCATION_TYPE_GESTURE;
97     public static final int INVOCATION_TYPE_OTHER =
98             AssistUtils.INVOCATION_TYPE_PHYSICAL_GESTURE;
99     public static final int INVOCATION_TYPE_VOICE =
100             AssistUtils.INVOCATION_TYPE_VOICE;
101     public static final int INVOCATION_TYPE_QUICK_SEARCH_BAR =
102             AssistUtils.INVOCATION_TYPE_QUICK_SEARCH_BAR;
103     public static final int INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS =
104             AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
105     public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS =
106             AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS;
107 
108     public static final int DISMISS_REASON_INVOCATION_CANCELLED = 1;
109     public static final int DISMISS_REASON_TAP = 2;
110     public static final int DISMISS_REASON_BACK = 3;
111     public static final int DISMISS_REASON_TIMEOUT = 4;
112 
113     private static final long TIMEOUT_SERVICE = 2500;
114     private static final long TIMEOUT_ACTIVITY = 1000;
115 
116     protected final Context mContext;
117     private final AssistDisclosure mAssistDisclosure;
118     private final PhoneStateMonitor mPhoneStateMonitor;
119     private final UiController mUiController;
120     protected final Lazy<SysUiState> mSysUiState;
121     protected final AssistLogger mAssistLogger;
122 
123     private final DeviceProvisionedController mDeviceProvisionedController;
124     private final CommandQueue mCommandQueue;
125     protected final AssistUtils mAssistUtils;
126 
127     @Inject
AssistManager( DeviceProvisionedController controller, Context context, AssistUtils assistUtils, CommandQueue commandQueue, PhoneStateMonitor phoneStateMonitor, OverviewProxyService overviewProxyService, Lazy<SysUiState> sysUiState, DefaultUiController defaultUiController, AssistLogger assistLogger, @Main Handler uiHandler)128     public AssistManager(
129             DeviceProvisionedController controller,
130             Context context,
131             AssistUtils assistUtils,
132             CommandQueue commandQueue,
133             PhoneStateMonitor phoneStateMonitor,
134             OverviewProxyService overviewProxyService,
135             Lazy<SysUiState> sysUiState,
136             DefaultUiController defaultUiController,
137             AssistLogger assistLogger,
138             @Main Handler uiHandler) {
139         mContext = context;
140         mDeviceProvisionedController = controller;
141         mCommandQueue = commandQueue;
142         mAssistUtils = assistUtils;
143         mAssistDisclosure = new AssistDisclosure(context, uiHandler);
144         mPhoneStateMonitor = phoneStateMonitor;
145         mAssistLogger = assistLogger;
146 
147         registerVoiceInteractionSessionListener();
148 
149         mUiController = defaultUiController;
150 
151         mSysUiState = sysUiState;
152 
153         overviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
154             @Override
155             public void onAssistantProgress(float progress) {
156                 // Progress goes from 0 to 1 to indicate how close the assist gesture is to
157                 // completion.
158                 onInvocationProgress(INVOCATION_TYPE_GESTURE, progress);
159             }
160 
161             @Override
162             public void onAssistantGestureCompletion(float velocity) {
163                 onGestureCompletion(velocity);
164             }
165         });
166     }
167 
registerVoiceInteractionSessionListener()168     protected void registerVoiceInteractionSessionListener() {
169         mAssistUtils.registerVoiceInteractionSessionListener(
170                 new IVoiceInteractionSessionListener.Stub() {
171                     @Override
172                     public void onVoiceSessionShown() throws RemoteException {
173                         if (VERBOSE) {
174                             Log.v(TAG, "Voice open");
175                         }
176                         mAssistLogger.reportAssistantSessionEvent(
177                                 AssistantSessionEvent.ASSISTANT_SESSION_UPDATE);
178                     }
179 
180                     @Override
181                     public void onVoiceSessionHidden() throws RemoteException {
182                         if (VERBOSE) {
183                             Log.v(TAG, "Voice closed");
184                         }
185                         mAssistLogger.reportAssistantSessionEvent(
186                                 AssistantSessionEvent.ASSISTANT_SESSION_CLOSE);
187                     }
188 
189                     @Override
190                     public void onSetUiHints(Bundle hints) {
191                         if (VERBOSE) {
192                             Log.v(TAG, "UI hints received");
193                         }
194 
195                         String action = hints.getString(ACTION_KEY);
196                         if (SET_ASSIST_GESTURE_CONSTRAINED_ACTION.equals(action)) {
197                             mSysUiState.get()
198                                     .setFlag(
199                                             SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
200                                             hints.getBoolean(CONSTRAINED_KEY, false))
201                                     .commitUpdate(DEFAULT_DISPLAY);
202                         }
203                     }
204                 });
205     }
206 
startAssist(Bundle args)207     public void startAssist(Bundle args) {
208         final ComponentName assistComponent = getAssistInfo();
209         if (assistComponent == null) {
210             return;
211         }
212 
213         final boolean isService = assistComponent.equals(getVoiceInteractorComponentName());
214 
215         if (args == null) {
216             args = new Bundle();
217         }
218         int legacyInvocationType = args.getInt(INVOCATION_TYPE_KEY, 0);
219         int legacyDeviceState = mPhoneStateMonitor.getPhoneState();
220         args.putInt(INVOCATION_PHONE_STATE_KEY, legacyDeviceState);
221         args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.elapsedRealtime());
222         mAssistLogger.reportAssistantInvocationEventFromLegacy(
223                 legacyInvocationType,
224                 /* isInvocationComplete = */ true,
225                 assistComponent,
226                 legacyDeviceState);
227         logStartAssistLegacy(legacyInvocationType, legacyDeviceState);
228         startAssistInternal(args, assistComponent, isService);
229     }
230 
231     /** Called when the user is performing an assistant invocation action (e.g. Active Edge) */
onInvocationProgress(int type, float progress)232     public void onInvocationProgress(int type, float progress) {
233         mUiController.onInvocationProgress(type, progress);
234     }
235 
236     /**
237      * Called when the user has invoked the assistant with the incoming velocity, in pixels per
238      * millisecond. For invocations without a velocity (e.g. slow drag), the velocity is set to
239      * zero.
240      */
onGestureCompletion(float velocity)241     public void onGestureCompletion(float velocity) {
242         mUiController.onGestureCompletion(velocity);
243     }
244 
hideAssist()245     public void hideAssist() {
246         mAssistUtils.hideCurrentSession();
247     }
248 
startAssistInternal(Bundle args, @NonNull ComponentName assistComponent, boolean isService)249     private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
250             boolean isService) {
251         if (isService) {
252             startVoiceInteractor(args);
253         } else {
254             startAssistActivity(args, assistComponent);
255         }
256     }
257 
startAssistActivity(Bundle args, @NonNull ComponentName assistComponent)258     private void startAssistActivity(Bundle args, @NonNull ComponentName assistComponent) {
259         if (!mDeviceProvisionedController.isDeviceProvisioned()) {
260             return;
261         }
262 
263         // Close Recent Apps if needed
264         mCommandQueue.animateCollapsePanels(
265                 CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
266                 false /* force */);
267 
268         boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
269                 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
270 
271         final SearchManager searchManager =
272                 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
273         if (searchManager == null) {
274             return;
275         }
276         final Intent intent = searchManager.getAssistIntent(structureEnabled);
277         if (intent == null) {
278             return;
279         }
280         intent.setComponent(assistComponent);
281         intent.putExtras(args);
282 
283         if (structureEnabled && AssistUtils.isDisclosureEnabled(mContext)) {
284             showDisclosure();
285         }
286 
287         try {
288             final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
289                     R.anim.search_launch_enter, R.anim.search_launch_exit);
290             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
291             AsyncTask.execute(new Runnable() {
292                 @Override
293                 public void run() {
294                     mContext.startActivityAsUser(intent, opts.toBundle(),
295                             new UserHandle(UserHandle.USER_CURRENT));
296                 }
297             });
298         } catch (ActivityNotFoundException e) {
299             Log.w(TAG, "Activity not found for " + intent.getAction());
300         }
301     }
302 
startVoiceInteractor(Bundle args)303     private void startVoiceInteractor(Bundle args) {
304         mAssistUtils.showSessionForActiveService(args,
305                 VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, null, null);
306     }
307 
launchVoiceAssistFromKeyguard()308     public void launchVoiceAssistFromKeyguard() {
309         mAssistUtils.launchVoiceAssistFromKeyguard();
310     }
311 
canVoiceAssistBeLaunchedFromKeyguard()312     public boolean canVoiceAssistBeLaunchedFromKeyguard() {
313         // TODO(b/140051519)
314         return whitelistIpcs(() -> mAssistUtils.activeServiceSupportsLaunchFromKeyguard());
315     }
316 
getVoiceInteractorComponentName()317     public ComponentName getVoiceInteractorComponentName() {
318         return mAssistUtils.getActiveServiceComponentName();
319     }
320 
isVoiceSessionRunning()321     private boolean isVoiceSessionRunning() {
322         return mAssistUtils.isSessionRunning();
323     }
324 
325     @Nullable
getAssistInfoForUser(int userId)326     public ComponentName getAssistInfoForUser(int userId) {
327         return mAssistUtils.getAssistComponentForUser(userId);
328     }
329 
330     @Nullable
getAssistInfo()331     private ComponentName getAssistInfo() {
332         return getAssistInfoForUser(KeyguardUpdateMonitor.getCurrentUser());
333     }
334 
showDisclosure()335     public void showDisclosure() {
336         mAssistDisclosure.postShow();
337     }
338 
onLockscreenShown()339     public void onLockscreenShown() {
340         AsyncTask.execute(new Runnable() {
341             @Override
342             public void run() {
343                 mAssistUtils.onLockscreenShown();
344             }
345         });
346     }
347 
348     /** Returns the logging flags for the given Assistant invocation type. */
toLoggingSubType(int invocationType)349     public int toLoggingSubType(int invocationType) {
350         return toLoggingSubType(invocationType, mPhoneStateMonitor.getPhoneState());
351     }
352 
logStartAssistLegacy(int invocationType, int phoneState)353     protected void logStartAssistLegacy(int invocationType, int phoneState) {
354         MetricsLogger.action(
355                 new LogMaker(MetricsEvent.ASSISTANT)
356                         .setType(MetricsEvent.TYPE_OPEN)
357                         .setSubtype(toLoggingSubType(invocationType, phoneState)));
358     }
359 
toLoggingSubType(int invocationType, int phoneState)360     protected final int toLoggingSubType(int invocationType, int phoneState) {
361         // Note that this logic will break if the number of Assistant invocation types exceeds 7.
362         // There are currently 5 invocation types, but we will be migrating to the new logging
363         // framework in the next update.
364         int subType = 0;
365         subType |= invocationType << 1;
366         subType |= phoneState << 4;
367         return subType;
368     }
369 }
370