blob: 002d4f34be1131a7145746940ca9ede3565b9de4 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.assist;
import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
import com.android.systemui.ScreenDecorations;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.phone.NavigationModeController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.EnumMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* A class for managing Assistant handle logic.
*
* Controls when visual handles for Assistant gesture affordance should be shown or hidden using an
* {@link AssistHandleBehavior}.
*/
public final class AssistHandleBehaviorController implements AssistHandleCallbacks, Dumpable {
private static final String TAG = "AssistHandleBehavior";
private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = 0;
private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3);
/**
* This is the default behavior that will be used once the system is up. It will be set once the
* behavior dependencies are available. This ensures proper behavior lifecycle.
*/
private static final AssistHandleBehavior DEFAULT_BEHAVIOR = AssistHandleBehavior.REMINDER_EXP;
private final Context mContext;
private final AssistUtils mAssistUtils;
private final Handler mHandler;
private final Runnable mHideHandles = this::hideHandles;
private final Runnable mShowAndGo = this::showAndGoInternal;
private final Supplier<ScreenDecorations> mScreenDecorationsSupplier;
private final PhenotypeHelper mPhenotypeHelper;
private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap =
new EnumMap<>(AssistHandleBehavior.class);
private boolean mHandlesShowing = false;
private long mHandlesLastHiddenAt;
/**
* This should always be initialized as {@link AssistHandleBehavior#OFF} to ensure proper
* behavior lifecycle.
*/
private AssistHandleBehavior mCurrentBehavior = AssistHandleBehavior.OFF;
private boolean mInGesturalMode;
AssistHandleBehaviorController(Context context, AssistUtils assistUtils, Handler handler) {
this(
context,
assistUtils,
handler,
() -> SysUiServiceProvider.getComponent(context, ScreenDecorations.class),
new PhenotypeHelper(),
/* testBehavior = */ null);
}
@VisibleForTesting
AssistHandleBehaviorController(
Context context,
AssistUtils assistUtils,
Handler handler,
Supplier<ScreenDecorations> screenDecorationsSupplier,
PhenotypeHelper phenotypeHelper,
@Nullable BehaviorController testBehavior) {
mContext = context;
mAssistUtils = assistUtils;
mHandler = handler;
mScreenDecorationsSupplier = screenDecorationsSupplier;
mPhenotypeHelper = phenotypeHelper;
mBehaviorMap.put(AssistHandleBehavior.OFF, new AssistHandleOffBehavior());
mBehaviorMap.put(AssistHandleBehavior.LIKE_HOME, new AssistHandleLikeHomeBehavior());
mBehaviorMap.put(
AssistHandleBehavior.REMINDER_EXP,
new AssistHandleReminderExpBehavior(handler, phenotypeHelper));
if (testBehavior != null) {
mBehaviorMap.put(AssistHandleBehavior.TEST, testBehavior);
}
mInGesturalMode = QuickStepContract.isGesturalMode(
Dependency.get(NavigationModeController.class)
.addListener(this::handleNavigationModeChange));
setBehavior(getBehaviorMode());
mPhenotypeHelper.addOnPropertiesChangedListener(
mHandler::post,
(properties) -> {
if (properties.getKeyset().contains(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE)) {
setBehavior(properties.getString(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, null));
}
});
Dependency.get(DumpController.class).addListener(this);
}
@Override // AssistHandleCallbacks
public void hide() {
clearPendingCommands();
mHandler.post(mHideHandles);
}
@Override // AssistHandleCallbacks
public void showAndGo() {
clearPendingCommands();
mHandler.post(mShowAndGo);
}
private void showAndGoInternal() {
maybeShowHandles(/* ignoreThreshold = */ false);
mHandler.postDelayed(mHideHandles, getShowAndGoDuration());
}
@Override // AssistHandleCallbacks
public void showAndGoDelayed(long delayMs, boolean hideIfShowing) {
clearPendingCommands();
if (hideIfShowing) {
mHandler.post(mHideHandles);
}
mHandler.postDelayed(mShowAndGo, delayMs);
}
@Override // AssistHandleCallbacks
public void showAndStay() {
clearPendingCommands();
mHandler.post(() -> maybeShowHandles(/* ignoreThreshold = */ true));
}
boolean areHandlesShowing() {
return mHandlesShowing;
}
void onAssistantGesturePerformed() {
mBehaviorMap.get(mCurrentBehavior).onAssistantGesturePerformed();
}
void setBehavior(AssistHandleBehavior behavior) {
if (mCurrentBehavior == behavior) {
return;
}
if (!mBehaviorMap.containsKey(behavior)) {
Log.e(TAG, "Unsupported behavior requested: " + behavior.toString());
return;
}
if (mInGesturalMode) {
mBehaviorMap.get(mCurrentBehavior).onModeDeactivated();
mBehaviorMap.get(behavior).onModeActivated(mContext, /* callbacks = */ this);
}
mCurrentBehavior = behavior;
}
private void setBehavior(@Nullable String behavior) {
try {
setBehavior(AssistHandleBehavior.valueOf(behavior));
} catch (IllegalArgumentException | NullPointerException e) {
Log.e(TAG, "Invalid behavior: " + behavior, e);
}
}
private boolean handlesUnblocked(boolean ignoreThreshold) {
long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt;
boolean notThrottled = ignoreThreshold || timeSinceHidden >= getShownFrequencyThreshold();
ComponentName assistantComponent =
mAssistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser());
return notThrottled && assistantComponent != null;
}
private long getShownFrequencyThreshold() {
return mPhenotypeHelper.getLong(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS,
DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS);
}
private long getShowAndGoDuration() {
return mPhenotypeHelper.getLong(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS,
DEFAULT_SHOW_AND_GO_DURATION_MS);
}
private String getBehaviorMode() {
return mPhenotypeHelper.getString(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE,
DEFAULT_BEHAVIOR.toString());
}
private void maybeShowHandles(boolean ignoreThreshold) {
if (mHandlesShowing) {
return;
}
if (handlesUnblocked(ignoreThreshold)) {
ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get();
if (screenDecorations == null) {
Log.w(TAG, "Couldn't show handles, ScreenDecorations unavailable");
} else {
mHandlesShowing = true;
screenDecorations.setAssistHintVisible(true);
}
}
}
private void hideHandles() {
if (!mHandlesShowing) {
return;
}
ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get();
if (screenDecorations == null) {
Log.w(TAG, "Couldn't hide handles, ScreenDecorations unavailable");
} else {
mHandlesShowing = false;
mHandlesLastHiddenAt = SystemClock.elapsedRealtime();
screenDecorations.setAssistHintVisible(false);
}
}
private void handleNavigationModeChange(int navigationMode) {
boolean inGesturalMode = QuickStepContract.isGesturalMode(navigationMode);
if (mInGesturalMode == inGesturalMode) {
return;
}
mInGesturalMode = inGesturalMode;
if (mInGesturalMode) {
mBehaviorMap.get(mCurrentBehavior).onModeActivated(mContext, /* callbacks = */ this);
} else {
mBehaviorMap.get(mCurrentBehavior).onModeDeactivated();
hide();
}
}
private void clearPendingCommands() {
mHandler.removeCallbacks(mHideHandles);
mHandler.removeCallbacks(mShowAndGo);
}
@VisibleForTesting
void setInGesturalModeForTest(boolean inGesturalMode) {
mInGesturalMode = inGesturalMode;
}
@Override // Dumpable
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Current AssistHandleBehaviorController State:");
pw.println(" mHandlesShowing=" + mHandlesShowing);
pw.println(" mHandlesLastHiddenAt=" + mHandlesLastHiddenAt);
pw.println(" mInGesturalMode=" + mInGesturalMode);
pw.println(" Phenotype Flags:");
pw.println(" "
+ SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS
+ "="
+ getShowAndGoDuration());
pw.println(" "
+ SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS
+ "="
+ getShownFrequencyThreshold());
pw.println(" "
+ SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE
+ "="
+ getBehaviorMode());
pw.println(" mCurrentBehavior=" + mCurrentBehavior.toString());
mBehaviorMap.get(mCurrentBehavior).dump(pw, " ");
}
interface BehaviorController {
void onModeActivated(Context context, AssistHandleCallbacks callbacks);
default void onModeDeactivated() {}
default void onAssistantGesturePerformed() {}
default void dump(PrintWriter pw, String prefix) {}
}
}