• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.assist;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.os.Handler;
22 import android.os.SystemClock;
23 import android.util.Log;
24 
25 import androidx.annotation.Nullable;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.app.AssistUtils;
29 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
30 import com.android.keyguard.KeyguardUpdateMonitor;
31 import com.android.systemui.Dependency;
32 import com.android.systemui.DumpController;
33 import com.android.systemui.Dumpable;
34 import com.android.systemui.ScreenDecorations;
35 import com.android.systemui.SysUiServiceProvider;
36 import com.android.systemui.shared.system.QuickStepContract;
37 import com.android.systemui.statusbar.phone.NavigationModeController;
38 
39 import java.io.FileDescriptor;
40 import java.io.PrintWriter;
41 import java.util.EnumMap;
42 import java.util.Map;
43 import java.util.concurrent.TimeUnit;
44 import java.util.function.Supplier;
45 
46 /**
47  * A class for managing Assistant handle logic.
48  *
49  * Controls when visual handles for Assistant gesture affordance should be shown or hidden using an
50  * {@link AssistHandleBehavior}.
51  */
52 public final class AssistHandleBehaviorController implements AssistHandleCallbacks, Dumpable {
53 
54     private static final String TAG = "AssistHandleBehavior";
55 
56     private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = 0;
57     private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3);
58 
59     /**
60      * This is the default behavior that will be used once the system is up. It will be set once the
61      * behavior dependencies are available. This ensures proper behavior lifecycle.
62      */
63     private static final AssistHandleBehavior DEFAULT_BEHAVIOR = AssistHandleBehavior.REMINDER_EXP;
64 
65     private final Context mContext;
66     private final AssistUtils mAssistUtils;
67     private final Handler mHandler;
68     private final Runnable mHideHandles = this::hideHandles;
69     private final Runnable mShowAndGo = this::showAndGoInternal;
70     private final Supplier<ScreenDecorations> mScreenDecorationsSupplier;
71     private final PhenotypeHelper mPhenotypeHelper;
72     private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap =
73             new EnumMap<>(AssistHandleBehavior.class);
74 
75     private boolean mHandlesShowing = false;
76     private long mHandlesLastHiddenAt;
77     /**
78      * This should always be initialized as {@link AssistHandleBehavior#OFF} to ensure proper
79      * behavior lifecycle.
80      */
81     private AssistHandleBehavior mCurrentBehavior = AssistHandleBehavior.OFF;
82     private boolean mInGesturalMode;
83 
AssistHandleBehaviorController(Context context, AssistUtils assistUtils, Handler handler)84     AssistHandleBehaviorController(Context context, AssistUtils assistUtils, Handler handler) {
85         this(
86                 context,
87                 assistUtils,
88                 handler,
89                 () -> SysUiServiceProvider.getComponent(context, ScreenDecorations.class),
90                 new PhenotypeHelper(),
91                 /* testBehavior = */ null);
92     }
93 
94     @VisibleForTesting
AssistHandleBehaviorController( Context context, AssistUtils assistUtils, Handler handler, Supplier<ScreenDecorations> screenDecorationsSupplier, PhenotypeHelper phenotypeHelper, @Nullable BehaviorController testBehavior)95     AssistHandleBehaviorController(
96             Context context,
97             AssistUtils assistUtils,
98             Handler handler,
99             Supplier<ScreenDecorations> screenDecorationsSupplier,
100             PhenotypeHelper phenotypeHelper,
101             @Nullable BehaviorController testBehavior) {
102         mContext = context;
103         mAssistUtils = assistUtils;
104         mHandler = handler;
105         mScreenDecorationsSupplier = screenDecorationsSupplier;
106         mPhenotypeHelper = phenotypeHelper;
107         mBehaviorMap.put(AssistHandleBehavior.OFF, new AssistHandleOffBehavior());
108         mBehaviorMap.put(AssistHandleBehavior.LIKE_HOME, new AssistHandleLikeHomeBehavior());
109         mBehaviorMap.put(
110                 AssistHandleBehavior.REMINDER_EXP,
111                 new AssistHandleReminderExpBehavior(handler, phenotypeHelper));
112         if (testBehavior != null) {
113             mBehaviorMap.put(AssistHandleBehavior.TEST, testBehavior);
114         }
115 
116         mInGesturalMode = QuickStepContract.isGesturalMode(
117                 Dependency.get(NavigationModeController.class)
118                         .addListener(this::handleNavigationModeChange));
119 
120         setBehavior(getBehaviorMode());
121         mPhenotypeHelper.addOnPropertiesChangedListener(
122                 mHandler::post,
123                 (properties) -> {
124                     if (properties.getKeyset().contains(
125                             SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE)) {
126                         setBehavior(properties.getString(
127                                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, null));
128                     }
129                 });
130         Dependency.get(DumpController.class).addListener(this);
131     }
132 
133     @Override // AssistHandleCallbacks
hide()134     public void hide() {
135         clearPendingCommands();
136         mHandler.post(mHideHandles);
137     }
138 
139     @Override // AssistHandleCallbacks
showAndGo()140     public void showAndGo() {
141         clearPendingCommands();
142         mHandler.post(mShowAndGo);
143     }
144 
showAndGoInternal()145     private void showAndGoInternal() {
146         maybeShowHandles(/* ignoreThreshold = */ false);
147         mHandler.postDelayed(mHideHandles, getShowAndGoDuration());
148     }
149 
150     @Override // AssistHandleCallbacks
showAndGoDelayed(long delayMs, boolean hideIfShowing)151     public void showAndGoDelayed(long delayMs, boolean hideIfShowing) {
152         clearPendingCommands();
153         if (hideIfShowing) {
154             mHandler.post(mHideHandles);
155         }
156         mHandler.postDelayed(mShowAndGo, delayMs);
157     }
158 
159     @Override // AssistHandleCallbacks
showAndStay()160     public void showAndStay() {
161         clearPendingCommands();
162         mHandler.post(() -> maybeShowHandles(/* ignoreThreshold = */ true));
163     }
164 
areHandlesShowing()165     boolean areHandlesShowing() {
166         return mHandlesShowing;
167     }
168 
onAssistantGesturePerformed()169     void onAssistantGesturePerformed() {
170         mBehaviorMap.get(mCurrentBehavior).onAssistantGesturePerformed();
171     }
172 
setBehavior(AssistHandleBehavior behavior)173     void setBehavior(AssistHandleBehavior behavior) {
174         if (mCurrentBehavior == behavior) {
175             return;
176         }
177 
178         if (!mBehaviorMap.containsKey(behavior)) {
179             Log.e(TAG, "Unsupported behavior requested: " + behavior.toString());
180             return;
181         }
182 
183         if (mInGesturalMode) {
184             mBehaviorMap.get(mCurrentBehavior).onModeDeactivated();
185             mBehaviorMap.get(behavior).onModeActivated(mContext, /* callbacks = */ this);
186         }
187 
188         mCurrentBehavior = behavior;
189     }
190 
setBehavior(@ullable String behavior)191     private void setBehavior(@Nullable String behavior) {
192         try {
193             setBehavior(AssistHandleBehavior.valueOf(behavior));
194         } catch (IllegalArgumentException | NullPointerException e) {
195             Log.e(TAG, "Invalid behavior: " + behavior, e);
196         }
197     }
198 
handlesUnblocked(boolean ignoreThreshold)199     private boolean handlesUnblocked(boolean ignoreThreshold) {
200         long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt;
201         boolean notThrottled = ignoreThreshold || timeSinceHidden >= getShownFrequencyThreshold();
202         ComponentName assistantComponent =
203                 mAssistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser());
204         return notThrottled && assistantComponent != null;
205     }
206 
getShownFrequencyThreshold()207     private long getShownFrequencyThreshold() {
208         return mPhenotypeHelper.getLong(
209                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS,
210                 DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS);
211     }
212 
getShowAndGoDuration()213     private long getShowAndGoDuration() {
214         return mPhenotypeHelper.getLong(
215                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS,
216                 DEFAULT_SHOW_AND_GO_DURATION_MS);
217     }
218 
getBehaviorMode()219     private String getBehaviorMode() {
220         return mPhenotypeHelper.getString(
221                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE,
222                 DEFAULT_BEHAVIOR.toString());
223     }
224 
maybeShowHandles(boolean ignoreThreshold)225     private void maybeShowHandles(boolean ignoreThreshold) {
226         if (mHandlesShowing) {
227             return;
228         }
229 
230         if (handlesUnblocked(ignoreThreshold)) {
231             ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get();
232             if (screenDecorations == null) {
233                 Log.w(TAG, "Couldn't show handles, ScreenDecorations unavailable");
234             } else {
235                 mHandlesShowing = true;
236                 screenDecorations.setAssistHintVisible(true);
237             }
238         }
239     }
240 
hideHandles()241     private void hideHandles() {
242         if (!mHandlesShowing) {
243             return;
244         }
245 
246         ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get();
247         if (screenDecorations == null) {
248             Log.w(TAG, "Couldn't hide handles, ScreenDecorations unavailable");
249         } else {
250             mHandlesShowing = false;
251             mHandlesLastHiddenAt = SystemClock.elapsedRealtime();
252             screenDecorations.setAssistHintVisible(false);
253         }
254     }
255 
handleNavigationModeChange(int navigationMode)256     private void handleNavigationModeChange(int navigationMode) {
257         boolean inGesturalMode = QuickStepContract.isGesturalMode(navigationMode);
258         if (mInGesturalMode == inGesturalMode) {
259             return;
260         }
261 
262         mInGesturalMode = inGesturalMode;
263         if (mInGesturalMode) {
264             mBehaviorMap.get(mCurrentBehavior).onModeActivated(mContext, /* callbacks = */ this);
265         } else {
266             mBehaviorMap.get(mCurrentBehavior).onModeDeactivated();
267             hide();
268         }
269     }
270 
clearPendingCommands()271     private void clearPendingCommands() {
272         mHandler.removeCallbacks(mHideHandles);
273         mHandler.removeCallbacks(mShowAndGo);
274     }
275 
276     @VisibleForTesting
setInGesturalModeForTest(boolean inGesturalMode)277     void setInGesturalModeForTest(boolean inGesturalMode) {
278         mInGesturalMode = inGesturalMode;
279     }
280 
281     @Override // Dumpable
dump(FileDescriptor fd, PrintWriter pw, String[] args)282     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
283         pw.println("Current AssistHandleBehaviorController State:");
284 
285         pw.println("   mHandlesShowing=" + mHandlesShowing);
286         pw.println("   mHandlesLastHiddenAt=" + mHandlesLastHiddenAt);
287         pw.println("   mInGesturalMode=" + mInGesturalMode);
288 
289         pw.println("   Phenotype Flags:");
290         pw.println("      "
291                 + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS
292                 + "="
293                 + getShowAndGoDuration());
294         pw.println("      "
295                 + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS
296                 + "="
297                 + getShownFrequencyThreshold());
298         pw.println("      "
299                 + SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE
300                 + "="
301                 + getBehaviorMode());
302 
303         pw.println("   mCurrentBehavior=" + mCurrentBehavior.toString());
304         mBehaviorMap.get(mCurrentBehavior).dump(pw, "   ");
305     }
306 
307     interface BehaviorController {
onModeActivated(Context context, AssistHandleCallbacks callbacks)308         void onModeActivated(Context context, AssistHandleCallbacks callbacks);
onModeDeactivated()309         default void onModeDeactivated() {}
onAssistantGesturePerformed()310         default void onAssistantGesturePerformed() {}
dump(PrintWriter pw, String prefix)311         default void dump(PrintWriter pw, String prefix) {}
312     }
313 }
314