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