• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 com.android.launcher3.Utilities.postAsyncCallback;
19 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
20 
21 import android.os.Looper;
22 import android.util.Log;
23 import android.util.SparseArray;
24 
25 import androidx.annotation.NonNull;
26 import androidx.annotation.Nullable;
27 
28 import com.android.launcher3.config.FeatureFlags;
29 import com.android.quickstep.util.ActiveGestureErrorDetector;
30 import com.android.quickstep.util.ActiveGestureLog;
31 
32 import java.util.ArrayList;
33 import java.util.LinkedList;
34 import java.util.StringJoiner;
35 import java.util.function.Consumer;
36 
37 /**
38  * Utility class to help manage multiple callbacks based on different states.
39  */
40 public class MultiStateCallback {
41 
42     private static final String TAG = "MultiStateCallback";
43     public static final boolean DEBUG_STATES = false;
44 
45     private final SparseArray<LinkedList<Runnable>> mCallbacks = new SparseArray<>();
46     private final SparseArray<ArrayList<Consumer<Boolean>>> mStateChangeListeners =
47             new SparseArray<>();
48 
49     @NonNull private final TrackedEventsMapper mTrackedEventsMapper;
50 
51     private final String[] mStateNames;
52 
53     private int mState = 0;
54 
MultiStateCallback(String[] stateNames)55     public MultiStateCallback(String[] stateNames) {
56         this(stateNames, stateFlag -> null);
57     }
58 
MultiStateCallback( String[] stateNames, @NonNull TrackedEventsMapper trackedEventsMapper)59     public MultiStateCallback(
60             String[] stateNames,
61             @NonNull TrackedEventsMapper trackedEventsMapper) {
62         mStateNames = DEBUG_STATES ? stateNames : null;
63         mTrackedEventsMapper = trackedEventsMapper;
64     }
65 
66     /**
67      * Adds the provided state flags to the global state on the UI thread and executes any callbacks
68      * as a result.
69      *
70      * Also tracks the provided gesture events for error detection. Each provided event must be
71      * associated with one provided state flag.
72      */
setStateOnUiThread(int stateFlag)73     public void setStateOnUiThread(int stateFlag) {
74         if (Looper.myLooper() == Looper.getMainLooper()) {
75             setState(stateFlag);
76         } else {
77             postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> setState(stateFlag));
78         }
79     }
80 
81     /**
82      * Adds the provided state flags to the global state and executes any callbacks as a result.
83      */
setState(int stateFlag)84     public void setState(int stateFlag) {
85         if (DEBUG_STATES) {
86             Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding "
87                     + convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState));
88         }
89         if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) {
90             trackGestureEvents(stateFlag);
91         }
92         final int oldState = mState;
93         mState = mState | stateFlag;
94 
95         int count = mCallbacks.size();
96         for (int i = 0; i < count; i++) {
97             int state = mCallbacks.keyAt(i);
98 
99             if ((mState & state) == state) {
100                 LinkedList<Runnable> callbacks = mCallbacks.valueAt(i);
101                 while (!callbacks.isEmpty()) {
102                     callbacks.pollFirst().run();
103                 }
104             }
105         }
106         notifyStateChangeListeners(oldState);
107     }
108 
trackGestureEvents(int stateFlags)109     private void trackGestureEvents(int stateFlags) {
110         for (int index = 0; (stateFlags >> index) != 0; index++) {
111             if ((stateFlags & (1 << index)) == 0) {
112                 continue;
113             }
114             ActiveGestureErrorDetector.GestureEvent gestureEvent =
115                     mTrackedEventsMapper.getTrackedEventForState(1 << index);
116             if (gestureEvent == null) {
117                 continue;
118             }
119             if (gestureEvent.mLogEvent && gestureEvent.mTrackEvent) {
120                 ActiveGestureLog.INSTANCE.addLog(gestureEvent.name(), gestureEvent);
121             } else if (gestureEvent.mLogEvent) {
122                 ActiveGestureLog.INSTANCE.addLog(gestureEvent.name());
123             } else if (gestureEvent.mTrackEvent) {
124                 ActiveGestureLog.INSTANCE.trackEvent(gestureEvent);
125             }
126         }
127     }
128 
129     /**
130      * Adds the provided state flags to the global state and executes any change handlers
131      * as a result.
132      */
clearState(int stateFlag)133     public void clearState(int stateFlag) {
134         if (DEBUG_STATES) {
135             Log.d(TAG, "[" + System.identityHashCode(this) + "] Removing "
136                     + convertToFlagNames(stateFlag) + " from " + convertToFlagNames(mState));
137         }
138 
139         int oldState = mState;
140         mState = mState & ~stateFlag;
141         notifyStateChangeListeners(oldState);
142     }
143 
notifyStateChangeListeners(int oldState)144     private void notifyStateChangeListeners(int oldState) {
145         int count = mStateChangeListeners.size();
146         for (int i = 0; i < count; i++) {
147             int state = mStateChangeListeners.keyAt(i);
148             boolean wasOn = (state & oldState) == state;
149             boolean isOn = (state & mState) == state;
150 
151             if (wasOn != isOn) {
152                 ArrayList<Consumer<Boolean>> listeners = mStateChangeListeners.valueAt(i);
153                 for (Consumer<Boolean> listener : listeners) {
154                     listener.accept(isOn);
155                 }
156             }
157         }
158     }
159 
160     /**
161      * Sets a callback to be run when the provided states in the given {@param stateMask} is
162      * enabled. The callback is only run *once*, and if the states are already set at the time of
163      * this call then the callback will be made immediately.
164      */
runOnceAtState(int stateMask, Runnable callback)165     public void runOnceAtState(int stateMask, Runnable callback) {
166         if ((mState & stateMask) == stateMask) {
167             callback.run();
168         } else {
169             final LinkedList<Runnable> callbacks;
170             if (mCallbacks.indexOfKey(stateMask) >= 0) {
171                 callbacks = mCallbacks.get(stateMask);
172                 if (FeatureFlags.IS_STUDIO_BUILD && callbacks.contains(callback)) {
173                     throw new IllegalStateException("Existing callback for state found");
174                 }
175             } else {
176                 callbacks = new LinkedList<>();
177                 mCallbacks.put(stateMask, callbacks);
178             }
179             callbacks.add(callback);
180         }
181     }
182 
183     /**
184      * Adds a persistent listener to be called states in the given {@param stateMask} are enabled
185      * or disabled.
186      */
addChangeListener(int stateMask, Consumer<Boolean> listener)187     public void addChangeListener(int stateMask, Consumer<Boolean> listener) {
188         final ArrayList<Consumer<Boolean>> listeners;
189         if (mStateChangeListeners.indexOfKey(stateMask) >= 0) {
190             listeners = mStateChangeListeners.get(stateMask);
191         } else {
192             listeners = new ArrayList<>();
193             mStateChangeListeners.put(stateMask, listeners);
194         }
195         listeners.add(listener);
196     }
197 
getState()198     public int getState() {
199         return mState;
200     }
201 
hasStates(int stateMask)202     public boolean hasStates(int stateMask) {
203         return (mState & stateMask) == stateMask;
204     }
205 
convertToFlagNames(int flags)206     private String convertToFlagNames(int flags) {
207         StringJoiner joiner = new StringJoiner(", ", "[", " (" + flags + ")]");
208         for (int i = 0; i < mStateNames.length; i++) {
209             if ((flags & (1 << i)) != 0) {
210                 joiner.add(mStateNames[i]);
211             }
212         }
213         return joiner.toString();
214     }
215 
216     public interface TrackedEventsMapper {
getTrackedEventForState(int stateflag)217         @Nullable ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateflag);
218     }
219 }
220