blob: a424674ed252ceb02e296a45986e41a8d9e84337 [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.keyguard;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.text.TextUtils;
import androidx.annotation.IntDef;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Rotates through messages to show on the keyguard bottom area on the lock screen
* NOTE: This controller should not be used on AoD to avoid waking up the AP too often.
*/
public class KeyguardIndicationRotateTextViewController extends
ViewController<KeyguardIndicationTextView> implements Dumpable {
public static String TAG = "KgIndicationRotatingCtrl";
private static final long DEFAULT_INDICATION_SHOW_LENGTH = 3500; // milliseconds
private final StatusBarStateController mStatusBarStateController;
private final float mMaxAlpha;
private final ColorStateList mInitialTextColorState;
// Stores @IndicationType => KeyguardIndication messages
private final Map<Integer, KeyguardIndication> mIndicationMessages = new HashMap<>();
// Executor that will show the next message after a delay
private final DelayableExecutor mExecutor;
@Nullable private ShowNextIndication mShowNextIndicationRunnable;
// List of indication types to show. The next indication to show is always at index 0
private final List<Integer> mIndicationQueue = new LinkedList<>();
private @IndicationType int mCurrIndicationType = INDICATION_TYPE_NONE;
private boolean mIsDozing;
public KeyguardIndicationRotateTextViewController(
KeyguardIndicationTextView view,
@Main DelayableExecutor executor,
StatusBarStateController statusBarStateController
) {
super(view);
mMaxAlpha = view.getAlpha();
mExecutor = executor;
mInitialTextColorState = mView != null
? mView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
mStatusBarStateController = statusBarStateController;
init();
}
@Override
protected void onViewAttached() {
mStatusBarStateController.addCallback(mStatusBarStateListener);
}
@Override
protected void onViewDetached() {
mStatusBarStateController.removeCallback(mStatusBarStateListener);
cancelScheduledIndication();
}
/**
* Update the indication type with the given String.
* @param type of indication
* @param newIndication message to associate with this indication type
* @param showImmediately if true: shows this indication message immediately. Else, the text
* associated with this type is updated and will show when its turn in
* the IndicationQueue comes around.
*/
public void updateIndication(@IndicationType int type, KeyguardIndication newIndication,
boolean updateImmediately) {
if (type == INDICATION_TYPE_REVERSE_CHARGING) {
// temporarily don't show here, instead use AmbientContainer b/181049781
return;
}
final boolean hasPreviousIndication = mIndicationMessages.get(type) != null;
final boolean hasNewIndication = newIndication != null;
if (!hasNewIndication) {
mIndicationMessages.remove(type);
mIndicationQueue.removeIf(x -> x == type);
} else {
if (!hasPreviousIndication) {
mIndicationQueue.add(type);
}
mIndicationMessages.put(type, newIndication);
}
if (mIsDozing) {
return;
}
final boolean showNow = updateImmediately
|| mCurrIndicationType == INDICATION_TYPE_NONE
|| mCurrIndicationType == type;
if (hasNewIndication) {
if (showNow) {
showIndication(type);
} else if (!isNextIndicationScheduled()) {
scheduleShowNextIndication();
}
return;
}
if (mCurrIndicationType == type
&& !hasNewIndication
&& updateImmediately) {
if (mShowNextIndicationRunnable != null) {
mShowNextIndicationRunnable.runImmediately();
} else {
showIndication(INDICATION_TYPE_NONE);
}
}
}
/**
* Stop showing the following indication type.
*
* If the current indication is of this type, immediately stops showing the message.
*/
public void hideIndication(@IndicationType int type) {
if (!mIndicationMessages.containsKey(type)
|| TextUtils.isEmpty(mIndicationMessages.get(type).getMessage())) {
return;
}
updateIndication(type, null, true);
}
/**
* Show a transient message.
* Transient messages:
* - show immediately
* - will continue to be in the rotation of messages shown until hideTransient is called.
*/
public void showTransient(CharSequence newIndication) {
final long inAnimationDuration = 600L; // see KeyguardIndicationTextView.getYInDuration
updateIndication(INDICATION_TYPE_TRANSIENT,
new KeyguardIndication.Builder()
.setMessage(newIndication)
.setMinVisibilityMillis(2000L + inAnimationDuration)
.setTextColor(mInitialTextColorState)
.build(),
/* showImmediately */true);
}
/**
* Hide a transient message immediately.
*/
public void hideTransient() {
hideIndication(INDICATION_TYPE_TRANSIENT);
}
/**
* @return true if there are available indications to show
*/
public boolean hasIndications() {
return mIndicationMessages.keySet().size() > 0;
}
/**
* Immediately show the passed indication type and schedule the next indication to show.
* Will re-add this indication to be re-shown after all other indications have been
* rotated through.
*/
private void showIndication(@IndicationType int type) {
cancelScheduledIndication();
mCurrIndicationType = type;
mIndicationQueue.removeIf(x -> x == type);
if (mCurrIndicationType != INDICATION_TYPE_NONE) {
mIndicationQueue.add(type); // re-add to show later
}
mView.switchIndication(mIndicationMessages.get(type));
// only schedule next indication if there's more than just this indication in the queue
if (mCurrIndicationType != INDICATION_TYPE_NONE && mIndicationQueue.size() > 1) {
scheduleShowNextIndication();
}
}
protected boolean isNextIndicationScheduled() {
return mShowNextIndicationRunnable != null;
}
private void scheduleShowNextIndication() {
cancelScheduledIndication();
mShowNextIndicationRunnable = new ShowNextIndication(DEFAULT_INDICATION_SHOW_LENGTH);
}
private void cancelScheduledIndication() {
if (mShowNextIndicationRunnable != null) {
mShowNextIndicationRunnable.cancelDelayedExecution();
mShowNextIndicationRunnable = null;
}
}
private StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onDozeAmountChanged(float linear, float eased) {
mView.setAlpha((1 - linear) * mMaxAlpha);
}
@Override
public void onDozingChanged(boolean isDozing) {
if (isDozing == mIsDozing) return;
mIsDozing = isDozing;
if (mIsDozing) {
showIndication(INDICATION_TYPE_NONE);
} else if (mIndicationQueue.size() > 0) {
showIndication(mIndicationQueue.remove(0));
}
}
};
/**
* Shows the next indication in the IndicationQueue after an optional delay.
* This wrapper has the ability to cancel itself (remove runnable from DelayableExecutor) or
* immediately run itself (which also removes itself from the DelayableExecutor).
*/
class ShowNextIndication {
private final Runnable mShowIndicationRunnable;
private Runnable mCancelDelayedRunnable;
ShowNextIndication(long delay) {
mShowIndicationRunnable = () -> {
int type = mIndicationQueue.size() == 0
? INDICATION_TYPE_NONE : mIndicationQueue.remove(0);
showIndication(type);
};
mCancelDelayedRunnable = mExecutor.executeDelayed(mShowIndicationRunnable, delay);
}
public void runImmediately() {
cancelDelayedExecution();
mShowIndicationRunnable.run();
}
public void cancelDelayedExecution() {
if (mCancelDelayedRunnable != null) {
mCancelDelayedRunnable.run();
mCancelDelayedRunnable = null;
}
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("KeyguardIndicationRotatingTextViewController:");
pw.println(" currentMessage=" + mView.getText());
pw.println(" dozing:" + mIsDozing);
pw.println(" queue:" + mIndicationQueue.toString());
pw.println(" showNextIndicationRunnable:" + mShowNextIndicationRunnable);
if (hasIndications()) {
pw.println(" All messages:");
for (int type : mIndicationMessages.keySet()) {
pw.println(" type=" + type + " " + mIndicationMessages.get(type));
}
}
}
static final int INDICATION_TYPE_NONE = -1;
public static final int INDICATION_TYPE_OWNER_INFO = 0;
public static final int INDICATION_TYPE_DISCLOSURE = 1;
public static final int INDICATION_TYPE_LOGOUT = 2;
public static final int INDICATION_TYPE_BATTERY = 3;
public static final int INDICATION_TYPE_ALIGNMENT = 4;
public static final int INDICATION_TYPE_TRANSIENT = 5;
public static final int INDICATION_TYPE_TRUST = 6;
public static final int INDICATION_TYPE_RESTING = 7;
public static final int INDICATION_TYPE_USER_LOCKED = 8;
public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
@IntDef({
INDICATION_TYPE_NONE,
INDICATION_TYPE_DISCLOSURE,
INDICATION_TYPE_OWNER_INFO,
INDICATION_TYPE_LOGOUT,
INDICATION_TYPE_BATTERY,
INDICATION_TYPE_ALIGNMENT,
INDICATION_TYPE_TRANSIENT,
INDICATION_TYPE_TRUST,
INDICATION_TYPE_RESTING,
INDICATION_TYPE_USER_LOCKED,
INDICATION_TYPE_REVERSE_CHARGING,
})
@Retention(RetentionPolicy.SOURCE)
public @interface IndicationType{}
}