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 65 private IVoiceInteractionSessionShowCallback mShowCallback = 66 new IVoiceInteractionSessionShowCallback.Stub() { 67 68 @Override 69 public void onFailed() throws RemoteException { 70 mView.post(mHideRunnable); 71 } 72 73 @Override 74 public void onShown() throws RemoteException { 75 mView.post(mHideRunnable); 76 } 77 }; 78 79 private Runnable mHideRunnable = new Runnable() { 80 @Override 81 public void run() { 82 mView.removeCallbacks(this); 83 mView.show(false /* show */, true /* animate */); 84 } 85 }; 86 87 public AssistManager(DeviceProvisionedController controller, Context context) { 88 mContext = context; 89 mDeviceProvisionedController = controller; 90 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 91 mAssistUtils = new AssistUtils(context); 92 mAssistDisclosure = new AssistDisclosure(context, new Handler()); 93 94 registerVoiceInteractionSessionListener(); 95 mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION 96 | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE 97 | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS); 98 onConfigurationChanged(context.getResources().getConfiguration()); 99 } 100 101 protected void registerVoiceInteractionSessionListener() { 102 mAssistUtils.registerVoiceInteractionSessionListener( 103 new IVoiceInteractionSessionListener.Stub() { 104 @Override 105 public void onVoiceSessionShown() throws RemoteException { 106 Log.v(TAG, "Voice open"); 107 } 108 109 @Override 110 public void onVoiceSessionHidden() throws RemoteException { 111 Log.v(TAG, "Voice closed"); 112 } 113 }); 114 } 115 116 public void onConfigurationChanged(Configuration newConfiguration) { 117 if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { 118 return; 119 } 120 boolean visible = false; 121 if (mView != null) { 122 visible = mView.isShowing(); 123 mWindowManager.removeView(mView); 124 } 125 126 mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate( 127 R.layout.assist_orb, null); 128 mView.setVisibility(View.GONE); 129 mView.setSystemUiVisibility( 130 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE 131 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 132 WindowManager.LayoutParams lp = getLayoutParams(); 133 mWindowManager.addView(mView, lp); 134 if (visible) { 135 mView.show(true /* show */, false /* animate */); 136 } 137 } 138 139 protected boolean shouldShowOrb() { 140 return true; 141 } 142 143 public void startAssist(Bundle args) { 144 final ComponentName assistComponent = getAssistInfo(); 145 if (assistComponent == null) { 146 return; 147 } 148 149 final boolean isService = assistComponent.equals(getVoiceInteractorComponentName()); 150 if (!isService || (!isVoiceSessionRunning() && shouldShowOrb())) { 151 showOrb(assistComponent, isService); 152 mView.postDelayed(mHideRunnable, isService 153 ? TIMEOUT_SERVICE 154 : TIMEOUT_ACTIVITY); 155 } 156 startAssistInternal(args, assistComponent, isService); 157 } 158 159 public void hideAssist() { 160 mAssistUtils.hideCurrentSession(); 161 } 162 163 private WindowManager.LayoutParams getLayoutParams() { 164 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 165 ViewGroup.LayoutParams.MATCH_PARENT, 166 mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height), 167 WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING, 168 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 169 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 170 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 171 PixelFormat.TRANSLUCENT); 172 lp.token = new Binder(); 173 if (ActivityManager.isHighEndGfx()) { 174 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 175 } 176 lp.gravity = Gravity.BOTTOM | Gravity.START; 177 lp.setTitle("AssistPreviewPanel"); 178 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 179 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 180 return lp; 181 } 182 183 private void showOrb(@NonNull ComponentName assistComponent, boolean isService) { 184 maybeSwapSearchIcon(assistComponent, isService); 185 mView.show(true /* show */, true /* animate */); 186 } 187 188 private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent, 189 boolean isService) { 190 if (isService) { 191 startVoiceInteractor(args); 192 } else { 193 startAssistActivity(args, assistComponent); 194 } 195 } 196 197 private void startAssistActivity(Bundle args, @NonNull ComponentName assistComponent) { 198 if (!mDeviceProvisionedController.isDeviceProvisioned()) { 199 return; 200 } 201 202 // Close Recent Apps if needed 203 SysUiServiceProvider.getComponent(mContext, CommandQueue.class).animateCollapsePanels( 204 CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL); 205 206 boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(), 207 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0; 208 209 final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) 210 .getAssistIntent(structureEnabled); 211 if (intent == null) { 212 return; 213 } 214 intent.setComponent(assistComponent); 215 intent.putExtras(args); 216 217 if (structureEnabled) { 218 showDisclosure(); 219 } 220 221 try { 222 final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 223 R.anim.search_launch_enter, R.anim.search_launch_exit); 224 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 225 AsyncTask.execute(new Runnable() { 226 @Override 227 public void run() { 228 mContext.startActivityAsUser(intent, opts.toBundle(), 229 new UserHandle(UserHandle.USER_CURRENT)); 230 } 231 }); 232 } catch (ActivityNotFoundException e) { 233 Log.w(TAG, "Activity not found for " + intent.getAction()); 234 } 235 } 236 237 private void startVoiceInteractor(Bundle args) { 238 mAssistUtils.showSessionForActiveService(args, 239 VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, mShowCallback, null); 240 } 241 242 public void launchVoiceAssistFromKeyguard() { 243 mAssistUtils.launchVoiceAssistFromKeyguard(); 244 } 245 246 public boolean canVoiceAssistBeLaunchedFromKeyguard() { 247 return mAssistUtils.activeServiceSupportsLaunchFromKeyguard(); 248 } 249 250 public ComponentName getVoiceInteractorComponentName() { 251 return mAssistUtils.getActiveServiceComponentName(); 252 } 253 254 private boolean isVoiceSessionRunning() { 255 return mAssistUtils.isSessionRunning(); 256 } 257 258 public void destroy() { 259 mWindowManager.removeViewImmediate(mView); 260 } 261 262 private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) { 263 replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME, 264 isService); 265 } 266 267 public void replaceDrawable(ImageView v, ComponentName component, String name, 268 boolean isService) { 269 if (component != null) { 270 try { 271 PackageManager packageManager = mContext.getPackageManager(); 272 // Look for the search icon specified in the activity meta-data 273 Bundle metaData = isService 274 ? packageManager.getServiceInfo( 275 component, PackageManager.GET_META_DATA).metaData 276 : packageManager.getActivityInfo( 277 component, PackageManager.GET_META_DATA).metaData; 278 if (metaData != null) { 279 int iconResId = metaData.getInt(name); 280 if (iconResId != 0) { 281 Resources res = packageManager.getResourcesForApplication( 282 component.getPackageName()); 283 v.setImageDrawable(res.getDrawable(iconResId)); 284 return; 285 } 286 } 287 } catch (PackageManager.NameNotFoundException e) { 288 Log.v(TAG, "Assistant component " 289 + component.flattenToShortString() + " not found"); 290 } catch (Resources.NotFoundException nfe) { 291 Log.w(TAG, "Failed to swap drawable from " 292 + component.flattenToShortString(), nfe); 293 } 294 } 295 v.setImageDrawable(null); 296 } 297 298 @Nullable 299 private ComponentName getAssistInfo() { 300 return mAssistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser()); 301 } 302 303 public void showDisclosure() { 304 mAssistDisclosure.postShow(); 305 } 306 307 public void onLockscreenShown() { 308 mAssistUtils.onLockscreenShown(); 309 } 310 } 311