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