blob: 69e41ba9b284bb7e1ca3cdbf1105fad17f9fb0e7 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.dreams;
import android.service.dreams.DreamService;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.statusbar.policy.CallbackController;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.inject.Inject;
/**
* {@link DreamOverlayStateController} is the source of truth for Dream overlay configurations and
* state. Clients can register as listeners for changes to the overlay composition and can query for
* the complications on-demand.
*/
@SysUISingleton
public class DreamOverlayStateController implements
CallbackController<DreamOverlayStateController.Callback> {
private static final String TAG = "DreamOverlayStateCtlr";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
private int mState;
/**
* Callback for dream overlay events.
*/
public interface Callback {
/**
* Called when the composition of complications changes.
*/
default void onComplicationsChanged() {
}
/**
* Called when the dream overlay state changes.
*/
default void onStateChanged() {
}
/**
* Called when the available complication types changes.
*/
default void onAvailableComplicationTypesChanged() {
}
}
private final Executor mExecutor;
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
@Complication.ComplicationType
private int mAvailableComplicationTypes = Complication.COMPLICATION_TYPE_NONE;
private boolean mShouldShowComplications = DreamService.DEFAULT_SHOW_COMPLICATIONS;
private final Collection<Complication> mComplications = new HashSet();
@VisibleForTesting
@Inject
public DreamOverlayStateController(@Main Executor executor) {
mExecutor = executor;
}
/**
* Adds a complication to be included on the dream overlay.
*/
public void addComplication(Complication complication) {
mExecutor.execute(() -> {
if (mComplications.add(complication)) {
if (DEBUG) {
Log.d(TAG, "addComplication: added " + complication);
}
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
}
/**
* Removes a complication from inclusion on the dream overlay.
*/
public void removeComplication(Complication complication) {
mExecutor.execute(() -> {
if (mComplications.remove(complication)) {
if (DEBUG) {
Log.d(TAG, "removeComplication: removed " + complication);
}
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
}
/**
* Returns collection of present {@link Complication}.
*/
public Collection<Complication> getComplications() {
return getComplications(true);
}
/**
* Returns collection of present {@link Complication}.
*/
public Collection<Complication> getComplications(boolean filterByAvailability) {
return Collections.unmodifiableCollection(filterByAvailability
? mComplications
.stream()
.filter(complication -> {
@Complication.ComplicationType
final int requiredTypes = complication.getRequiredTypeAvailability();
// If it should show complications, show ones whose required types are
// available. Otherwise, only show ones that don't require types.
if (mShouldShowComplications) {
return (requiredTypes & getAvailableComplicationTypes()) == requiredTypes;
}
return requiredTypes == Complication.COMPLICATION_TYPE_NONE;
})
.collect(Collectors.toCollection(HashSet::new))
: mComplications);
}
private void notifyCallbacks(Consumer<Callback> callbackConsumer) {
mExecutor.execute(() -> {
for (Callback callback : mCallbacks) {
callbackConsumer.accept(callback);
}
});
}
@Override
public void addCallback(@NonNull Callback callback) {
mExecutor.execute(() -> {
Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
if (mCallbacks.contains(callback)) {
return;
}
mCallbacks.add(callback);
if (mComplications.isEmpty()) {
return;
}
callback.onComplicationsChanged();
});
}
@Override
public void removeCallback(@NonNull Callback callback) {
mExecutor.execute(() -> {
Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
mCallbacks.remove(callback);
});
}
/**
* Returns whether the overlay is active.
* @return {@code true} if overlay is active, {@code false} otherwise.
*/
public boolean isOverlayActive() {
return containsState(STATE_DREAM_OVERLAY_ACTIVE);
}
private boolean containsState(int state) {
return (mState & state) != 0;
}
private void modifyState(int op, int state) {
final int existingState = mState;
switch (op) {
case OP_CLEAR_STATE:
mState &= ~state;
break;
case OP_SET_STATE:
mState |= state;
break;
}
if (existingState != mState) {
notifyCallbacks(callback -> callback.onStateChanged());
}
}
/**
* Sets whether the overlay is active.
* @param active {@code true} if overlay is active, {@code false} otherwise.
*/
public void setOverlayActive(boolean active) {
modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
}
/**
* Returns the available complication types.
*/
@Complication.ComplicationType
public int getAvailableComplicationTypes() {
return mAvailableComplicationTypes;
}
/**
* Sets the available complication types for the dream overlay.
*/
public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
mExecutor.execute(() -> {
mAvailableComplicationTypes = types;
mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
});
}
/**
* Returns whether the dream overlay should show complications.
*/
public boolean getShouldShowComplications() {
return mShouldShowComplications;
}
/**
* Sets whether the dream overlay should show complications.
*/
public void setShouldShowComplications(boolean shouldShowComplications) {
mExecutor.execute(() -> {
mShouldShowComplications = shouldShowComplications;
mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
});
}
}