Home | History | Annotate | Download | only in assist
      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          */
     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          */
     77         void onGestureCompletion(float velocity);
     78 
     79         /**
     80          * Called with the Bundle from VoiceInteractionSessionListener.onSetUiHints.
     81          */
     82         void processBundle(Bundle hints);
     83 
     84         /**
     85          * Hides the UI.
     86          */
     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 
    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 
    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 
    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 
    234     protected boolean shouldShowOrb() {
    235         return false;
    236     }
    237 
    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) */
    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      */
    280     public void onGestureCompletion(float velocity) {
    281         mUiController.onGestureCompletion(velocity);
    282     }
    283 
    284     public void hideAssist() {
    285         mAssistUtils.hideCurrentSession();
    286     }
    287 
    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 
    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 
    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 
    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 
    366     private void startVoiceInteractor(Bundle args) {
    367         mAssistUtils.showSessionForActiveService(args,
    368                 VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, mShowCallback, null);
    369     }
    370 
    371     public void launchVoiceAssistFromKeyguard() {
    372         mAssistUtils.launchVoiceAssistFromKeyguard();
    373     }
    374 
    375     public boolean canVoiceAssistBeLaunchedFromKeyguard() {
    376         return mAssistUtils.activeServiceSupportsLaunchFromKeyguard();
    377     }
    378 
    379     public ComponentName getVoiceInteractorComponentName() {
    380         return mAssistUtils.getActiveServiceComponentName();
    381     }
    382 
    383     private boolean isVoiceSessionRunning() {
    384         return mAssistUtils.isSessionRunning();
    385     }
    386 
    387     private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) {
    388         replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME,
    389                 isService);
    390     }
    391 
    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 
    425     protected AssistHandleBehaviorController getHandleBehaviorController() {
    426         return mHandleController;
    427     }
    428 
    429     @Nullable
    430     public ComponentName getAssistInfoForUser(int userId) {
    431         return mAssistUtils.getAssistComponentForUser(userId);
    432     }
    433 
    434     @Nullable
    435     private ComponentName getAssistInfo() {
    436         return getAssistInfoForUser(KeyguardUpdateMonitor.getCurrentUser());
    437     }
    438 
    439     public void showDisclosure() {
    440         mAssistDisclosure.postShow();
    441     }
    442 
    443     public void onLockscreenShown() {
    444         mAssistUtils.onLockscreenShown();
    445     }
    446 
    447     /** Returns the logging flags for the given Assistant invocation type. */
    448     public int toLoggingSubType(int invocationType) {
    449         return toLoggingSubType(invocationType, mPhoneStateMonitor.getPhoneState());
    450     }
    451 
    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