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