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.PackageManager;
     13 import android.content.res.Resources;
     14 import android.graphics.PixelFormat;
     15 import android.os.AsyncTask;
     16 import android.os.Bundle;
     17 import android.os.Handler;
     18 import android.os.IBinder;
     19 import android.os.RemoteException;
     20 import android.os.UserHandle;
     21 import android.provider.Settings;
     22 import android.service.voice.VoiceInteractionSession;
     23 import android.util.Log;
     24 import android.view.Gravity;
     25 import android.view.LayoutInflater;
     26 import android.view.View;
     27 import android.view.ViewGroup;
     28 import android.view.WindowManager;
     29 import android.widget.ImageView;
     30 
     31 import com.android.internal.app.AssistUtils;
     32 import com.android.internal.app.IVoiceInteractionSessionListener;
     33 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
     34 import com.android.systemui.R;
     35 import com.android.systemui.statusbar.BaseStatusBar;
     36 import com.android.systemui.statusbar.CommandQueue;
     37 
     38 /**
     39  * Class to manage everything related to assist in SystemUI.
     40  */
     41 public class AssistManager {
     42 
     43     private static final String TAG = "AssistManager";
     44     private static final String ASSIST_ICON_METADATA_NAME =
     45             "com.android.systemui.action_assist_icon";
     46 
     47     private static final long TIMEOUT_SERVICE = 2500;
     48     private static final long TIMEOUT_ACTIVITY = 1000;
     49 
     50     protected final Context mContext;
     51     private final WindowManager mWindowManager;
     52     private final AssistDisclosure mAssistDisclosure;
     53 
     54     private AssistOrbContainer mView;
     55     private final BaseStatusBar mBar;
     56     protected final AssistUtils mAssistUtils;
     57 
     58     private IVoiceInteractionSessionShowCallback mShowCallback =
     59             new IVoiceInteractionSessionShowCallback.Stub() {
     60 
     61         @Override
     62         public void onFailed() throws RemoteException {
     63             mView.post(mHideRunnable);
     64         }
     65 
     66         @Override
     67         public void onShown() throws RemoteException {
     68             mView.post(mHideRunnable);
     69         }
     70     };
     71 
     72     private Runnable mHideRunnable = new Runnable() {
     73         @Override
     74         public void run() {
     75             mView.removeCallbacks(this);
     76             mView.show(false /* show */, true /* animate */);
     77         }
     78     };
     79 
     80     public AssistManager(BaseStatusBar bar, Context context) {
     81         mContext = context;
     82         mBar = bar;
     83         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
     84         mAssistUtils = new AssistUtils(context);
     85         mAssistDisclosure = new AssistDisclosure(context, new Handler());
     86 
     87         registerVoiceInteractionSessionListener();
     88     }
     89 
     90     protected void registerVoiceInteractionSessionListener() {
     91         mAssistUtils.registerVoiceInteractionSessionListener(
     92                 new IVoiceInteractionSessionListener.Stub() {
     93             @Override
     94             public void onVoiceSessionShown() throws RemoteException {
     95                 Log.v(TAG, "Voice open");
     96             }
     97 
     98             @Override
     99             public void onVoiceSessionHidden() throws RemoteException {
    100                 Log.v(TAG, "Voice closed");
    101             }
    102         });
    103     }
    104 
    105     public void onConfigurationChanged() {
    106         boolean visible = false;
    107         if (mView != null) {
    108             visible = mView.isShowing();
    109             mWindowManager.removeView(mView);
    110         }
    111 
    112         mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
    113                 R.layout.assist_orb, null);
    114         mView.setVisibility(View.GONE);
    115         mView.setSystemUiVisibility(
    116                 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    117                         | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
    118         WindowManager.LayoutParams lp = getLayoutParams();
    119         mWindowManager.addView(mView, lp);
    120         if (visible) {
    121             mView.show(true /* show */, false /* animate */);
    122         }
    123     }
    124 
    125     protected boolean shouldShowOrb() {
    126         return true;
    127     }
    128 
    129     public void startAssist(Bundle args) {
    130         final ComponentName assistComponent = getAssistInfo();
    131         if (assistComponent == null) {
    132             return;
    133         }
    134 
    135         final boolean isService = assistComponent.equals(getVoiceInteractorComponentName());
    136         if (!isService || (!isVoiceSessionRunning() && shouldShowOrb())) {
    137             showOrb(assistComponent, isService);
    138             mView.postDelayed(mHideRunnable, isService
    139                     ? TIMEOUT_SERVICE
    140                     : TIMEOUT_ACTIVITY);
    141         }
    142         startAssistInternal(args, assistComponent, isService);
    143     }
    144 
    145     public void hideAssist() {
    146         mAssistUtils.hideCurrentSession();
    147     }
    148 
    149     private WindowManager.LayoutParams getLayoutParams() {
    150         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
    151                 ViewGroup.LayoutParams.MATCH_PARENT,
    152                 mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
    153                 WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
    154                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    155                         | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
    156                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
    157                 PixelFormat.TRANSLUCENT);
    158         if (ActivityManager.isHighEndGfx()) {
    159             lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
    160         }
    161         lp.gravity = Gravity.BOTTOM | Gravity.START;
    162         lp.setTitle("AssistPreviewPanel");
    163         lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
    164                 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
    165         return lp;
    166     }
    167 
    168     private void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
    169         maybeSwapSearchIcon(assistComponent, isService);
    170         mView.show(true /* show */, true /* animate */);
    171     }
    172 
    173     private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
    174             boolean isService) {
    175         if (isService) {
    176             startVoiceInteractor(args);
    177         } else {
    178             startAssistActivity(args, assistComponent);
    179         }
    180     }
    181 
    182     private void startAssistActivity(Bundle args, @NonNull ComponentName assistComponent) {
    183         if (!mBar.isDeviceProvisioned()) {
    184             return;
    185         }
    186 
    187         // Close Recent Apps if needed
    188         mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL |
    189                 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
    190 
    191         boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
    192                 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
    193 
    194         final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
    195                 .getAssistIntent(structureEnabled);
    196         if (intent == null) {
    197             return;
    198         }
    199         intent.setComponent(assistComponent);
    200         intent.putExtras(args);
    201 
    202         if (structureEnabled) {
    203             showDisclosure();
    204         }
    205 
    206         try {
    207             final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
    208                     R.anim.search_launch_enter, R.anim.search_launch_exit);
    209             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    210             AsyncTask.execute(new Runnable() {
    211                 @Override
    212                 public void run() {
    213                     mContext.startActivityAsUser(intent, opts.toBundle(),
    214                             new UserHandle(UserHandle.USER_CURRENT));
    215                 }
    216             });
    217         } catch (ActivityNotFoundException e) {
    218             Log.w(TAG, "Activity not found for " + intent.getAction());
    219         }
    220     }
    221 
    222     private void startVoiceInteractor(Bundle args) {
    223         mAssistUtils.showSessionForActiveService(args,
    224                 VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, mShowCallback, null);
    225     }
    226 
    227     public void launchVoiceAssistFromKeyguard() {
    228         mAssistUtils.launchVoiceAssistFromKeyguard();
    229     }
    230 
    231     public boolean canVoiceAssistBeLaunchedFromKeyguard() {
    232         return mAssistUtils.activeServiceSupportsLaunchFromKeyguard();
    233     }
    234 
    235     public ComponentName getVoiceInteractorComponentName() {
    236         return mAssistUtils.getActiveServiceComponentName();
    237     }
    238 
    239     private boolean isVoiceSessionRunning() {
    240         return mAssistUtils.isSessionRunning();
    241     }
    242 
    243     public void destroy() {
    244         mWindowManager.removeViewImmediate(mView);
    245     }
    246 
    247     private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) {
    248         replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME,
    249                 isService);
    250     }
    251 
    252     public void replaceDrawable(ImageView v, ComponentName component, String name,
    253             boolean isService) {
    254         if (component != null) {
    255             try {
    256                 PackageManager packageManager = mContext.getPackageManager();
    257                 // Look for the search icon specified in the activity meta-data
    258                 Bundle metaData = isService
    259                         ? packageManager.getServiceInfo(
    260                                 component, PackageManager.GET_META_DATA).metaData
    261                         : packageManager.getActivityInfo(
    262                                 component, PackageManager.GET_META_DATA).metaData;
    263                 if (metaData != null) {
    264                     int iconResId = metaData.getInt(name);
    265                     if (iconResId != 0) {
    266                         Resources res = packageManager.getResourcesForApplication(
    267                                 component.getPackageName());
    268                         v.setImageDrawable(res.getDrawable(iconResId));
    269                         return;
    270                     }
    271                 }
    272             } catch (PackageManager.NameNotFoundException e) {
    273                 Log.v(TAG, "Assistant component "
    274                         + component.flattenToShortString() + " not found");
    275             } catch (Resources.NotFoundException nfe) {
    276                 Log.w(TAG, "Failed to swap drawable from "
    277                         + component.flattenToShortString(), nfe);
    278             }
    279         }
    280         v.setImageDrawable(null);
    281     }
    282 
    283     @Nullable
    284     private ComponentName getAssistInfo() {
    285         return mAssistUtils.getAssistComponentForUser(UserHandle.USER_CURRENT);
    286     }
    287 
    288     public void showDisclosure() {
    289         mAssistDisclosure.postShow();
    290     }
    291 
    292     public void onLockscreenShown() {
    293         mAssistUtils.onLockscreenShown();
    294     }
    295 }
    296