• 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 package com.android.quickstep;
17 
18 import static android.content.Intent.ACTION_USER_UNLOCKED;
19 
20 import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
21 import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
22 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
23 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
24 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
26 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
28 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
29 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
30 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
31 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
32 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
33 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
34 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
35 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
36 
37 import android.app.ActivityManager;
38 import android.content.BroadcastReceiver;
39 import android.content.ComponentName;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.res.Resources;
44 import android.graphics.Region;
45 import android.os.Process;
46 import android.os.UserManager;
47 import android.provider.Settings;
48 import android.text.TextUtils;
49 import android.view.MotionEvent;
50 
51 import androidx.annotation.BinderThread;
52 
53 import com.android.launcher3.R;
54 import com.android.launcher3.Utilities;
55 import com.android.launcher3.util.DefaultDisplay;
56 import com.android.launcher3.util.SecureSettingsObserver;
57 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
58 import com.android.quickstep.util.NavBarPosition;
59 import com.android.systemui.shared.system.ActivityManagerWrapper;
60 import com.android.systemui.shared.system.QuickStepContract;
61 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
62 import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
63 
64 import java.io.PrintWriter;
65 import java.util.ArrayList;
66 import java.util.List;
67 import java.util.stream.Collectors;
68 
69 /**
70  * Manages the state of the system during a swipe up gesture.
71  */
72 public class RecentsAnimationDeviceState implements
73         NavigationModeChangeListener,
74         DefaultDisplay.DisplayInfoChangeListener {
75 
76     private final Context mContext;
77     private final SysUINavigationMode mSysUiNavMode;
78     private final DefaultDisplay mDefaultDisplay;
79     private final int mDisplayId;
80     private final RotationTouchHelper mRotationTouchHelper;
81 
82     private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
83 
84     private @SystemUiStateFlags int mSystemUiStateFlags;
85     private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
86     private NavBarPosition mNavBarPosition;
87 
88     private final Region mDeferredGestureRegion = new Region();
89     private boolean mAssistantAvailable;
90     private float mAssistantVisibility;
91 
92     private boolean mIsUserUnlocked;
93     private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
94     private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
95         @Override
96         public void onReceive(Context context, Intent intent) {
97             if (ACTION_USER_UNLOCKED.equals(intent.getAction())) {
98                 mIsUserUnlocked = true;
99                 notifyUserUnlocked();
100             }
101         }
102     };
103 
104     private Region mExclusionRegion;
105     private SystemGestureExclusionListenerCompat mExclusionListener;
106 
107     private final List<ComponentName> mGestureBlockedActivities;
108 
109     private boolean mIsUserSetupComplete;
110 
RecentsAnimationDeviceState(Context context)111     public RecentsAnimationDeviceState(Context context) {
112         mContext = context;
113         mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
114         mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
115         mDisplayId = mDefaultDisplay.getInfo().id;
116         runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this));
117         mRotationTouchHelper = new RotationTouchHelper(context);
118         runOnDestroy(mRotationTouchHelper::destroy);
119 
120         // Register for user unlocked if necessary
121         mIsUserUnlocked = context.getSystemService(UserManager.class)
122                 .isUserUnlocked(Process.myUserHandle());
123         if (!mIsUserUnlocked) {
124             mContext.registerReceiver(mUserUnlockedReceiver,
125                     new IntentFilter(ACTION_USER_UNLOCKED));
126         }
127         runOnDestroy(() -> Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver));
128 
129         // Register for exclusion updates
130         mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
131             @Override
132             @BinderThread
133             public void onExclusionChanged(Region region) {
134                 // Assignments are atomic, it should be safe on binder thread
135                 mExclusionRegion = region;
136             }
137         };
138         runOnDestroy(mExclusionListener::unregister);
139 
140         // Register for navigation mode changes
141         onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
142         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
143 
144         // Add any blocked activities
145         String[] blockingActivities;
146         try {
147             blockingActivities =
148                     context.getResources().getStringArray(R.array.gesture_blocking_activities);
149         } catch (Resources.NotFoundException e) {
150             blockingActivities = new String[0];
151         }
152         mGestureBlockedActivities = new ArrayList<>(blockingActivities.length);
153         for (String blockingActivity : blockingActivities) {
154             if (!TextUtils.isEmpty(blockingActivity)) {
155                 mGestureBlockedActivities.add(
156                         ComponentName.unflattenFromString(blockingActivity));
157             }
158         }
159 
160         SecureSettingsObserver userSetupObserver = new SecureSettingsObserver(
161                 context.getContentResolver(),
162                 e -> mIsUserSetupComplete = e,
163                 Settings.Secure.USER_SETUP_COMPLETE,
164                 0);
165         mIsUserSetupComplete = userSetupObserver.getValue();
166         if (!mIsUserSetupComplete) {
167             userSetupObserver.register();
168             runOnDestroy(userSetupObserver::unregister);
169         }
170     }
171 
runOnDestroy(Runnable action)172     private void runOnDestroy(Runnable action) {
173         mOnDestroyActions.add(action);
174     }
175 
176     /**
177      * Cleans up all the registered listeners and receivers.
178      */
destroy()179     public void destroy() {
180         for (Runnable r : mOnDestroyActions) {
181             r.run();
182         }
183     }
184 
185     /**
186      * Adds a listener for the nav mode change, guaranteed to be called after the device state's
187      * mode has changed.
188      */
addNavigationModeChangedCallback(NavigationModeChangeListener listener)189     public void addNavigationModeChangedCallback(NavigationModeChangeListener listener) {
190         listener.onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(listener));
191         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener));
192     }
193 
194     @Override
onNavigationModeChanged(SysUINavigationMode.Mode newMode)195     public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
196         mDefaultDisplay.removeChangeListener(this);
197         mDefaultDisplay.addChangeListener(this);
198         onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
199 
200         if (newMode == NO_BUTTON) {
201             mExclusionListener.register();
202         } else {
203             mExclusionListener.unregister();
204         }
205 
206         mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
207 
208         mMode = newMode;
209     }
210 
211     @Override
onDisplayInfoChanged(DefaultDisplay.Info info, int flags)212     public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
213         if (info.id != getDisplayId() || flags == CHANGE_FRAME_DELAY) {
214             // ignore displays that aren't running launcher and frame refresh rate changes
215             return;
216         }
217 
218         if (!mMode.hasGestures) {
219             return;
220         }
221         mNavBarPosition = new NavBarPosition(mMode, info);
222     }
223 
224     /**
225      * @return the current navigation mode for the device.
226      */
getNavMode()227     public SysUINavigationMode.Mode getNavMode() {
228         return mMode;
229     }
230 
231     /**
232      * @return the nav bar position for the current nav bar mode and display rotation.
233      */
getNavBarPosition()234     public NavBarPosition getNavBarPosition() {
235         return mNavBarPosition;
236     }
237 
238     /**
239      * @return whether the current nav mode is fully gestural.
240      */
isFullyGesturalNavMode()241     public boolean isFullyGesturalNavMode() {
242         return mMode == NO_BUTTON;
243     }
244 
245     /**
246      * @return whether the current nav mode has some gestures (either 2 or 0 button mode).
247      */
isGesturalNavMode()248     public boolean isGesturalNavMode() {
249         return mMode == TWO_BUTTONS || mMode == NO_BUTTON;
250     }
251 
252     /**
253      * @return whether the current nav mode is button-based.
254      */
isButtonNavMode()255     public boolean isButtonNavMode() {
256         return mMode == THREE_BUTTONS;
257     }
258 
259     /**
260      * @return the display id for the display that Launcher is running on.
261      */
getDisplayId()262     public int getDisplayId() {
263         return mDisplayId;
264     }
265 
266     /**
267      * Adds a callback for when a user is unlocked. If the user is already unlocked, this listener
268      * will be called back immediately.
269      */
runOnUserUnlocked(Runnable action)270     public void runOnUserUnlocked(Runnable action) {
271         if (mIsUserUnlocked) {
272             action.run();
273         } else {
274             mUserUnlockedActions.add(action);
275         }
276     }
277 
278     /**
279      * @return whether the user is unlocked.
280      */
isUserUnlocked()281     public boolean isUserUnlocked() {
282         return mIsUserUnlocked;
283     }
284 
285     /**
286      * @return whether the user has completed setup wizard
287      */
isUserSetupComplete()288     public boolean isUserSetupComplete() {
289         return mIsUserSetupComplete;
290     }
291 
notifyUserUnlocked()292     private void notifyUserUnlocked() {
293         for (Runnable action : mUserUnlockedActions) {
294             action.run();
295         }
296         mUserUnlockedActions.clear();
297         Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver);
298     }
299 
300     /**
301      * @return whether the given running task info matches the gesture-blocked activity.
302      */
isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo)303     public boolean isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo) {
304         return runningTaskInfo != null
305                 && mGestureBlockedActivities.contains(runningTaskInfo.topActivity);
306     }
307 
308     /**
309      * @return the packages of gesture-blocked activities.
310      */
getGestureBlockedActivityPackages()311     public List<String> getGestureBlockedActivityPackages() {
312         return mGestureBlockedActivities.stream().map(ComponentName::getPackageName)
313                 .collect(Collectors.toList());
314     }
315 
316     /**
317      * Updates the system ui state flags from SystemUI.
318      */
setSystemUiFlags(int stateFlags)319     public void setSystemUiFlags(int stateFlags) {
320         mSystemUiStateFlags = stateFlags;
321     }
322 
323     /**
324      * @return the system ui state flags.
325      */
326     // TODO(141886704): See if we can remove this
getSystemUiStateFlags()327     public @SystemUiStateFlags int getSystemUiStateFlags() {
328         return mSystemUiStateFlags;
329     }
330 
331     /**
332      * @return whether SystemUI is in a state where we can start a system gesture.
333      */
canStartSystemGesture()334     public boolean canStartSystemGesture() {
335         boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
336                 || mRotationTouchHelper.isTaskListFrozen();
337         return canStartWithNavHidden
338                 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
339                 && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
340                 && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
341                         || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
342     }
343 
344     /**
345      * @return whether the keyguard is showing and is occluded by an app showing above the keyguard
346      *         (like camera or maps)
347      */
isKeyguardShowingOccluded()348     public boolean isKeyguardShowingOccluded() {
349         return (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0;
350     }
351 
352     /**
353      * @return whether screen pinning is enabled and active
354      */
isScreenPinningActive()355     public boolean isScreenPinningActive() {
356         return (mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
357     }
358 
359     /**
360      * @return whether the bubble stack is expanded
361      */
isBubblesExpanded()362     public boolean isBubblesExpanded() {
363         return (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
364     }
365 
366     /**
367      * @return whether the global actions dialog is showing
368      */
isGlobalActionsShowing()369     public boolean isGlobalActionsShowing() {
370         return (mSystemUiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0;
371     }
372 
373     /**
374      * @return whether lock-task mode is active
375      */
isLockToAppActive()376     public boolean isLockToAppActive() {
377         return ActivityManagerWrapper.getInstance().isLockToAppActive();
378     }
379 
380     /**
381      * @return whether the accessibility menu is available.
382      */
isAccessibilityMenuAvailable()383     public boolean isAccessibilityMenuAvailable() {
384         return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
385     }
386 
387     /**
388      * @return whether the accessibility menu shortcut is available.
389      */
isAccessibilityMenuShortcutAvailable()390     public boolean isAccessibilityMenuShortcutAvailable() {
391         return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
392     }
393 
394     /**
395      * @return whether home is disabled (either by SUW/SysUI/device policy)
396      */
isHomeDisabled()397     public boolean isHomeDisabled() {
398         return (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
399     }
400 
401     /**
402      * @return whether overview is disabled (either by SUW/SysUI/device policy)
403      */
isOverviewDisabled()404     public boolean isOverviewDisabled() {
405         return (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
406     }
407 
408     /**
409      * Sets the region in screen space where the gestures should be deferred (ie. due to specific
410      * nav bar ui).
411      */
setDeferredGestureRegion(Region deferredGestureRegion)412     public void setDeferredGestureRegion(Region deferredGestureRegion) {
413         mDeferredGestureRegion.set(deferredGestureRegion);
414     }
415 
416     /**
417      * @return whether the given {@param event} is in the deferred gesture region indicating that
418      *         the Launcher should not immediately start the recents animation until the gesture
419      *         passes a certain threshold.
420      */
isInDeferredGestureRegion(MotionEvent event)421     public boolean isInDeferredGestureRegion(MotionEvent event) {
422         return mDeferredGestureRegion.contains((int) event.getX(), (int) event.getY());
423     }
424 
425     /**
426      * @return whether the given {@param event} is in the app-requested gesture-exclusion region.
427      *         This is only used for quickswitch, and not swipe up.
428      */
isInExclusionRegion(MotionEvent event)429     public boolean isInExclusionRegion(MotionEvent event) {
430         // mExclusionRegion can change on binder thread, use a local instance here.
431         Region exclusionRegion = mExclusionRegion;
432         return mMode == NO_BUTTON && exclusionRegion != null
433                 && exclusionRegion.contains((int) event.getX(), (int) event.getY());
434     }
435 
436     /**
437      * Sets whether the assistant is available.
438      */
setAssistantAvailable(boolean assistantAvailable)439     public void setAssistantAvailable(boolean assistantAvailable) {
440         mAssistantAvailable = assistantAvailable;
441     }
442 
443     /**
444      * Sets the visibility fraction of the assistant.
445      */
setAssistantVisibility(float visibility)446     public void setAssistantVisibility(float visibility) {
447         mAssistantVisibility = visibility;
448     }
449 
450     /**
451      * @return the visibility fraction of the assistant.
452      */
getAssistantVisibility()453     public float getAssistantVisibility() {
454         return mAssistantVisibility;
455     }
456 
457     /**
458      * @param ev An ACTION_DOWN motion event
459      * @param task Info for the currently running task
460      * @return whether the given motion event can trigger the assistant over the current task.
461      */
canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task)462     public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) {
463         return mAssistantAvailable
464                 && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
465                 && mRotationTouchHelper.touchInAssistantRegion(ev)
466                 && !isLockToAppActive()
467                 && !isGestureBlockedActivity(task);
468     }
469 
getRotationTouchHelper()470     public RotationTouchHelper getRotationTouchHelper() {
471         return mRotationTouchHelper;
472     }
473 
dump(PrintWriter pw)474     public void dump(PrintWriter pw) {
475         pw.println("DeviceState:");
476         pw.println("  canStartSystemGesture=" + canStartSystemGesture());
477         pw.println("  systemUiFlags=" + mSystemUiStateFlags);
478         pw.println("  systemUiFlagsDesc="
479                 + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags));
480         pw.println("  assistantAvailable=" + mAssistantAvailable);
481         pw.println("  assistantDisabled="
482                 + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
483         pw.println("  isUserUnlocked=" + mIsUserUnlocked);
484         mRotationTouchHelper.dump(pw);
485     }
486 }
487