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