1 package com.android.systemui.assist; 2 3 import static android.view.Display.DEFAULT_DISPLAY; 4 5 import static com.android.systemui.DejankUtils.whitelistIpcs; 6 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED; 7 8 import android.annotation.NonNull; 9 import android.annotation.Nullable; 10 import android.app.ActivityOptions; 11 import android.app.SearchManager; 12 import android.content.ActivityNotFoundException; 13 import android.content.ComponentName; 14 import android.content.Context; 15 import android.content.Intent; 16 import android.metrics.LogMaker; 17 import android.os.AsyncTask; 18 import android.os.Bundle; 19 import android.os.Handler; 20 import android.os.RemoteException; 21 import android.os.SystemClock; 22 import android.os.UserHandle; 23 import android.provider.Settings; 24 import android.service.voice.VoiceInteractionSession; 25 import android.util.Log; 26 27 import com.android.internal.app.AssistUtils; 28 import com.android.internal.app.IVoiceInteractionSessionListener; 29 import com.android.internal.logging.MetricsLogger; 30 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 31 import com.android.keyguard.KeyguardUpdateMonitor; 32 import com.android.systemui.R; 33 import com.android.systemui.assist.ui.DefaultUiController; 34 import com.android.systemui.dagger.SysUISingleton; 35 import com.android.systemui.dagger.qualifiers.Main; 36 import com.android.systemui.model.SysUiState; 37 import com.android.systemui.recents.OverviewProxyService; 38 import com.android.systemui.statusbar.CommandQueue; 39 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 40 41 import javax.inject.Inject; 42 43 import dagger.Lazy; 44 45 /** 46 * Class to manage everything related to assist in SystemUI. 47 */ 48 @SysUISingleton 49 public class AssistManager { 50 51 /** 52 * Controls the UI for showing Assistant invocation progress. 53 */ 54 public interface UiController { 55 /** 56 * Updates the invocation progress. 57 * 58 * @param type one of INVOCATION_TYPE_GESTURE, INVOCATION_TYPE_ACTIVE_EDGE, 59 * INVOCATION_TYPE_VOICE, INVOCATION_TYPE_QUICK_SEARCH_BAR, 60 * INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS 61 * @param progress a float between 0 and 1 inclusive. 0 represents the beginning of the 62 * gesture; 1 represents the end. 63 */ onInvocationProgress(int type, float progress)64 void onInvocationProgress(int type, float progress); 65 66 /** 67 * Called when an invocation gesture completes. 68 * 69 * @param velocity the speed of the invocation gesture, in pixels per millisecond. For 70 * drags, this is 0. 71 */ onGestureCompletion(float velocity)72 void onGestureCompletion(float velocity); 73 74 /** 75 * Hides any SysUI for the assistant, but _does not_ close the assistant itself. 76 */ hide()77 void hide(); 78 } 79 80 private static final String TAG = "AssistManager"; 81 82 // Note that VERBOSE logging may leak PII (e.g. transcription contents). 83 private static final boolean VERBOSE = false; 84 85 private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms"; 86 private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state"; 87 protected static final String ACTION_KEY = "action"; 88 protected static final String SET_ASSIST_GESTURE_CONSTRAINED_ACTION = 89 "set_assist_gesture_constrained"; 90 protected static final String CONSTRAINED_KEY = "should_constrain"; 91 92 public static final String INVOCATION_TYPE_KEY = "invocation_type"; 93 public static final int INVOCATION_TYPE_UNKNOWN = 94 AssistUtils.INVOCATION_TYPE_UNKNOWN; 95 public static final int INVOCATION_TYPE_GESTURE = 96 AssistUtils.INVOCATION_TYPE_GESTURE; 97 public static final int INVOCATION_TYPE_OTHER = 98 AssistUtils.INVOCATION_TYPE_PHYSICAL_GESTURE; 99 public static final int INVOCATION_TYPE_VOICE = 100 AssistUtils.INVOCATION_TYPE_VOICE; 101 public static final int INVOCATION_TYPE_QUICK_SEARCH_BAR = 102 AssistUtils.INVOCATION_TYPE_QUICK_SEARCH_BAR; 103 public static final int INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS = 104 AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS; 105 public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS = 106 AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS; 107 108 public static final int DISMISS_REASON_INVOCATION_CANCELLED = 1; 109 public static final int DISMISS_REASON_TAP = 2; 110 public static final int DISMISS_REASON_BACK = 3; 111 public static final int DISMISS_REASON_TIMEOUT = 4; 112 113 private static final long TIMEOUT_SERVICE = 2500; 114 private static final long TIMEOUT_ACTIVITY = 1000; 115 116 protected final Context mContext; 117 private final AssistDisclosure mAssistDisclosure; 118 private final PhoneStateMonitor mPhoneStateMonitor; 119 private final UiController mUiController; 120 protected final Lazy<SysUiState> mSysUiState; 121 protected final AssistLogger mAssistLogger; 122 123 private final DeviceProvisionedController mDeviceProvisionedController; 124 private final CommandQueue mCommandQueue; 125 protected final AssistUtils mAssistUtils; 126 127 @Inject AssistManager( DeviceProvisionedController controller, Context context, AssistUtils assistUtils, CommandQueue commandQueue, PhoneStateMonitor phoneStateMonitor, OverviewProxyService overviewProxyService, Lazy<SysUiState> sysUiState, DefaultUiController defaultUiController, AssistLogger assistLogger, @Main Handler uiHandler)128 public AssistManager( 129 DeviceProvisionedController controller, 130 Context context, 131 AssistUtils assistUtils, 132 CommandQueue commandQueue, 133 PhoneStateMonitor phoneStateMonitor, 134 OverviewProxyService overviewProxyService, 135 Lazy<SysUiState> sysUiState, 136 DefaultUiController defaultUiController, 137 AssistLogger assistLogger, 138 @Main Handler uiHandler) { 139 mContext = context; 140 mDeviceProvisionedController = controller; 141 mCommandQueue = commandQueue; 142 mAssistUtils = assistUtils; 143 mAssistDisclosure = new AssistDisclosure(context, uiHandler); 144 mPhoneStateMonitor = phoneStateMonitor; 145 mAssistLogger = assistLogger; 146 147 registerVoiceInteractionSessionListener(); 148 149 mUiController = defaultUiController; 150 151 mSysUiState = sysUiState; 152 153 overviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() { 154 @Override 155 public void onAssistantProgress(float progress) { 156 // Progress goes from 0 to 1 to indicate how close the assist gesture is to 157 // completion. 158 onInvocationProgress(INVOCATION_TYPE_GESTURE, progress); 159 } 160 161 @Override 162 public void onAssistantGestureCompletion(float velocity) { 163 onGestureCompletion(velocity); 164 } 165 }); 166 } 167 registerVoiceInteractionSessionListener()168 protected void registerVoiceInteractionSessionListener() { 169 mAssistUtils.registerVoiceInteractionSessionListener( 170 new IVoiceInteractionSessionListener.Stub() { 171 @Override 172 public void onVoiceSessionShown() throws RemoteException { 173 if (VERBOSE) { 174 Log.v(TAG, "Voice open"); 175 } 176 mAssistLogger.reportAssistantSessionEvent( 177 AssistantSessionEvent.ASSISTANT_SESSION_UPDATE); 178 } 179 180 @Override 181 public void onVoiceSessionHidden() throws RemoteException { 182 if (VERBOSE) { 183 Log.v(TAG, "Voice closed"); 184 } 185 mAssistLogger.reportAssistantSessionEvent( 186 AssistantSessionEvent.ASSISTANT_SESSION_CLOSE); 187 } 188 189 @Override 190 public void onSetUiHints(Bundle hints) { 191 if (VERBOSE) { 192 Log.v(TAG, "UI hints received"); 193 } 194 195 String action = hints.getString(ACTION_KEY); 196 if (SET_ASSIST_GESTURE_CONSTRAINED_ACTION.equals(action)) { 197 mSysUiState.get() 198 .setFlag( 199 SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED, 200 hints.getBoolean(CONSTRAINED_KEY, false)) 201 .commitUpdate(DEFAULT_DISPLAY); 202 } 203 } 204 }); 205 } 206 startAssist(Bundle args)207 public void startAssist(Bundle args) { 208 final ComponentName assistComponent = getAssistInfo(); 209 if (assistComponent == null) { 210 return; 211 } 212 213 final boolean isService = assistComponent.equals(getVoiceInteractorComponentName()); 214 215 if (args == null) { 216 args = new Bundle(); 217 } 218 int legacyInvocationType = args.getInt(INVOCATION_TYPE_KEY, 0); 219 int legacyDeviceState = mPhoneStateMonitor.getPhoneState(); 220 args.putInt(INVOCATION_PHONE_STATE_KEY, legacyDeviceState); 221 args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.elapsedRealtime()); 222 mAssistLogger.reportAssistantInvocationEventFromLegacy( 223 legacyInvocationType, 224 /* isInvocationComplete = */ true, 225 assistComponent, 226 legacyDeviceState); 227 logStartAssistLegacy(legacyInvocationType, legacyDeviceState); 228 startAssistInternal(args, assistComponent, isService); 229 } 230 231 /** Called when the user is performing an assistant invocation action (e.g. Active Edge) */ onInvocationProgress(int type, float progress)232 public void onInvocationProgress(int type, float progress) { 233 mUiController.onInvocationProgress(type, progress); 234 } 235 236 /** 237 * Called when the user has invoked the assistant with the incoming velocity, in pixels per 238 * millisecond. For invocations without a velocity (e.g. slow drag), the velocity is set to 239 * zero. 240 */ onGestureCompletion(float velocity)241 public void onGestureCompletion(float velocity) { 242 mUiController.onGestureCompletion(velocity); 243 } 244 hideAssist()245 public void hideAssist() { 246 mAssistUtils.hideCurrentSession(); 247 } 248 startAssistInternal(Bundle args, @NonNull ComponentName assistComponent, boolean isService)249 private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent, 250 boolean isService) { 251 if (isService) { 252 startVoiceInteractor(args); 253 } else { 254 startAssistActivity(args, assistComponent); 255 } 256 } 257 startAssistActivity(Bundle args, @NonNull ComponentName assistComponent)258 private void startAssistActivity(Bundle args, @NonNull ComponentName assistComponent) { 259 if (!mDeviceProvisionedController.isDeviceProvisioned()) { 260 return; 261 } 262 263 // Close Recent Apps if needed 264 mCommandQueue.animateCollapsePanels( 265 CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, 266 false /* force */); 267 268 boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(), 269 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0; 270 271 final SearchManager searchManager = 272 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); 273 if (searchManager == null) { 274 return; 275 } 276 final Intent intent = searchManager.getAssistIntent(structureEnabled); 277 if (intent == null) { 278 return; 279 } 280 intent.setComponent(assistComponent); 281 intent.putExtras(args); 282 283 if (structureEnabled && AssistUtils.isDisclosureEnabled(mContext)) { 284 showDisclosure(); 285 } 286 287 try { 288 final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 289 R.anim.search_launch_enter, R.anim.search_launch_exit); 290 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 291 AsyncTask.execute(new Runnable() { 292 @Override 293 public void run() { 294 mContext.startActivityAsUser(intent, opts.toBundle(), 295 new UserHandle(UserHandle.USER_CURRENT)); 296 } 297 }); 298 } catch (ActivityNotFoundException e) { 299 Log.w(TAG, "Activity not found for " + intent.getAction()); 300 } 301 } 302 startVoiceInteractor(Bundle args)303 private void startVoiceInteractor(Bundle args) { 304 mAssistUtils.showSessionForActiveService(args, 305 VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, null, null); 306 } 307 launchVoiceAssistFromKeyguard()308 public void launchVoiceAssistFromKeyguard() { 309 mAssistUtils.launchVoiceAssistFromKeyguard(); 310 } 311 canVoiceAssistBeLaunchedFromKeyguard()312 public boolean canVoiceAssistBeLaunchedFromKeyguard() { 313 // TODO(b/140051519) 314 return whitelistIpcs(() -> mAssistUtils.activeServiceSupportsLaunchFromKeyguard()); 315 } 316 getVoiceInteractorComponentName()317 public ComponentName getVoiceInteractorComponentName() { 318 return mAssistUtils.getActiveServiceComponentName(); 319 } 320 isVoiceSessionRunning()321 private boolean isVoiceSessionRunning() { 322 return mAssistUtils.isSessionRunning(); 323 } 324 325 @Nullable getAssistInfoForUser(int userId)326 public ComponentName getAssistInfoForUser(int userId) { 327 return mAssistUtils.getAssistComponentForUser(userId); 328 } 329 330 @Nullable getAssistInfo()331 private ComponentName getAssistInfo() { 332 return getAssistInfoForUser(KeyguardUpdateMonitor.getCurrentUser()); 333 } 334 showDisclosure()335 public void showDisclosure() { 336 mAssistDisclosure.postShow(); 337 } 338 onLockscreenShown()339 public void onLockscreenShown() { 340 AsyncTask.execute(new Runnable() { 341 @Override 342 public void run() { 343 mAssistUtils.onLockscreenShown(); 344 } 345 }); 346 } 347 348 /** Returns the logging flags for the given Assistant invocation type. */ toLoggingSubType(int invocationType)349 public int toLoggingSubType(int invocationType) { 350 return toLoggingSubType(invocationType, mPhoneStateMonitor.getPhoneState()); 351 } 352 logStartAssistLegacy(int invocationType, int phoneState)353 protected void logStartAssistLegacy(int invocationType, int phoneState) { 354 MetricsLogger.action( 355 new LogMaker(MetricsEvent.ASSISTANT) 356 .setType(MetricsEvent.TYPE_OPEN) 357 .setSubtype(toLoggingSubType(invocationType, phoneState))); 358 } 359 toLoggingSubType(int invocationType, int phoneState)360 protected final int toLoggingSubType(int invocationType, int phoneState) { 361 // Note that this logic will break if the number of Assistant invocation types exceeds 7. 362 // There are currently 5 invocation types, but we will be migrating to the new logging 363 // framework in the next update. 364 int subType = 0; 365 subType |= invocationType << 1; 366 subType |= phoneState << 4; 367 return subType; 368 } 369 } 370