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 static com.android.systemui.assist.AssistModule.ASSIST_HANDLE_THREAD_NAME;
20 
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.os.Handler;
24 import android.os.SystemClock;
25 import android.provider.Settings;
26 import android.util.Log;
27 import android.view.accessibility.AccessibilityManager;
28 
29 import androidx.annotation.Nullable;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.internal.app.AssistUtils;
33 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
34 import com.android.keyguard.KeyguardUpdateMonitor;
35 import com.android.systemui.Dumpable;
36 import com.android.systemui.dump.DumpManager;
37 import com.android.systemui.shared.system.QuickStepContract;
38 import com.android.systemui.statusbar.phone.NavigationModeController;
39 
40 import java.io.FileDescriptor;
41 import java.io.PrintWriter;
42 import java.util.Map;
43 import java.util.concurrent.TimeUnit;
44 
45 import javax.inject.Inject;
46 import javax.inject.Named;
47 import javax.inject.Provider;
48 import javax.inject.Singleton;
49 
50 import dagger.Lazy;
51 
52 /**
53  * A class for managing Assistant handle logic.
54  *
55  * Controls when visual handles for Assistant gesture affordance should be shown or hidden using an
56  * {@link AssistHandleBehavior}.
57  */
58 @Singleton
59 public final class AssistHandleBehaviorController implements AssistHandleCallbacks, Dumpable {
60 
61     private static final String TAG = "AssistHandleBehavior";
62 
63     private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = 0;
64     private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3);
65     private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
66 
67     /**
68      * This is the default behavior that will be used once the system is up. It will be set once the
69      * behavior dependencies are available. This ensures proper behavior lifecycle.
70      */
71     private static final AssistHandleBehavior DEFAULT_BEHAVIOR = AssistHandleBehavior.REMINDER_EXP;
72 
73     private final Context mContext;
74     private final AssistUtils mAssistUtils;
75     private final Handler mHandler;
76     private final Runnable mHideHandles = this::hideHandles;
77     private final Runnable mShowAndGo = this::showAndGoInternal;
78     private final Provider<AssistHandleViewController> mAssistHandleViewController;
79     private final DeviceConfigHelper mDeviceConfigHelper;
80     private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap;
81     private final Lazy<AccessibilityManager> mA11yManager;
82 
83     private boolean mHandlesShowing = false;
84     private long mHandlesLastHiddenAt;
85     private long mShowAndGoEndsAt;
86     /**
87      * This should always be initialized as {@link AssistHandleBehavior#OFF} to ensure proper
88      * behavior lifecycle.
89      */
90     private AssistHandleBehavior mCurrentBehavior = AssistHandleBehavior.OFF;
91     private boolean mInGesturalMode;
92 
93     @Inject
AssistHandleBehaviorController( Context context, AssistUtils assistUtils, @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler, Provider<AssistHandleViewController> assistHandleViewController, DeviceConfigHelper deviceConfigHelper, Map<AssistHandleBehavior, BehaviorController> behaviorMap, NavigationModeController navigationModeController, Lazy<AccessibilityManager> a11yManager, DumpManager dumpManager)94     AssistHandleBehaviorController(
95             Context context,
96             AssistUtils assistUtils,
97             @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler,
98             Provider<AssistHandleViewController> assistHandleViewController,
99             DeviceConfigHelper deviceConfigHelper,
100             Map<AssistHandleBehavior, BehaviorController> behaviorMap,
101             NavigationModeController navigationModeController,
102             Lazy<AccessibilityManager> a11yManager,
103             DumpManager dumpManager) {
104         mContext = context;
105         mAssistUtils = assistUtils;
106         mHandler = handler;
107         mAssistHandleViewController = assistHandleViewController;
108         mDeviceConfigHelper = deviceConfigHelper;
109         mBehaviorMap = behaviorMap;
110         mA11yManager = a11yManager;
111 
112         mInGesturalMode = QuickStepContract.isGesturalMode(
113                 navigationModeController.addListener(this::handleNavigationModeChange));
114 
115         setBehavior(getBehaviorMode());
116         mDeviceConfigHelper.addOnPropertiesChangedListener(
117                 mHandler::post,
118                 (properties) -> {
119                     if (properties.getKeyset().contains(
120                             SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE)) {
121                         setBehavior(properties.getString(
122                                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, null));
123                     }
124                 });
125 
126         dumpManager.registerDumpable(TAG, this);
127     }
128 
129     @Override // AssistHandleCallbacks
hide()130     public void hide() {
131         clearPendingCommands();
132         mHandler.post(mHideHandles);
133     }
134 
135     @Override // AssistHandleCallbacks
showAndGo()136     public void showAndGo() {
137         clearPendingCommands();
138         mHandler.post(mShowAndGo);
139     }
140 
showAndGoInternal()141     private void showAndGoInternal() {
142         maybeShowHandles(/* ignoreThreshold = */ false);
143         long showAndGoDuration = getShowAndGoDuration();
144         mShowAndGoEndsAt = SystemClock.elapsedRealtime() + showAndGoDuration;
145         mHandler.postDelayed(mHideHandles, showAndGoDuration);
146     }
147 
148     @Override // AssistHandleCallbacks
showAndGoDelayed(long delayMs, boolean hideIfShowing)149     public void showAndGoDelayed(long delayMs, boolean hideIfShowing) {
150         clearPendingCommands();
151         if (hideIfShowing) {
152             mHandler.post(mHideHandles);
153         }
154         mHandler.postDelayed(mShowAndGo, delayMs);
155     }
156 
157     @Override // AssistHandleCallbacks
showAndStay()158     public void showAndStay() {
159         clearPendingCommands();
160         mHandler.post(() -> maybeShowHandles(/* ignoreThreshold = */ true));
161     }
162 
getShowAndGoRemainingTimeMs()163     public long getShowAndGoRemainingTimeMs() {
164         return Long.max(mShowAndGoEndsAt - SystemClock.elapsedRealtime(), 0);
165     }
166 
areHandlesShowing()167     public boolean areHandlesShowing() {
168         return mHandlesShowing;
169     }
170 
onAssistantGesturePerformed()171     void onAssistantGesturePerformed() {
172         mBehaviorMap.get(mCurrentBehavior).onAssistantGesturePerformed();
173     }
174 
onAssistHandlesRequested()175     void onAssistHandlesRequested() {
176         if (mInGesturalMode) {
177             mBehaviorMap.get(mCurrentBehavior).onAssistHandlesRequested();
178         }
179     }
180 
setBehavior(AssistHandleBehavior behavior)181     void setBehavior(AssistHandleBehavior behavior) {
182         if (mCurrentBehavior == behavior) {
183             return;
184         }
185 
186         if (!mBehaviorMap.containsKey(behavior)) {
187             Log.e(TAG, "Unsupported behavior requested: " + behavior.toString());
188             return;
189         }
190 
191         if (mInGesturalMode) {
192             mBehaviorMap.get(mCurrentBehavior).onModeDeactivated();
193             mBehaviorMap.get(behavior).onModeActivated(mContext, /* callbacks = */ this);
194         }
195 
196         mCurrentBehavior = behavior;
197     }
198 
setBehavior(@ullable String behavior)199     private void setBehavior(@Nullable String behavior) {
200         try {
201             setBehavior(AssistHandleBehavior.valueOf(behavior));
202         } catch (IllegalArgumentException | NullPointerException e) {
203             Log.e(TAG, "Invalid behavior: " + behavior);
204         }
205     }
206 
handlesUnblocked(boolean ignoreThreshold)207     private boolean handlesUnblocked(boolean ignoreThreshold) {
208         if (!isUserSetupComplete()) {
209             return false;
210         }
211 
212         long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt;
213         boolean notThrottled = ignoreThreshold || timeSinceHidden >= getShownFrequencyThreshold();
214         ComponentName assistantComponent =
215                 mAssistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser());
216         return notThrottled && assistantComponent != null;
217     }
218 
getShownFrequencyThreshold()219     private long getShownFrequencyThreshold() {
220         return mDeviceConfigHelper.getLong(
221                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS,
222                 DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS);
223     }
224 
getShowAndGoDuration()225     private long getShowAndGoDuration() {
226         long configuredTime = mDeviceConfigHelper.getLong(
227                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS,
228                 DEFAULT_SHOW_AND_GO_DURATION_MS);
229         return mA11yManager.get().getRecommendedTimeoutMillis(
230                 (int) configuredTime, AccessibilityManager.FLAG_CONTENT_ICONS);
231     }
232 
getBehaviorMode()233     private String getBehaviorMode() {
234         return mDeviceConfigHelper.getString(
235                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE,
236                 DEFAULT_BEHAVIOR.toString());
237     }
238 
maybeShowHandles(boolean ignoreThreshold)239     private void maybeShowHandles(boolean ignoreThreshold) {
240         if (mHandlesShowing) {
241             return;
242         }
243 
244         if (handlesUnblocked(ignoreThreshold)) {
245             mHandlesShowing = true;
246             AssistHandleViewController assistHandleViewController =
247                     mAssistHandleViewController.get();
248             if (assistHandleViewController == null) {
249                 Log.w(TAG, "Couldn't show handles, AssistHandleViewController unavailable");
250             } else {
251                 assistHandleViewController.setAssistHintVisible(true);
252             }
253         }
254     }
255 
hideHandles()256     private void hideHandles() {
257         if (!mHandlesShowing) {
258             return;
259         }
260 
261         mHandlesShowing = false;
262         mHandlesLastHiddenAt = SystemClock.elapsedRealtime();
263         AssistHandleViewController assistHandleViewController =
264                 mAssistHandleViewController.get();
265         if (assistHandleViewController == null) {
266             Log.w(TAG, "Couldn't show handles, AssistHandleViewController unavailable");
267         } else {
268             assistHandleViewController.setAssistHintVisible(false);
269         }
270     }
271 
handleNavigationModeChange(int navigationMode)272     private void handleNavigationModeChange(int navigationMode) {
273         boolean inGesturalMode = QuickStepContract.isGesturalMode(navigationMode);
274         if (mInGesturalMode == inGesturalMode) {
275             return;
276         }
277 
278         mInGesturalMode = inGesturalMode;
279         if (mInGesturalMode) {
280             mBehaviorMap.get(mCurrentBehavior).onModeActivated(mContext, /* callbacks = */ this);
281         } else {
282             mBehaviorMap.get(mCurrentBehavior).onModeDeactivated();
283             hide();
284         }
285     }
286 
clearPendingCommands()287     private void clearPendingCommands() {
288         mHandler.removeCallbacks(mHideHandles);
289         mHandler.removeCallbacks(mShowAndGo);
290         mShowAndGoEndsAt = 0;
291     }
292 
isUserSetupComplete()293     private boolean isUserSetupComplete() {
294         return Settings.Secure.getInt(
295                 mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
296     }
297 
298     @VisibleForTesting
setInGesturalModeForTest(boolean inGesturalMode)299     void setInGesturalModeForTest(boolean inGesturalMode) {
300         mInGesturalMode = inGesturalMode;
301     }
302 
303     @Override // Dumpable
dump(FileDescriptor fd, PrintWriter pw, String[] args)304     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
305         pw.println("Current AssistHandleBehaviorController State:");
306 
307         pw.println("   mHandlesShowing=" + mHandlesShowing);
308         pw.println("   mHandlesLastHiddenAt=" + mHandlesLastHiddenAt);
309         pw.println("   mInGesturalMode=" + mInGesturalMode);
310 
311         pw.println("   Phenotype Flags:");
312         pw.println("      "
313                 + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS + "(a11y modded)"
314                 + "="
315                 + getShowAndGoDuration());
316         pw.println("      "
317                 + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS
318                 + "="
319                 + getShownFrequencyThreshold());
320         pw.println("      "
321                 + SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE
322                 + "="
323                 + getBehaviorMode());
324 
325         pw.println("   mCurrentBehavior=" + mCurrentBehavior.toString());
326         mBehaviorMap.get(mCurrentBehavior).dump(pw, "   ");
327     }
328 
329     interface BehaviorController {
onModeActivated(Context context, AssistHandleCallbacks callbacks)330         void onModeActivated(Context context, AssistHandleCallbacks callbacks);
onModeDeactivated()331         default void onModeDeactivated() {}
onAssistantGesturePerformed()332         default void onAssistantGesturePerformed() {}
onAssistHandlesRequested()333         default void onAssistHandlesRequested() {}
dump(PrintWriter pw, String prefix)334         default void dump(PrintWriter pw, String prefix) {}
335     }
336 }
337