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