blob: 9d5b93c2b329b01cb546278b6713e7f90ef9ddd4 [file] [log] [blame]
package com.android.systemui.assist;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.metrics.LogMaker;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.voice.VoiceInteractionSession;
import android.util.Log;
import com.android.internal.app.AssistUtils;
import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.assist.ui.DefaultUiController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import javax.inject.Inject;
import dagger.Lazy;
/**
* Class to manage everything related to assist in SystemUI.
*/
@SysUISingleton
public class AssistManager {
/**
* Controls the UI for showing Assistant invocation progress.
*/
public interface UiController {
/**
* Updates the invocation progress.
*
* @param type one of INVOCATION_TYPE_GESTURE, INVOCATION_TYPE_ACTIVE_EDGE,
* INVOCATION_TYPE_VOICE, INVOCATION_TYPE_QUICK_SEARCH_BAR,
* INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS
* @param progress a float between 0 and 1 inclusive. 0 represents the beginning of the
* gesture; 1 represents the end.
*/
void onInvocationProgress(int type, float progress);
/**
* Called when an invocation gesture completes.
*
* @param velocity the speed of the invocation gesture, in pixels per millisecond. For
* drags, this is 0.
*/
void onGestureCompletion(float velocity);
/**
* Hides any SysUI for the assistant, but _does not_ close the assistant itself.
*/
void hide();
}
private static final String TAG = "AssistManager";
// Note that VERBOSE logging may leak PII (e.g. transcription contents).
private static final boolean VERBOSE = false;
private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms";
private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state";
protected static final String ACTION_KEY = "action";
protected static final String SET_ASSIST_GESTURE_CONSTRAINED_ACTION =
"set_assist_gesture_constrained";
protected static final String CONSTRAINED_KEY = "should_constrain";
public static final String INVOCATION_TYPE_KEY = "invocation_type";
public static final int INVOCATION_TYPE_UNKNOWN =
AssistUtils.INVOCATION_TYPE_UNKNOWN;
public static final int INVOCATION_TYPE_GESTURE =
AssistUtils.INVOCATION_TYPE_GESTURE;
public static final int INVOCATION_TYPE_OTHER =
AssistUtils.INVOCATION_TYPE_PHYSICAL_GESTURE;
public static final int INVOCATION_TYPE_VOICE =
AssistUtils.INVOCATION_TYPE_VOICE;
public static final int INVOCATION_TYPE_QUICK_SEARCH_BAR =
AssistUtils.INVOCATION_TYPE_QUICK_SEARCH_BAR;
public static final int INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS =
AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS =
AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS;
public static final int DISMISS_REASON_INVOCATION_CANCELLED = 1;
public static final int DISMISS_REASON_TAP = 2;
public static final int DISMISS_REASON_BACK = 3;
public static final int DISMISS_REASON_TIMEOUT = 4;
private static final long TIMEOUT_SERVICE = 2500;
private static final long TIMEOUT_ACTIVITY = 1000;
protected final Context mContext;
private final AssistDisclosure mAssistDisclosure;
private final PhoneStateMonitor mPhoneStateMonitor;
private final UiController mUiController;
protected final Lazy<SysUiState> mSysUiState;
protected final AssistLogger mAssistLogger;
private final DeviceProvisionedController mDeviceProvisionedController;
private final CommandQueue mCommandQueue;
protected final AssistUtils mAssistUtils;
@Inject
public AssistManager(
DeviceProvisionedController controller,
Context context,
AssistUtils assistUtils,
CommandQueue commandQueue,
PhoneStateMonitor phoneStateMonitor,
OverviewProxyService overviewProxyService,
Lazy<SysUiState> sysUiState,
DefaultUiController defaultUiController,
AssistLogger assistLogger,
@Main Handler uiHandler) {
mContext = context;
mDeviceProvisionedController = controller;
mCommandQueue = commandQueue;
mAssistUtils = assistUtils;
mAssistDisclosure = new AssistDisclosure(context, uiHandler);
mPhoneStateMonitor = phoneStateMonitor;
mAssistLogger = assistLogger;
registerVoiceInteractionSessionListener();
mUiController = defaultUiController;
mSysUiState = sysUiState;
overviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
@Override
public void onAssistantProgress(float progress) {
// Progress goes from 0 to 1 to indicate how close the assist gesture is to
// completion.
onInvocationProgress(INVOCATION_TYPE_GESTURE, progress);
}
@Override
public void onAssistantGestureCompletion(float velocity) {
onGestureCompletion(velocity);
}
});
}
protected void registerVoiceInteractionSessionListener() {
mAssistUtils.registerVoiceInteractionSessionListener(
new IVoiceInteractionSessionListener.Stub() {
@Override
public void onVoiceSessionShown() throws RemoteException {
if (VERBOSE) {
Log.v(TAG, "Voice open");
}
mAssistLogger.reportAssistantSessionEvent(
AssistantSessionEvent.ASSISTANT_SESSION_UPDATE);
}
@Override
public void onVoiceSessionHidden() throws RemoteException {
if (VERBOSE) {
Log.v(TAG, "Voice closed");
}
mAssistLogger.reportAssistantSessionEvent(
AssistantSessionEvent.ASSISTANT_SESSION_CLOSE);
}
@Override
public void onSetUiHints(Bundle hints) {
if (VERBOSE) {
Log.v(TAG, "UI hints received");
}
String action = hints.getString(ACTION_KEY);
if (SET_ASSIST_GESTURE_CONSTRAINED_ACTION.equals(action)) {
mSysUiState.get()
.setFlag(
SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
hints.getBoolean(CONSTRAINED_KEY, false))
.commitUpdate(DEFAULT_DISPLAY);
}
}
});
}
public void startAssist(Bundle args) {
final ComponentName assistComponent = getAssistInfo();
if (assistComponent == null) {
return;
}
final boolean isService = assistComponent.equals(getVoiceInteractorComponentName());
if (args == null) {
args = new Bundle();
}
int legacyInvocationType = args.getInt(INVOCATION_TYPE_KEY, 0);
int legacyDeviceState = mPhoneStateMonitor.getPhoneState();
args.putInt(INVOCATION_PHONE_STATE_KEY, legacyDeviceState);
args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.elapsedRealtime());
mAssistLogger.reportAssistantInvocationEventFromLegacy(
legacyInvocationType,
/* isInvocationComplete = */ true,
assistComponent,
legacyDeviceState);
logStartAssistLegacy(legacyInvocationType, legacyDeviceState);
startAssistInternal(args, assistComponent, isService);
}
/** Called when the user is performing an assistant invocation action (e.g. Active Edge) */
public void onInvocationProgress(int type, float progress) {
mUiController.onInvocationProgress(type, progress);
}
/**
* Called when the user has invoked the assistant with the incoming velocity, in pixels per
* millisecond. For invocations without a velocity (e.g. slow drag), the velocity is set to
* zero.
*/
public void onGestureCompletion(float velocity) {
mUiController.onGestureCompletion(velocity);
}
public void hideAssist() {
mAssistUtils.hideCurrentSession();
}
private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
boolean isService) {
if (isService) {
startVoiceInteractor(args);
} else {
startAssistActivity(args, assistComponent);
}
}
private void startAssistActivity(Bundle args, @NonNull ComponentName assistComponent) {
if (!mDeviceProvisionedController.isDeviceProvisioned()) {
return;
}
// Close Recent Apps if needed
mCommandQueue.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
false /* force */);
boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
final SearchManager searchManager =
(SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
if (searchManager == null) {
return;
}
final Intent intent = searchManager.getAssistIntent(structureEnabled);
if (intent == null) {
return;
}
intent.setComponent(assistComponent);
intent.putExtras(args);
if (structureEnabled && AssistUtils.isDisclosureEnabled(mContext)) {
showDisclosure();
}
try {
final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
R.anim.search_launch_enter, R.anim.search_launch_exit);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
mContext.startActivityAsUser(intent, opts.toBundle(),
new UserHandle(UserHandle.USER_CURRENT));
}
});
} catch (ActivityNotFoundException e) {
Log.w(TAG, "Activity not found for " + intent.getAction());
}
}
private void startVoiceInteractor(Bundle args) {
mAssistUtils.showSessionForActiveService(args,
VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, null, null);
}
public void launchVoiceAssistFromKeyguard() {
mAssistUtils.launchVoiceAssistFromKeyguard();
}
public boolean canVoiceAssistBeLaunchedFromKeyguard() {
// TODO(b/140051519)
return whitelistIpcs(() -> mAssistUtils.activeServiceSupportsLaunchFromKeyguard());
}
public ComponentName getVoiceInteractorComponentName() {
return mAssistUtils.getActiveServiceComponentName();
}
private boolean isVoiceSessionRunning() {
return mAssistUtils.isSessionRunning();
}
@Nullable
public ComponentName getAssistInfoForUser(int userId) {
return mAssistUtils.getAssistComponentForUser(userId);
}
@Nullable
private ComponentName getAssistInfo() {
return getAssistInfoForUser(KeyguardUpdateMonitor.getCurrentUser());
}
public void showDisclosure() {
mAssistDisclosure.postShow();
}
public void onLockscreenShown() {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
mAssistUtils.onLockscreenShown();
}
});
}
/** Returns the logging flags for the given Assistant invocation type. */
public int toLoggingSubType(int invocationType) {
return toLoggingSubType(invocationType, mPhoneStateMonitor.getPhoneState());
}
protected void logStartAssistLegacy(int invocationType, int phoneState) {
MetricsLogger.action(
new LogMaker(MetricsEvent.ASSISTANT)
.setType(MetricsEvent.TYPE_OPEN)
.setSubtype(toLoggingSubType(invocationType, phoneState)));
}
protected final int toLoggingSubType(int invocationType, int phoneState) {
// Note that this logic will break if the number of Assistant invocation types exceeds 7.
// There are currently 5 invocation types, but we will be migrating to the new logging
// framework in the next update.
int subType = 0;
subType |= invocationType << 1;
subType |= phoneState << 4;
return subType;
}
}