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