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