• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.keyguard;
18 
19 import android.annotation.Nullable;
20 import android.content.res.ColorStateList;
21 import android.graphics.Color;
22 import android.text.TextUtils;
23 
24 import androidx.annotation.IntDef;
25 
26 import com.android.systemui.Dumpable;
27 import com.android.systemui.dagger.qualifiers.Main;
28 import com.android.systemui.plugins.statusbar.StatusBarStateController;
29 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
30 import com.android.systemui.util.ViewController;
31 import com.android.systemui.util.concurrency.DelayableExecutor;
32 
33 import java.io.FileDescriptor;
34 import java.io.PrintWriter;
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.HashMap;
38 import java.util.LinkedList;
39 import java.util.List;
40 import java.util.Map;
41 
42 /**
43  * Rotates through messages to show on the keyguard bottom area on the lock screen
44  * NOTE: This controller should not be used on AoD to avoid waking up the AP too often.
45  */
46 public class KeyguardIndicationRotateTextViewController extends
47         ViewController<KeyguardIndicationTextView> implements Dumpable {
48     public static String TAG = "KgIndicationRotatingCtrl";
49     private static final long DEFAULT_INDICATION_SHOW_LENGTH = 3500; // milliseconds
50 
51     private final StatusBarStateController mStatusBarStateController;
52     private final float mMaxAlpha;
53     private final ColorStateList mInitialTextColorState;
54 
55     // Stores @IndicationType => KeyguardIndication messages
56     private final Map<Integer, KeyguardIndication> mIndicationMessages = new HashMap<>();
57 
58     // Executor that will show the next message after a delay
59     private final DelayableExecutor mExecutor;
60     @Nullable private ShowNextIndication mShowNextIndicationRunnable;
61 
62     // List of indication types to show. The next indication to show is always at index 0
63     private final List<Integer> mIndicationQueue = new LinkedList<>();
64     private @IndicationType int mCurrIndicationType = INDICATION_TYPE_NONE;
65 
66     private boolean mIsDozing;
67 
KeyguardIndicationRotateTextViewController( KeyguardIndicationTextView view, @Main DelayableExecutor executor, StatusBarStateController statusBarStateController )68     public KeyguardIndicationRotateTextViewController(
69             KeyguardIndicationTextView view,
70             @Main DelayableExecutor executor,
71             StatusBarStateController statusBarStateController
72     ) {
73         super(view);
74         mMaxAlpha = view.getAlpha();
75         mExecutor = executor;
76         mInitialTextColorState = mView != null
77                 ? mView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
78         mStatusBarStateController = statusBarStateController;
79         init();
80     }
81 
82     @Override
onViewAttached()83     protected void onViewAttached() {
84         mStatusBarStateController.addCallback(mStatusBarStateListener);
85     }
86 
87     @Override
onViewDetached()88     protected void onViewDetached() {
89         mStatusBarStateController.removeCallback(mStatusBarStateListener);
90         cancelScheduledIndication();
91     }
92 
93     /**
94      * Update the indication type with the given String.
95      * @param type of indication
96      * @param newIndication message to associate with this indication type
97      * @param showImmediately if true: shows this indication message immediately. Else, the text
98      *                        associated with this type is updated and will show when its turn in
99      *                        the IndicationQueue comes around.
100      */
updateIndication(@ndicationType int type, KeyguardIndication newIndication, boolean updateImmediately)101     public void updateIndication(@IndicationType int type, KeyguardIndication newIndication,
102             boolean updateImmediately) {
103         if (type == INDICATION_TYPE_NOW_PLAYING
104                 || type == INDICATION_TYPE_REVERSE_CHARGING) {
105             // temporarily don't show here, instead use AmbientContainer b/181049781
106             return;
107         }
108         final boolean hasPreviousIndication = mIndicationMessages.get(type) != null;
109         final boolean hasNewIndication = newIndication != null;
110         if (!hasNewIndication) {
111             mIndicationMessages.remove(type);
112             mIndicationQueue.removeIf(x -> x == type);
113         } else {
114             if (!hasPreviousIndication) {
115                 mIndicationQueue.add(type);
116             }
117 
118             mIndicationMessages.put(type, newIndication);
119         }
120 
121         if (mIsDozing) {
122             return;
123         }
124 
125         final boolean showNow = updateImmediately
126                 || mCurrIndicationType == INDICATION_TYPE_NONE
127                 || mCurrIndicationType == type;
128         if (hasNewIndication) {
129             if (showNow) {
130                 showIndication(type);
131             } else if (!isNextIndicationScheduled()) {
132                 scheduleShowNextIndication();
133             }
134             return;
135         }
136 
137         if (mCurrIndicationType == type
138                 && !hasNewIndication
139                 && updateImmediately) {
140             if (mShowNextIndicationRunnable != null) {
141                 mShowNextIndicationRunnable.runImmediately();
142             } else {
143                 showIndication(INDICATION_TYPE_NONE);
144             }
145         }
146     }
147 
148     /**
149      * Stop showing the following indication type.
150      *
151      * If the current indication is of this type, immediately stops showing the message.
152      */
hideIndication(@ndicationType int type)153     public void hideIndication(@IndicationType int type) {
154         if (!mIndicationMessages.containsKey(type)
155                 || TextUtils.isEmpty(mIndicationMessages.get(type).getMessage())) {
156             return;
157         }
158         updateIndication(type, null, true);
159     }
160 
161     /**
162      * Show a transient message.
163      * Transient messages:
164      * - show immediately
165      * - will continue to be in the rotation of messages shown until hideTransient is called.
166      */
showTransient(CharSequence newIndication)167     public void showTransient(CharSequence newIndication) {
168         final long inAnimationDuration = 600L; // see KeyguardIndicationTextView.getYInDuration
169         updateIndication(INDICATION_TYPE_TRANSIENT,
170                 new KeyguardIndication.Builder()
171                         .setMessage(newIndication)
172                         .setMinVisibilityMillis(2000L + inAnimationDuration)
173                         .setTextColor(mInitialTextColorState)
174                         .build(),
175                 /* showImmediately */true);
176     }
177 
178     /**
179      * Hide a transient message immediately.
180      */
hideTransient()181     public void hideTransient() {
182         hideIndication(INDICATION_TYPE_TRANSIENT);
183     }
184 
185     /**
186      * @return true if there are available indications to show
187      */
hasIndications()188     public boolean hasIndications() {
189         return mIndicationMessages.keySet().size() > 0;
190     }
191 
192     /**
193      * Immediately show the passed indication type and schedule the next indication to show.
194      * Will re-add this indication to be re-shown after all other indications have been
195      * rotated through.
196      */
showIndication(@ndicationType int type)197     private void showIndication(@IndicationType int type) {
198         cancelScheduledIndication();
199 
200         mCurrIndicationType = type;
201         mIndicationQueue.removeIf(x -> x == type);
202         if (mCurrIndicationType != INDICATION_TYPE_NONE) {
203             mIndicationQueue.add(type); // re-add to show later
204         }
205 
206         mView.switchIndication(mIndicationMessages.get(type));
207 
208         // only schedule next indication if there's more than just this indication in the queue
209         if (mCurrIndicationType != INDICATION_TYPE_NONE && mIndicationQueue.size() > 1) {
210             scheduleShowNextIndication();
211         }
212     }
213 
isNextIndicationScheduled()214     protected boolean isNextIndicationScheduled() {
215         return mShowNextIndicationRunnable != null;
216     }
217 
scheduleShowNextIndication()218     private void scheduleShowNextIndication() {
219         cancelScheduledIndication();
220         mShowNextIndicationRunnable = new ShowNextIndication(DEFAULT_INDICATION_SHOW_LENGTH);
221     }
222 
cancelScheduledIndication()223     private void cancelScheduledIndication() {
224         if (mShowNextIndicationRunnable != null) {
225             mShowNextIndicationRunnable.cancelDelayedExecution();
226             mShowNextIndicationRunnable = null;
227         }
228     }
229 
230     private StatusBarStateController.StateListener mStatusBarStateListener =
231             new StatusBarStateController.StateListener() {
232                 @Override
233                 public void onDozeAmountChanged(float linear, float eased) {
234                     mView.setAlpha((1 - linear) * mMaxAlpha);
235                 }
236 
237                 @Override
238                 public void onDozingChanged(boolean isDozing) {
239                     if (isDozing == mIsDozing) return;
240                     mIsDozing = isDozing;
241                     if (mIsDozing) {
242                         showIndication(INDICATION_TYPE_NONE);
243                     } else if (mIndicationQueue.size() > 0) {
244                         showIndication(mIndicationQueue.remove(0));
245                     }
246                 }
247             };
248 
249     /**
250      * Shows the next indication in the IndicationQueue after an optional delay.
251      * This wrapper has the ability to cancel itself (remove runnable from DelayableExecutor) or
252      * immediately run itself (which also removes itself from the DelayableExecutor).
253      */
254     class ShowNextIndication {
255         private final Runnable mShowIndicationRunnable;
256         private Runnable mCancelDelayedRunnable;
257 
ShowNextIndication(long delay)258         ShowNextIndication(long delay) {
259             mShowIndicationRunnable = () -> {
260                 int type = mIndicationQueue.size() == 0
261                         ? INDICATION_TYPE_NONE : mIndicationQueue.remove(0);
262                 showIndication(type);
263             };
264             mCancelDelayedRunnable = mExecutor.executeDelayed(mShowIndicationRunnable, delay);
265         }
266 
runImmediately()267         public void runImmediately() {
268             cancelDelayedExecution();
269             mShowIndicationRunnable.run();
270         }
271 
cancelDelayedExecution()272         public void cancelDelayedExecution() {
273             if (mCancelDelayedRunnable != null) {
274                 mCancelDelayedRunnable.run();
275                 mCancelDelayedRunnable = null;
276             }
277         }
278     }
279 
280     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)281     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
282         pw.println("KeyguardIndicationRotatingTextViewController:");
283         pw.println("    currentMessage=" + mView.getText());
284         pw.println("    dozing:" + mIsDozing);
285         pw.println("    queue:" + mIndicationQueue.toString());
286         pw.println("    showNextIndicationRunnable:" + mShowNextIndicationRunnable);
287 
288         if (hasIndications()) {
289             pw.println("    All messages:");
290             for (int type : mIndicationMessages.keySet()) {
291                 pw.println("        type=" + type + " " + mIndicationMessages.get(type));
292             }
293         }
294     }
295 
296     static final int INDICATION_TYPE_NONE = -1;
297     public static final int INDICATION_TYPE_OWNER_INFO = 0;
298     public static final int INDICATION_TYPE_DISCLOSURE = 1;
299     public static final int INDICATION_TYPE_LOGOUT = 2;
300     public static final int INDICATION_TYPE_BATTERY = 3;
301     public static final int INDICATION_TYPE_ALIGNMENT = 4;
302     public static final int INDICATION_TYPE_TRANSIENT = 5;
303     public static final int INDICATION_TYPE_TRUST = 6;
304     public static final int INDICATION_TYPE_RESTING = 7;
305     public static final int INDICATION_TYPE_USER_LOCKED = 8;
306     public static final int INDICATION_TYPE_NOW_PLAYING = 9;
307     public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
308 
309     @IntDef({
310             INDICATION_TYPE_NONE,
311             INDICATION_TYPE_DISCLOSURE,
312             INDICATION_TYPE_OWNER_INFO,
313             INDICATION_TYPE_LOGOUT,
314             INDICATION_TYPE_BATTERY,
315             INDICATION_TYPE_ALIGNMENT,
316             INDICATION_TYPE_TRANSIENT,
317             INDICATION_TYPE_TRUST,
318             INDICATION_TYPE_RESTING,
319             INDICATION_TYPE_USER_LOCKED,
320             INDICATION_TYPE_NOW_PLAYING,
321             INDICATION_TYPE_REVERSE_CHARGING,
322     })
323     @Retention(RetentionPolicy.SOURCE)
324     public @interface IndicationType{}
325 }
326