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