• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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