• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.dreams;
18 
19 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_ENABLED;
20 
21 import android.service.dreams.DreamService;
22 import android.util.Log;
23 
24 import androidx.annotation.NonNull;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.systemui.dagger.SysUISingleton;
28 import com.android.systemui.dagger.qualifiers.Main;
29 import com.android.systemui.dreams.complication.Complication;
30 import com.android.systemui.flags.FeatureFlags;
31 import com.android.systemui.flags.Flags;
32 import com.android.systemui.statusbar.policy.CallbackController;
33 
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.HashSet;
38 import java.util.Objects;
39 import java.util.concurrent.Executor;
40 import java.util.function.Consumer;
41 import java.util.stream.Collectors;
42 
43 import javax.inject.Inject;
44 import javax.inject.Named;
45 
46 /**
47  * {@link DreamOverlayStateController} is the source of truth for Dream overlay configurations and
48  * state. Clients can register as listeners for changes to the overlay composition and can query for
49  * the complications on-demand.
50  */
51 @SysUISingleton
52 public class DreamOverlayStateController implements
53         CallbackController<DreamOverlayStateController.Callback> {
54     private static final String TAG = "DreamOverlayStateCtlr";
55     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
56 
57     public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
58     public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
59     public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
60     public static final int STATE_DREAM_EXIT_ANIMATIONS_RUNNING = 1 << 3;
61 
62     private static final int OP_CLEAR_STATE = 1;
63     private static final int OP_SET_STATE = 2;
64 
65     private int mState;
66 
67     /**
68      * Callback for dream overlay events.
69      */
70     public interface Callback {
71         /**
72          * Called when the composition of complications changes.
73          */
onComplicationsChanged()74         default void onComplicationsChanged() {
75         }
76 
77         /**
78          * Called when the dream overlay state changes.
79          */
onStateChanged()80         default void onStateChanged() {
81         }
82 
83         /**
84          * Called when the available complication types changes.
85          */
onAvailableComplicationTypesChanged()86         default void onAvailableComplicationTypesChanged() {
87         }
88 
89         /**
90          * Called when the low light dream is exiting and transitioning back to the user dream.
91          */
onExitLowLight()92         default void onExitLowLight() {
93         }
94     }
95 
96     private final Executor mExecutor;
97     private final boolean mOverlayEnabled;
98     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
99 
100     @Complication.ComplicationType
101     private int mAvailableComplicationTypes = Complication.COMPLICATION_TYPE_NONE;
102 
103     private boolean mShouldShowComplications = DreamService.DEFAULT_SHOW_COMPLICATIONS;
104 
105     private final Collection<Complication> mComplications = new HashSet();
106 
107     private final FeatureFlags mFeatureFlags;
108 
109     private final int mSupportedTypes;
110 
111     @VisibleForTesting
112     @Inject
DreamOverlayStateController(@ain Executor executor, @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled, FeatureFlags featureFlags)113     public DreamOverlayStateController(@Main Executor executor,
114             @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled,
115             FeatureFlags featureFlags) {
116         mExecutor = executor;
117         mOverlayEnabled = overlayEnabled;
118         mFeatureFlags = featureFlags;
119         if (DEBUG) {
120             Log.d(TAG, "Dream overlay enabled:" + mOverlayEnabled);
121         }
122         if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) {
123             mSupportedTypes = Complication.COMPLICATION_TYPE_NONE
124                     | Complication.COMPLICATION_TYPE_HOME_CONTROLS;
125         } else {
126             mSupportedTypes = Complication.COMPLICATION_TYPE_NONE;
127         }
128     }
129 
130     /**
131      * Adds a complication to be included on the dream overlay.
132      */
addComplication(Complication complication)133     public void addComplication(Complication complication) {
134         if (!mOverlayEnabled) {
135             if (DEBUG) {
136                 Log.d(TAG,
137                         "Ignoring adding complication due to overlay disabled:" + complication);
138             }
139             return;
140         }
141 
142         mExecutor.execute(() -> {
143             if (mComplications.add(complication)) {
144                 if (DEBUG) {
145                     Log.d(TAG, "addComplication: added " + complication);
146                 }
147                 mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
148             }
149         });
150     }
151 
152     /**
153      * Removes a complication from inclusion on the dream overlay.
154      */
removeComplication(Complication complication)155     public void removeComplication(Complication complication) {
156         if (!mOverlayEnabled) {
157             if (DEBUG) {
158                 Log.d(TAG,
159                         "Ignoring removing complication due to overlay disabled:" + complication);
160             }
161             return;
162         }
163 
164         mExecutor.execute(() -> {
165             if (mComplications.remove(complication)) {
166                 if (DEBUG) {
167                     Log.d(TAG, "removeComplication: removed " + complication);
168                 }
169                 mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
170             }
171         });
172     }
173 
174     /**
175      * Returns collection of present {@link Complication}.
176      */
getComplications()177     public Collection<Complication> getComplications() {
178         return getComplications(true);
179     }
180 
181     /**
182      * Returns collection of present {@link Complication}.
183      */
getComplications(boolean filterByAvailability)184     public Collection<Complication> getComplications(boolean filterByAvailability) {
185         if (isLowLightActive()) {
186             // Don't show complications on low light.
187             return Collections.emptyList();
188         }
189         return Collections.unmodifiableCollection(filterByAvailability
190                 ? mComplications
191                 .stream()
192                 .filter(complication -> {
193                     @Complication.ComplicationType
194                     final int requiredTypes = complication.getRequiredTypeAvailability();
195                     // If it should show complications, show ones whose required types are
196                     // available. Otherwise, only show ones that don't require types.
197                     if (mShouldShowComplications) {
198                         return (requiredTypes & getAvailableComplicationTypes()) == requiredTypes;
199                     }
200                     final int typesToAlwaysShow = mSupportedTypes & getAvailableComplicationTypes();
201                     return (requiredTypes & typesToAlwaysShow) == requiredTypes;
202                 })
203                 .collect(Collectors.toCollection(HashSet::new))
204                 : mComplications);
205     }
206 
notifyCallbacks(Consumer<Callback> callbackConsumer)207     private void notifyCallbacks(Consumer<Callback> callbackConsumer) {
208         mExecutor.execute(() -> {
209             for (Callback callback : mCallbacks) {
210                 callbackConsumer.accept(callback);
211             }
212         });
213     }
214 
215     @Override
addCallback(@onNull Callback callback)216     public void addCallback(@NonNull Callback callback) {
217         mExecutor.execute(() -> {
218             Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
219             if (mCallbacks.contains(callback)) {
220                 return;
221             }
222 
223             mCallbacks.add(callback);
224 
225             if (mComplications.isEmpty()) {
226                 return;
227             }
228 
229             callback.onComplicationsChanged();
230         });
231     }
232 
233     @Override
removeCallback(@onNull Callback callback)234     public void removeCallback(@NonNull Callback callback) {
235         mExecutor.execute(() -> {
236             Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
237             mCallbacks.remove(callback);
238         });
239     }
240 
241     /**
242      * Returns whether the overlay is active.
243      * @return {@code true} if overlay is active, {@code false} otherwise.
244      */
isOverlayActive()245     public boolean isOverlayActive() {
246         return mOverlayEnabled && containsState(STATE_DREAM_OVERLAY_ACTIVE);
247     }
248 
249     /**
250      * Returns whether low light mode is active.
251      * @return {@code true} if in low light mode, {@code false} otherwise.
252      */
isLowLightActive()253     public boolean isLowLightActive() {
254         return containsState(STATE_LOW_LIGHT_ACTIVE);
255     }
256 
257     /**
258      * Returns whether the dream content and dream overlay entry animations are finished.
259      * @return {@code true} if animations are finished, {@code false} otherwise.
260      */
areEntryAnimationsFinished()261     public boolean areEntryAnimationsFinished() {
262         return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
263     }
264 
265     /**
266      * Returns whether the dream content and dream overlay exit animations are running.
267      * @return {@code true} if animations are running, {@code false} otherwise.
268      */
areExitAnimationsRunning()269     public boolean areExitAnimationsRunning() {
270         return containsState(STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
271     }
272 
containsState(int state)273     private boolean containsState(int state) {
274         return (mState & state) != 0;
275     }
276 
modifyState(int op, int state)277     private void modifyState(int op, int state) {
278         final int existingState = mState;
279         switch (op) {
280             case OP_CLEAR_STATE:
281                 mState &= ~state;
282                 break;
283             case OP_SET_STATE:
284                 mState |= state;
285                 break;
286         }
287 
288         if (existingState != mState) {
289             notifyCallbacks(Callback::onStateChanged);
290         }
291     }
292 
293     /**
294      * Sets whether the overlay is active.
295      * @param active {@code true} if overlay is active, {@code false} otherwise.
296      */
setOverlayActive(boolean active)297     public void setOverlayActive(boolean active) {
298         modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
299     }
300 
301     /**
302      * Sets whether low light mode is active.
303      * @param active {@code true} if low light mode is active, {@code false} otherwise.
304      */
setLowLightActive(boolean active)305     public void setLowLightActive(boolean active) {
306         if (isLowLightActive() && !active) {
307             // Notify that we're exiting low light only on the transition from active to not active.
308             mCallbacks.forEach(Callback::onExitLowLight);
309         }
310         modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_LOW_LIGHT_ACTIVE);
311     }
312 
313     /**
314      * Sets whether dream content and dream overlay entry animations are finished.
315      * @param finished {@code true} if entry animations are finished, {@code false} otherwise.
316      */
setEntryAnimationsFinished(boolean finished)317     public void setEntryAnimationsFinished(boolean finished) {
318         modifyState(finished ? OP_SET_STATE : OP_CLEAR_STATE,
319                 STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
320     }
321 
322     /**
323      * Sets whether dream content and dream overlay exit animations are running.
324      * @param running {@code true} if exit animations are running, {@code false} otherwise.
325      */
setExitAnimationsRunning(boolean running)326     public void setExitAnimationsRunning(boolean running) {
327         modifyState(running ? OP_SET_STATE : OP_CLEAR_STATE,
328                 STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
329     }
330 
331     /**
332      * Returns the available complication types.
333      */
334     @Complication.ComplicationType
getAvailableComplicationTypes()335     public int getAvailableComplicationTypes() {
336         return mAvailableComplicationTypes;
337     }
338 
339     /**
340      * Sets the available complication types for the dream overlay.
341      */
setAvailableComplicationTypes(@omplication.ComplicationType int types)342     public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
343         mExecutor.execute(() -> {
344             mAvailableComplicationTypes = types;
345             mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
346         });
347     }
348 
349     /**
350      * Returns whether the dream overlay should show complications.
351      */
getShouldShowComplications()352     public boolean getShouldShowComplications() {
353         return mShouldShowComplications;
354     }
355 
356     /**
357      * Sets whether the dream overlay should show complications.
358      */
setShouldShowComplications(boolean shouldShowComplications)359     public void setShouldShowComplications(boolean shouldShowComplications) {
360         mExecutor.execute(() -> {
361             mShouldShowComplications = shouldShowComplications;
362             mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
363         });
364     }
365 }
366