blob: 674356b0f86686b5ffac66351c61cfdcc7bdab68 [file] [log] [blame]
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -07001package com.android.systemui.assist;
2
3import android.app.ActivityManager;
4import android.app.ActivityOptions;
5import android.app.SearchManager;
6import android.content.ActivityNotFoundException;
7import android.content.ComponentName;
8import android.content.Context;
9import android.content.Intent;
10import android.content.pm.PackageManager;
11import android.content.res.Resources;
Xiyuan Xiae6de8b92015-06-06 22:18:05 -070012import android.database.ContentObserver;
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070013import android.graphics.PixelFormat;
14import android.media.AudioAttributes;
15import android.os.AsyncTask;
16import android.os.Bundle;
Xiyuan Xiae6de8b92015-06-06 22:18:05 -070017import android.os.Handler;
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070018import android.os.RemoteException;
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070019import android.os.UserHandle;
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070020import android.provider.Settings;
21import android.util.Log;
22import android.view.Gravity;
Jorim Jaggi537bb5f2015-06-04 17:38:20 -070023import android.view.HapticFeedbackConstants;
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070024import android.view.LayoutInflater;
25import android.view.View;
26import android.view.ViewGroup;
27import android.view.WindowManager;
28import android.widget.ImageView;
29
Adrian Roose9175082015-06-15 13:23:14 -070030import com.android.internal.app.AssistUtils;
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070031import com.android.internal.app.IVoiceInteractionSessionShowCallback;
32import com.android.systemui.R;
33import com.android.systemui.statusbar.CommandQueue;
34import com.android.systemui.statusbar.phone.PhoneStatusBar;
35
Adrian Roose9175082015-06-15 13:23:14 -070036import java.io.FileDescriptor;
37import java.io.PrintWriter;
38
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070039/**
Selim Cineke70d6532015-04-24 16:46:13 -070040 * Class to manage everything related to assist in SystemUI.
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070041 */
Selim Cineke70d6532015-04-24 16:46:13 -070042public class AssistManager {
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070043
Selim Cineke70d6532015-04-24 16:46:13 -070044 private static final String TAG = "AssistManager";
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070045 private static final String ASSIST_ICON_METADATA_NAME =
46 "com.android.systemui.action_assist_icon";
47
48 private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
49 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
50 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
51 .build();
52
53 private static final long TIMEOUT_SERVICE = 2500;
54 private static final long TIMEOUT_ACTIVITY = 1000;
55
56 private final Context mContext;
57 private final WindowManager mWindowManager;
Adrian Roos4f43dc02015-06-17 16:43:38 -070058 private final AssistDisclosure mAssistDisclosure;
59
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070060 private AssistOrbContainer mView;
61 private final PhoneStatusBar mBar;
Adrian Roose9175082015-06-15 13:23:14 -070062 private final AssistUtils mAssistUtils;
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070063
Xiyuan Xiae6de8b92015-06-06 22:18:05 -070064 private ComponentName mAssistComponent;
65
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070066 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
Xiyuan Xiae6de8b92015-06-06 22:18:05 -070088 private final ContentObserver mAssistSettingsObserver = new ContentObserver(new Handler()) {
89 @Override
90 public void onChange(boolean selfChange) {
91 updateAssistInfo();
92 }
93 };
94
Selim Cineke70d6532015-04-24 16:46:13 -070095 public AssistManager(PhoneStatusBar bar, Context context) {
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -070096 mContext = context;
97 mBar = bar;
98 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Adrian Roose9175082015-06-15 13:23:14 -070099 mAssistUtils = new AssistUtils(context);
Xiyuan Xiae6de8b92015-06-06 22:18:05 -0700100
101 mContext.getContentResolver().registerContentObserver(
102 Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), false,
103 mAssistSettingsObserver);
104 mAssistSettingsObserver.onChange(false);
Adrian Roos4f43dc02015-06-17 16:43:38 -0700105 mAssistDisclosure = new AssistDisclosure(context, new Handler());
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700106 }
107
108 public void onConfigurationChanged() {
109 boolean visible = false;
110 if (mView != null) {
111 visible = mView.isShowing();
112 mWindowManager.removeView(mView);
113 }
114
115 mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
116 R.layout.assist_orb, null);
117 mView.setVisibility(View.GONE);
118 mView.setSystemUiVisibility(
119 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
120 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
121 WindowManager.LayoutParams lp = getLayoutParams();
122 mWindowManager.addView(mView, lp);
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700123 if (visible) {
124 mView.show(true /* show */, false /* animate */);
125 }
126 }
127
128 public void onGestureInvoked(boolean vibrate) {
Xiyuan Xiae6de8b92015-06-06 22:18:05 -0700129 if (mAssistComponent == null) {
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700130 return;
131 }
Xiyuan Xiae6de8b92015-06-06 22:18:05 -0700132
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700133 if (vibrate) {
134 vibrate();
135 }
Xiyuan Xiae6de8b92015-06-06 22:18:05 -0700136 final boolean isService = isAssistantService();
137 if (isService || !isVoiceSessionRunning()) {
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700138 showOrb();
Xiyuan Xiae6de8b92015-06-06 22:18:05 -0700139 mView.postDelayed(mHideRunnable, isService
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700140 ? TIMEOUT_SERVICE
141 : TIMEOUT_ACTIVITY);
142 }
143 startAssist();
144 }
145
Jorim Jaggib835dd72015-06-08 12:28:42 -0700146 public void hideAssist() {
Adrian Roose9175082015-06-15 13:23:14 -0700147 mAssistUtils.hideCurrentSession();
Jorim Jaggib835dd72015-06-08 12:28:42 -0700148 }
149
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700150 private WindowManager.LayoutParams getLayoutParams() {
151 WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
152 ViewGroup.LayoutParams.MATCH_PARENT,
153 mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
154 WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
155 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
156 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
157 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
158 PixelFormat.TRANSLUCENT);
159 if (ActivityManager.isHighEndGfx()) {
160 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
161 }
162 lp.gravity = Gravity.BOTTOM | Gravity.START;
163 lp.setTitle("AssistPreviewPanel");
164 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
165 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
166 return lp;
167 }
168
169 private void showOrb() {
170 maybeSwapSearchIcon();
171 mView.show(true /* show */, true /* animate */);
172 }
173
174 private void startAssist() {
Xiyuan Xiae6de8b92015-06-06 22:18:05 -0700175 if (mAssistComponent != null) {
176 if (isAssistantService()) {
177 startVoiceInteractor();
178 } else {
179 startAssistActivity();
180 }
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700181 }
182 }
183
184 private void startAssistActivity() {
185 if (!mBar.isDeviceProvisioned()) {
186 return;
187 }
188
189 // Close Recent Apps if needed
190 mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL |
191 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
192
Adrian Roos4f43dc02015-06-17 16:43:38 -0700193 boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
194 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
195
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700196 final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
Adrian Roos4f43dc02015-06-17 16:43:38 -0700197 .getAssistIntent(mContext, structureEnabled, UserHandle.USER_CURRENT);
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700198 if (intent == null) {
199 return;
200 }
Xiyuan Xiae6de8b92015-06-06 22:18:05 -0700201 if (mAssistComponent != null) {
202 intent.setComponent(mAssistComponent);
203 }
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700204
Adrian Roos4f43dc02015-06-17 16:43:38 -0700205 if (structureEnabled) {
206 showDisclosure();
207 }
208
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700209 try {
210 final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
211 R.anim.search_launch_enter, R.anim.search_launch_exit);
212 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
213 AsyncTask.execute(new Runnable() {
214 @Override
215 public void run() {
216 mContext.startActivityAsUser(intent, opts.toBundle(),
217 new UserHandle(UserHandle.USER_CURRENT));
218 }
219 });
220 } catch (ActivityNotFoundException e) {
221 Log.w(TAG, "Activity not found for " + intent.getAction());
222 }
223 }
224
225 private void startVoiceInteractor() {
Adrian Roose9175082015-06-15 13:23:14 -0700226 mAssistUtils.showSessionForActiveService(mShowCallback);
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700227 }
228
Selim Cineke70d6532015-04-24 16:46:13 -0700229 public void launchVoiceAssistFromKeyguard() {
Adrian Roose9175082015-06-15 13:23:14 -0700230 mAssistUtils.launchVoiceAssistFromKeyguard();
Selim Cineke70d6532015-04-24 16:46:13 -0700231 }
232
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700233 private boolean getVoiceInteractorSupportsAssistGesture() {
Adrian Roose9175082015-06-15 13:23:14 -0700234 return mAssistUtils.activeServiceSupportsAssistGesture();
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700235 }
236
Selim Cineke70d6532015-04-24 16:46:13 -0700237 public boolean canVoiceAssistBeLaunchedFromKeyguard() {
Adrian Roose9175082015-06-15 13:23:14 -0700238 return mAssistUtils.activeServiceSupportsLaunchFromKeyguard();
Selim Cineke70d6532015-04-24 16:46:13 -0700239 }
240
241 public ComponentName getVoiceInteractorComponentName() {
Adrian Roose9175082015-06-15 13:23:14 -0700242 return mAssistUtils.getActiveServiceComponentName();
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700243 }
244
245 private boolean isVoiceSessionRunning() {
Adrian Roose9175082015-06-15 13:23:14 -0700246 return mAssistUtils.isSessionRunning();
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700247 }
248
249 public void destroy() {
250 mWindowManager.removeViewImmediate(mView);
251 }
252
253 private void maybeSwapSearchIcon() {
Xiyuan Xiae6de8b92015-06-06 22:18:05 -0700254 if (mAssistComponent != null) {
255 replaceDrawable(mView.getOrb().getLogo(), mAssistComponent, ASSIST_ICON_METADATA_NAME,
256 isAssistantService());
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700257 } else {
258 mView.getOrb().getLogo().setImageDrawable(null);
259 }
260 }
261
262 public void replaceDrawable(ImageView v, ComponentName component, String name,
263 boolean isService) {
264 if (component != null) {
265 try {
266 PackageManager packageManager = mContext.getPackageManager();
267 // Look for the search icon specified in the activity meta-data
268 Bundle metaData = isService
269 ? packageManager.getServiceInfo(
270 component, PackageManager.GET_META_DATA).metaData
271 : packageManager.getActivityInfo(
272 component, PackageManager.GET_META_DATA).metaData;
273 if (metaData != null) {
274 int iconResId = metaData.getInt(name);
275 if (iconResId != 0) {
276 Resources res = packageManager.getResourcesForApplication(
277 component.getPackageName());
278 v.setImageDrawable(res.getDrawable(iconResId));
279 return;
280 }
281 }
282 } catch (PackageManager.NameNotFoundException e) {
283 Log.w(TAG, "Failed to swap drawable; "
284 + component.flattenToShortString() + " not found", e);
285 } catch (Resources.NotFoundException nfe) {
286 Log.w(TAG, "Failed to swap drawable from "
287 + component.flattenToShortString(), nfe);
288 }
289 }
290 v.setImageDrawable(null);
291 }
292
293 private void vibrate() {
Jorim Jaggi537bb5f2015-06-04 17:38:20 -0700294 mView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700295 }
296
Xiyuan Xiae6de8b92015-06-06 22:18:05 -0700297 private boolean isAssistantService() {
298 return mAssistComponent == null ?
299 false : mAssistComponent.equals(getVoiceInteractorComponentName());
300 }
301
302 private void updateAssistInfo() {
Adrian Roose9175082015-06-15 13:23:14 -0700303 mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.USER_CURRENT);
304 }
Xiyuan Xiae6de8b92015-06-06 22:18:05 -0700305
Adrian Roose9175082015-06-15 13:23:14 -0700306 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
307 pw.println("AssistManager state:");
308 pw.print(" mAssistComponent="); pw.println(mAssistComponent);
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700309 }
Adrian Roos4f43dc02015-06-17 16:43:38 -0700310
311 public void showDisclosure() {
312 mAssistDisclosure.postShow();
313 }
Jorim Jaggi2fdeeab2015-04-01 15:13:03 -0700314}