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