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