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