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