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