1 package com.android.systemui.assist;
2 
3 import android.app.ActivityManager;
4 import android.app.ActivityOptions;
5 import android.app.SearchManager;
6 import android.content.ActivityNotFoundException;
7 import android.content.ComponentName;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.pm.PackageManager;
11 import android.content.res.Resources;
12 import android.database.ContentObserver;
13 import android.graphics.PixelFormat;
14 import android.media.AudioAttributes;
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.HapticFeedbackConstants;
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.IVoiceInteractionSessionShowCallback;
33 import com.android.systemui.R;
34 import com.android.systemui.statusbar.BaseStatusBar;
35 import com.android.systemui.statusbar.CommandQueue;
36 import com.android.systemui.statusbar.phone.PhoneStatusBar;
37 
38 import java.io.FileDescriptor;
39 import java.io.PrintWriter;
40 
41 /**
42  * Class to manage everything related to assist in SystemUI.
43  */
44 public class AssistManager {
45 
46     private static final String TAG = "AssistManager";
47     private static final String ASSIST_ICON_METADATA_NAME =
48             "com.android.systemui.action_assist_icon";
49 
50     private static final long TIMEOUT_SERVICE = 2500;
51     private static final long TIMEOUT_ACTIVITY = 1000;
52 
53     private final Context mContext;
54     private final WindowManager mWindowManager;
55     private final AssistDisclosure mAssistDisclosure;
56 
57     private AssistOrbContainer mView;
58     private final BaseStatusBar mBar;
59     private final AssistUtils mAssistUtils;
60 
61     private ComponentName mAssistComponent;
62 
63     private IVoiceInteractionSessionShowCallback mShowCallback =
64             new IVoiceInteractionSessionShowCallback.Stub() {
65 
66         @Override
67         public void onFailed() throws RemoteException {
68             mView.post(mHideRunnable);
69         }
70 
71         @Override
72         public void onShown() throws RemoteException {
73             mView.post(mHideRunnable);
74         }
75     };
76 
77     private Runnable mHideRunnable = new Runnable() {
78         @Override
79         public void run() {
80             mView.removeCallbacks(this);
81             mView.show(false /* show */, true /* animate */);
82         }
83     };
84 
85     private final ContentObserver mAssistSettingsObserver = new ContentObserver(new Handler()) {
86         @Override
87         public void onChange(boolean selfChange) {
88             updateAssistInfo();
89         }
90     };
91 
AssistManager(BaseStatusBar bar, Context context)92     public AssistManager(BaseStatusBar bar, Context context) {
93         mContext = context;
94         mBar = bar;
95         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
96         mAssistUtils = new AssistUtils(context);
97 
98         mContext.getContentResolver().registerContentObserver(
99                 Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), false,
100                 mAssistSettingsObserver);
101         mAssistSettingsObserver.onChange(false);
102         mAssistDisclosure = new AssistDisclosure(context, new Handler());
103     }
104 
onConfigurationChanged()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 
startAssist(Bundle args)125     public void startAssist(Bundle args) {
126         updateAssistInfo();
127         if (mAssistComponent == null) {
128             return;
129         }
130 
131         final boolean isService = isAssistantService();
132         if (!isService || !isVoiceSessionRunning()) {
133             showOrb();
134             mView.postDelayed(mHideRunnable, isService
135                     ? TIMEOUT_SERVICE
136                     : TIMEOUT_ACTIVITY);
137         }
138         startAssistInternal(args);
139     }
140 
hideAssist()141     public void hideAssist() {
142         mAssistUtils.hideCurrentSession();
143     }
144 
getLayoutParams()145     private WindowManager.LayoutParams getLayoutParams() {
146         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
147                 ViewGroup.LayoutParams.MATCH_PARENT,
148                 mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
149                 WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
150                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
151                         | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
152                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
153                 PixelFormat.TRANSLUCENT);
154         if (ActivityManager.isHighEndGfx()) {
155             lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
156         }
157         lp.gravity = Gravity.BOTTOM | Gravity.START;
158         lp.setTitle("AssistPreviewPanel");
159         lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
160                 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
161         return lp;
162     }
163 
showOrb()164     private void showOrb() {
165         maybeSwapSearchIcon();
166         mView.show(true /* show */, true /* animate */);
167     }
168 
startAssistInternal(Bundle args)169     private void startAssistInternal(Bundle args) {
170         if (mAssistComponent != null) {
171             if (isAssistantService()) {
172                 startVoiceInteractor(args);
173             } else {
174                 startAssistActivity(args);
175             }
176         }
177     }
178 
startAssistActivity(Bundle args)179     private void startAssistActivity(Bundle args) {
180         if (!mBar.isDeviceProvisioned()) {
181             return;
182         }
183 
184         // Close Recent Apps if needed
185         mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL |
186                 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
187 
188         boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
189                 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
190 
191         final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
192                 .getAssistIntent(structureEnabled);
193         if (intent == null) {
194             return;
195         }
196         if (mAssistComponent != null) {
197             intent.setComponent(mAssistComponent);
198         }
199         intent.putExtras(args);
200 
201         if (structureEnabled) {
202             showDisclosure();
203         }
204 
205         try {
206             final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
207                     R.anim.search_launch_enter, R.anim.search_launch_exit);
208             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
209             AsyncTask.execute(new Runnable() {
210                 @Override
211                 public void run() {
212                     mContext.startActivityAsUser(intent, opts.toBundle(),
213                             new UserHandle(UserHandle.USER_CURRENT));
214                 }
215             });
216         } catch (ActivityNotFoundException e) {
217             Log.w(TAG, "Activity not found for " + intent.getAction());
218         }
219     }
220 
startVoiceInteractor(Bundle args)221     private void startVoiceInteractor(Bundle args) {
222         mAssistUtils.showSessionForActiveService(args,
223                 VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, mShowCallback, null);
224     }
225 
launchVoiceAssistFromKeyguard()226     public void launchVoiceAssistFromKeyguard() {
227         mAssistUtils.launchVoiceAssistFromKeyguard();
228     }
229 
canVoiceAssistBeLaunchedFromKeyguard()230     public boolean canVoiceAssistBeLaunchedFromKeyguard() {
231         return mAssistUtils.activeServiceSupportsLaunchFromKeyguard();
232     }
233 
getVoiceInteractorComponentName()234     public ComponentName getVoiceInteractorComponentName() {
235         return mAssistUtils.getActiveServiceComponentName();
236     }
237 
isVoiceSessionRunning()238     private boolean isVoiceSessionRunning() {
239         return mAssistUtils.isSessionRunning();
240     }
241 
destroy()242     public void destroy() {
243         mWindowManager.removeViewImmediate(mView);
244     }
245 
maybeSwapSearchIcon()246     private void maybeSwapSearchIcon() {
247         if (mAssistComponent != null) {
248             replaceDrawable(mView.getOrb().getLogo(), mAssistComponent, ASSIST_ICON_METADATA_NAME,
249                     isAssistantService());
250         } else {
251             mView.getOrb().getLogo().setImageDrawable(null);
252         }
253     }
254 
replaceDrawable(ImageView v, ComponentName component, String name, boolean isService)255     public void replaceDrawable(ImageView v, ComponentName component, String name,
256             boolean isService) {
257         if (component != null) {
258             try {
259                 PackageManager packageManager = mContext.getPackageManager();
260                 // Look for the search icon specified in the activity meta-data
261                 Bundle metaData = isService
262                         ? packageManager.getServiceInfo(
263                                 component, PackageManager.GET_META_DATA).metaData
264                         : packageManager.getActivityInfo(
265                                 component, PackageManager.GET_META_DATA).metaData;
266                 if (metaData != null) {
267                     int iconResId = metaData.getInt(name);
268                     if (iconResId != 0) {
269                         Resources res = packageManager.getResourcesForApplication(
270                                 component.getPackageName());
271                         v.setImageDrawable(res.getDrawable(iconResId));
272                         return;
273                     }
274                 }
275             } catch (PackageManager.NameNotFoundException e) {
276                 Log.w(TAG, "Failed to swap drawable; "
277                         + component.flattenToShortString() + " not found", e);
278             } catch (Resources.NotFoundException nfe) {
279                 Log.w(TAG, "Failed to swap drawable from "
280                         + component.flattenToShortString(), nfe);
281             }
282         }
283         v.setImageDrawable(null);
284     }
285 
isAssistantService()286     private boolean isAssistantService() {
287         return mAssistComponent == null ?
288                 false : mAssistComponent.equals(getVoiceInteractorComponentName());
289     }
290 
updateAssistInfo()291     private void updateAssistInfo() {
292         mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.USER_CURRENT);
293     }
294 
dump(FileDescriptor fd, PrintWriter pw, String[] args)295     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
296         pw.println("AssistManager state:");
297         pw.print("  mAssistComponent="); pw.println(mAssistComponent);
298     }
299 
showDisclosure()300     public void showDisclosure() {
301         mAssistDisclosure.postShow();
302     }
303 
onUserSwitched(int newUserId)304     public void onUserSwitched(int newUserId) {
305         updateAssistInfo();
306     }
307 
onLockscreenShown()308     public void onLockscreenShown() {
309         mAssistUtils.onLockscreenShown();
310     }
311 }
312