• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.qs.tileimpl;
16 
17 import static androidx.lifecycle.Lifecycle.State.CREATED;
18 import static androidx.lifecycle.Lifecycle.State.DESTROYED;
19 import static androidx.lifecycle.Lifecycle.State.RESUMED;
20 import static androidx.lifecycle.Lifecycle.State.STARTED;
21 
22 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
23 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
24 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
25 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_IS_FULL_QS;
26 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
27 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
28 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_BAR_STATE;
29 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
30 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
31 
32 import android.annotation.CallSuper;
33 import android.annotation.NonNull;
34 import android.annotation.SuppressLint;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.graphics.drawable.Drawable;
38 import android.metrics.LogMaker;
39 import android.os.Handler;
40 import android.os.Looper;
41 import android.os.Message;
42 import android.text.format.DateUtils;
43 import android.util.ArraySet;
44 import android.util.Log;
45 import android.util.SparseArray;
46 
47 import androidx.annotation.Nullable;
48 import androidx.lifecycle.Lifecycle;
49 import androidx.lifecycle.LifecycleOwner;
50 import androidx.lifecycle.LifecycleRegistry;
51 
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.internal.jank.InteractionJankMonitor;
54 import com.android.internal.logging.InstanceId;
55 import com.android.internal.logging.MetricsLogger;
56 import com.android.internal.logging.UiEventLogger;
57 import com.android.settingslib.RestrictedLockUtils;
58 import com.android.settingslib.RestrictedLockUtilsInternal;
59 import com.android.settingslib.graph.SignalDrawable;
60 import com.android.systemui.Dumpable;
61 import com.android.systemui.animation.ActivityTransitionAnimator;
62 import com.android.systemui.animation.Expandable;
63 import com.android.systemui.plugins.ActivityStarter;
64 import com.android.systemui.plugins.FalsingManager;
65 import com.android.systemui.plugins.qs.QSTile;
66 import com.android.systemui.plugins.qs.QSTile.State;
67 import com.android.systemui.plugins.statusbar.StatusBarStateController;
68 import com.android.systemui.qs.QSEvent;
69 import com.android.systemui.qs.QSHost;
70 import com.android.systemui.qs.QsEventLogger;
71 import com.android.systemui.qs.SideLabelTileLayout;
72 import com.android.systemui.qs.flags.QsInCompose;
73 import com.android.systemui.qs.logging.QSLogger;
74 
75 import java.io.PrintWriter;
76 import java.util.Objects;
77 import java.util.concurrent.atomic.AtomicBoolean;
78 import java.util.concurrent.atomic.AtomicInteger;
79 
80 /**
81  * Base quick-settings tile, extend this to create a new tile.
82  *
83  * State management done on a looper provided by the host.  Tiles should update state in
84  * handleUpdateState.  Callbacks affecting state should use refreshState to trigger another
85  * state update pass on tile looper.
86  *
87  * @param <TState> see above
88  */
89 public abstract class QSTileImpl<TState extends State> implements QSTile, LifecycleOwner, Dumpable {
90     protected final String TAG = "Tile." + getClass().getSimpleName();
91     protected final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG);
92 
93     private static final long DEFAULT_STALE_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS;
94     protected static final Object ARG_SHOW_TRANSIENT_ENABLING = new Object();
95     protected static final Object ARG_SHOW_TRANSIENT_DISABLING = new Object();
96 
97     private static final int READY_STATE_NOT_READY = 0;
98     private static final int READY_STATE_READYING = 1;
99     private static final int READY_STATE_READY = 2;
100 
101     protected final QSHost mHost;
102     protected final Context mContext;
103     // @NonFinalForTesting
104     protected final H mHandler;
105     protected final Handler mUiHandler;
106     private final ArraySet<Object> mListeners = new ArraySet<>();
107     private final MetricsLogger mMetricsLogger;
108     private final StatusBarStateController mStatusBarStateController;
109     protected final ActivityStarter mActivityStarter;
110     private final UiEventLogger mUiEventLogger;
111     private final FalsingManager mFalsingManager;
112     protected final QSLogger mQSLogger;
113     private volatile int mReadyState;
114     // Keeps track of the click event, to match it with the handling in the background thread
115     // Only read and modified in main thread (where click events come through).
116     private int mClickEventId = 0;
117 
118     private final ArraySet<Callback> mCallbacks = new ArraySet<>();
119     private final Object mStaleListener = new Object();
120     protected TState mState;
121     private TState mTmpState;
122     private final InstanceId mInstanceId;
123     private boolean mAnnounceNextStateChange;
124 
125     private String mTileSpec;
126     @Nullable
127     @VisibleForTesting
128     protected EnforcedAdmin mEnforcedAdmin;
129     private boolean mShowingDetail;
130     private int mIsFullQs;
131 
132     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
133     private final AtomicBoolean mIsDestroyed = new AtomicBoolean(false);
134     private final AtomicInteger mCurrentTileUser = new AtomicInteger();
135 
136     /**
137      * Provides a new {@link TState} of the appropriate type to use between this tile and the
138      * corresponding view.
139      *
140      * @return new state to use by the tile.
141      */
newTileState()142     public abstract TState newTileState();
143 
144     /**
145      * Handles clicks by the user.
146      *
147      * Calls to the controller should be made here to set the new state of the device.
148      *
149      * @param expandable {@link Expandable} that was clicked.
150      */
handleClick(@ullable Expandable expandable)151     protected abstract void handleClick(@Nullable Expandable expandable);
152 
153     /**
154      * Update state of the tile based on device state
155      *
156      * Called whenever the state of the tile needs to be updated, either after user
157      * interaction or from callbacks from the controller. It populates {@code state} with the
158      * information to display to the user.
159      *
160      * @param state {@link TState} to populate with information to display
161      * @param arg additional arguments needed to populate {@code state}
162      */
handleUpdateState(TState state, Object arg)163     abstract protected void handleUpdateState(TState state, Object arg);
164 
165     /**
166      * Declare the category of this tile.
167      *
168      * Categories are defined in {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}
169      * by editing frameworks/base/proto/src/metrics_constants.proto.
170      *
171      * @deprecated Not needed as this logging is deprecated. Logging tiles is done using
172      * {@link QSTile#getMetricsSpec}
173      */
174     @Deprecated
getMetricsCategory()175     public int getMetricsCategory() {
176         return 0;
177     }
178 
179     /**
180      * Performs initialization of the tile
181      *
182      * Use this to perform initialization of the tile. Empty by default.
183      */
handleInitialize()184     protected void handleInitialize() {
185 
186     }
187 
QSTileImpl( QSHost host, QsEventLogger uiEventLogger, Looper backgroundLooper, Handler mainHandler, FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger )188     protected QSTileImpl(
189             QSHost host,
190             QsEventLogger uiEventLogger,
191             Looper backgroundLooper,
192             Handler mainHandler,
193             FalsingManager falsingManager,
194             MetricsLogger metricsLogger,
195             StatusBarStateController statusBarStateController,
196             ActivityStarter activityStarter,
197             QSLogger qsLogger
198     ) {
199         mHost = host;
200         mContext = host.getContext();
201         mInstanceId = uiEventLogger.getNewInstanceId();
202         mUiEventLogger = uiEventLogger;
203 
204         mUiHandler = mainHandler;
205         mHandler = new H(backgroundLooper);
206         mFalsingManager = falsingManager;
207         mQSLogger = qsLogger;
208         mMetricsLogger = metricsLogger;
209         mStatusBarStateController = statusBarStateController;
210         mActivityStarter = activityStarter;
211         mCurrentTileUser.set(host.getUserId());
212 
213         resetStates();
214         mUiHandler.post(() -> mLifecycle.setCurrentState(CREATED));
215     }
216 
resetStates()217     protected final void resetStates() {
218         mState = newTileState();
219         mTmpState = newTileState();
220         mState.spec = mTileSpec;
221         mTmpState.spec = mTileSpec;
222     }
223 
224     @NonNull
225     @Override
getLifecycle()226     public Lifecycle getLifecycle() {
227         return mLifecycle;
228     }
229 
230     @Override
getInstanceId()231     public InstanceId getInstanceId() {
232         return mInstanceId;
233     }
234 
235     /**
236      * Adds or removes a listening client for the tile. If the tile has one or more
237      * listening client it will go into the listening state.
238      */
setListening(Object listener, boolean listening)239     public void setListening(Object listener, boolean listening) {
240         mHandler.obtainMessage(H.SET_LISTENING, listening ? 1 : 0, 0, listener).sendToTarget();
241     }
242 
getStaleTimeout()243     protected long getStaleTimeout() {
244         return DEFAULT_STALE_TIMEOUT;
245     }
246 
247     @VisibleForTesting
handleStale()248     protected void handleStale() {
249         if (!mListeners.isEmpty()) {
250             // If the tile is already listening (it's been a long time since it refreshed), just
251             // force a refresh. Don't add the staleListener because there's already a listener there
252             refreshState();
253         } else {
254             setListening(mStaleListener, true);
255         }
256     }
257 
getTileSpec()258     public String getTileSpec() {
259         return mTileSpec;
260     }
261 
setTileSpec(String tileSpec)262     public void setTileSpec(String tileSpec) {
263         mTileSpec = tileSpec;
264         mState.spec = tileSpec;
265         mTmpState.spec = tileSpec;
266     }
267 
getHost()268     public QSHost getHost() {
269         return mHost;
270     }
271 
272     /**
273      * Is a startup check whether this device currently supports this tile.
274      * Should not be used to conditionally hide tiles.  Only checked on tile
275      * creation or whether should be shown in edit screen.
276      */
isAvailable()277     public boolean isAvailable() {
278         return true;
279     }
280 
281     // safe to call from any thread
282 
addCallback(Callback callback)283     public void addCallback(Callback callback) {
284         mHandler.obtainMessage(H.ADD_CALLBACK, callback).sendToTarget();
285     }
286 
removeCallback(Callback callback)287     public void removeCallback(Callback callback) {
288         mHandler.obtainMessage(H.REMOVE_CALLBACK, callback).sendToTarget();
289     }
290 
removeCallbacks()291     public void removeCallbacks() {
292         mHandler.sendEmptyMessage(H.REMOVE_CALLBACKS);
293     }
294 
295     @Override
click(@ullable Expandable expandable)296     public void click(@Nullable Expandable expandable) {
297         mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION)
298                 .addTaggedData(FIELD_STATUS_BAR_STATE,
299                         mStatusBarStateController.getState())));
300         mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_CLICK, 0, getMetricsSpec(),
301                 getInstanceId());
302         final int eventId = mClickEventId++;
303         mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
304                 eventId);
305         if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
306             mHandler.obtainMessage(H.CLICK, eventId, 0, expandable).sendToTarget();
307         }
308     }
309 
310     @Override
secondaryClick(@ullable Expandable expandable)311     public void secondaryClick(@Nullable Expandable expandable) {
312         mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION)
313                 .addTaggedData(FIELD_STATUS_BAR_STATE,
314                         mStatusBarStateController.getState())));
315         mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_SECONDARY_CLICK, 0, getMetricsSpec(),
316                 getInstanceId());
317         final int eventId = mClickEventId++;
318         mQSLogger.logTileSecondaryClick(mTileSpec, mStatusBarStateController.getState(),
319                 mState.state, eventId);
320         mHandler.obtainMessage(H.SECONDARY_CLICK, eventId, 0, expandable).sendToTarget();
321     }
322 
323     @Override
longClick(@ullable Expandable expandable)324     public void longClick(@Nullable Expandable expandable) {
325         mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)
326                 .addTaggedData(FIELD_STATUS_BAR_STATE,
327                         mStatusBarStateController.getState())));
328         mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_LONG_PRESS, 0, getMetricsSpec(),
329                 getInstanceId());
330         final int eventId = mClickEventId++;
331         mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
332                 eventId);
333         if (!mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
334             mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, expandable).sendToTarget();
335         }
336     }
337 
populate(LogMaker logMaker)338     public LogMaker populate(LogMaker logMaker) {
339         if (mState instanceof BooleanState) {
340             logMaker.addTaggedData(FIELD_QS_VALUE, ((BooleanState) mState).value ? 1 : 0);
341         }
342         return logMaker.setSubtype(getMetricsCategory())
343                 .addTaggedData(FIELD_IS_FULL_QS, mIsFullQs)
344                 .addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec));
345     }
346 
refreshState()347     public void refreshState() {
348         refreshState(null);
349     }
350 
351     @Override
isListening()352     public final boolean isListening() {
353         return getLifecycle().getCurrentState().isAtLeast(RESUMED);
354     }
355 
refreshState(@ullable Object arg)356     protected final void refreshState(@Nullable Object arg) {
357         mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
358     }
359 
userSwitch(int newUserId)360     public void userSwitch(int newUserId) {
361         mCurrentTileUser.set(newUserId);
362         mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
363         postStale();
364     }
365 
366     @Override
getCurrentTileUser()367     public int getCurrentTileUser() {
368         return mCurrentTileUser.get();
369     }
370 
destroy()371     public void destroy() {
372         // We mark it as soon as we start the destroy process, as nothing can interrupt it.
373         mIsDestroyed.set(true);
374         mHandler.sendEmptyMessage(H.DESTROY);
375     }
376 
377     /**
378      * Schedules initialization of the tile.
379      *
380      * Should be called upon creation of the tile, before performing other operations
381      */
initialize()382     public final void initialize() {
383         mHandler.sendEmptyMessage(H.INITIALIZE);
384     }
385 
386     @androidx.annotation.NonNull
getState()387     public TState getState() {
388         return mState;
389     }
390 
setDetailListening(boolean listening)391     public void setDetailListening(boolean listening) {
392         // optional
393     }
394 
395     // call only on tile worker looper
396 
handleAddCallback(Callback callback)397     private void handleAddCallback(Callback callback) {
398         mCallbacks.add(callback);
399         callback.onStateChanged(mState);
400     }
401 
handleRemoveCallback(Callback callback)402     private void handleRemoveCallback(Callback callback) {
403         mCallbacks.remove(callback);
404     }
405 
handleRemoveCallbacks()406     private void handleRemoveCallbacks() {
407         mCallbacks.clear();
408     }
409 
410     /**
411      * Posts a stale message to the background thread.
412      */
postStale()413     public void postStale() {
414         mHandler.sendEmptyMessage(H.STALE);
415     }
416 
417     /**
418      * Handles secondary click on the tile.
419      *
420      * Defaults to {@link QSTileImpl#handleClick}
421      *
422      * @param expandable {@link Expandable} that was clicked.
423      */
handleSecondaryClick(@ullable Expandable expandable)424     protected void handleSecondaryClick(@Nullable Expandable expandable) {
425         // Default to normal click.
426         handleClick(expandable);
427     }
428 
429     /**
430      * Handles long click on the tile by launching the {@link Intent} defined in
431      * {@link QSTileImpl#getLongClickIntent}.
432      *
433      * @param expandable {@link Expandable} from which the opening window will be animated.
434      */
handleLongClick(@ullable Expandable expandable)435     protected void handleLongClick(@Nullable Expandable expandable) {
436         ActivityTransitionAnimator.Controller animationController =
437                 expandable != null ? expandable.activityTransitionController(
438                         InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) : null;
439         mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
440                 animationController);
441     }
442 
443     /**
444      * Returns an intent to be launched when the tile is long pressed.
445      *
446      * @return the intent to launch
447      */
448     @Nullable
getLongClickIntent()449     public abstract Intent getLongClickIntent();
450 
handleRefreshState(@ullable Object arg)451     protected final void handleRefreshState(@Nullable Object arg) {
452         handleUpdateState(mTmpState, arg);
453         boolean changed = mTmpState.copyTo(mState);
454         if (mReadyState == READY_STATE_READYING) {
455             mReadyState = READY_STATE_READY;
456             changed = true;
457         }
458         if (changed) {
459             mQSLogger.logTileUpdated(mTileSpec, mState);
460             handleStateChanged();
461         }
462         mHandler.removeMessages(H.STALE);
463         mHandler.sendEmptyMessageDelayed(H.STALE, getStaleTimeout());
464         setListening(mStaleListener, false);
465     }
466 
handleStateChanged()467     private void handleStateChanged() {
468         if (!mCallbacks.isEmpty()) {
469             for (int i = 0; i < mCallbacks.size(); i++) {
470                 mCallbacks.valueAt(i).onStateChanged(mState);
471             }
472         }
473     }
474 
handleUserSwitch(int newUserId)475     protected void handleUserSwitch(int newUserId) {
476         handleRefreshState(null);
477     }
478 
handleSetListeningInternal(Object listener, boolean listening)479     private void handleSetListeningInternal(Object listener, boolean listening) {
480         // This should be used to go from resumed to paused. Listening for ON_RESUME and ON_PAUSE
481         // in this lifecycle will determine the listening window.
482         if (listening) {
483             if (mListeners.add(listener) && mListeners.size() == 1) {
484                 if (DEBUG) Log.d(TAG, "handleSetListening true");
485                 handleSetListening(listening);
486                 mUiHandler.post(() -> {
487                     // This tile has been destroyed, the state should not change anymore and we
488                     // should not refresh it anymore.
489                     if (mLifecycle.getCurrentState().equals(DESTROYED)) return;
490                     mLifecycle.setCurrentState(RESUMED);
491                     if (mReadyState == READY_STATE_NOT_READY) {
492                         mReadyState = READY_STATE_READYING;
493                     }
494                     refreshState(); // Ensure we get at least one refresh after listening.
495                 });
496             }
497         } else {
498             if (mListeners.remove(listener) && mListeners.size() == 0) {
499                 if (DEBUG) Log.d(TAG, "handleSetListening false");
500                 handleSetListening(listening);
501                 mUiHandler.post(() -> {
502                     // This tile has been destroyed, the state should not change anymore.
503                     if (mLifecycle.getCurrentState().equals(DESTROYED)) return;
504                     mLifecycle.setCurrentState(STARTED);
505                 });
506             }
507         }
508         updateIsFullQs();
509     }
510 
updateIsFullQs()511     private void updateIsFullQs() {
512         for (Object listener : mListeners) {
513             if (SideLabelTileLayout.class.equals(listener.getClass())) {
514                 mIsFullQs = 1;
515                 return;
516             }
517         }
518         mIsFullQs = 0;
519     }
520 
521     @CallSuper
handleSetListening(boolean listening)522     protected void handleSetListening(boolean listening) {
523         if (mTileSpec != null) {
524             mQSLogger.logTileChangeListening(mTileSpec, listening);
525         }
526     }
527 
handleDestroy()528     protected void handleDestroy() {
529         mQSLogger.logTileDestroyed(mTileSpec, "Handle destroy");
530         if (mListeners.size() != 0) {
531             handleSetListening(false);
532             mListeners.clear();
533         }
534         mCallbacks.clear();
535         mHandler.removeCallbacksAndMessages(null);
536         // This will force it to be removed from all controllers that may have it registered.
537         mUiHandler.post(() -> {
538             mLifecycle.setCurrentState(DESTROYED);
539         });
540     }
541 
542     @Override
isDestroyed()543     public final boolean isDestroyed() {
544         return mIsDestroyed.get();
545     }
546 
checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction)547     protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
548         EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
549                 userRestriction, mHost.getUserId());
550         if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
551                 userRestriction, mHost.getUserId())) {
552             state.disabledByPolicy = true;
553             mEnforcedAdmin = admin;
554         } else {
555             state.disabledByPolicy = false;
556             mEnforcedAdmin = null;
557         }
558     }
559 
maybeLoadResourceIcon(int id)560     protected Icon maybeLoadResourceIcon(int id) {
561         return maybeLoadResourceIcon(id, mContext);
562     }
563 
564     /**
565      * Returns the {@link QSTile.Icon} for the resource ID, optionally loading the drawable if
566      * {@link QsInCompose#isEnabled()} is true.
567      */
568     @SuppressLint("UseCompatLoadingForDrawables")
maybeLoadResourceIcon(int id, Context context)569     public static Icon maybeLoadResourceIcon(int id, Context context) {
570         if (QsInCompose.isEnabled()) {
571             return new DrawableIconWithRes(context.getDrawable(id), id);
572         } else {
573             return ResourceIcon.get(id);
574         }
575     }
576 
577     @Override
getMetricsSpec()578     public String getMetricsSpec() {
579         return mTileSpec;
580     }
581 
582     /**
583      * Provides a default label for the tile.
584      * @return default label for the tile.
585      */
getTileLabel()586     public abstract CharSequence getTileLabel();
587 
588     /**
589      * @return {@code true} if the tile has refreshed state at least once after having set its
590      *         lifecycle to {@link Lifecycle.State#RESUMED}.
591      */
592     @Override
isTileReady()593     public boolean isTileReady() {
594         return mReadyState == READY_STATE_READY;
595     }
596 
597     protected final class H extends Handler {
598         private static final int ADD_CALLBACK = 1;
599         private static final int CLICK = 2;
600         private static final int SECONDARY_CLICK = 3;
601         private static final int LONG_CLICK = 4;
602         private static final int REFRESH_STATE = 5;
603         private static final int USER_SWITCH = 6;
604         private static final int DESTROY = 7;
605         private static final int REMOVE_CALLBACKS = 8;
606         private static final int REMOVE_CALLBACK = 9;
607         private static final int SET_LISTENING = 10;
608         @VisibleForTesting
609         protected static final int STALE = 11;
610         private static final int INITIALIZE = 12;
611 
612         @VisibleForTesting
H(Looper looper)613         protected H(Looper looper) {
614             super(looper);
615         }
616 
617         @Override
handleMessage(Message msg)618         public void handleMessage(Message msg) {
619             String name = null;
620             try {
621                 if (msg.what == ADD_CALLBACK) {
622                     name = "handleAddCallback";
623                     handleAddCallback((QSTile.Callback) msg.obj);
624                 } else if (msg.what == REMOVE_CALLBACKS) {
625                     name = "handleRemoveCallbacks";
626                     handleRemoveCallbacks();
627                 } else if (msg.what == REMOVE_CALLBACK) {
628                     name = "handleRemoveCallback";
629                     handleRemoveCallback((QSTile.Callback) msg.obj);
630                 } else if (msg.what == CLICK) {
631                     name = "handleClick";
632                     if (mState.disabledByPolicy) {
633                         Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
634                                 mEnforcedAdmin);
635                         mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
636                     } else {
637                         mQSLogger.logHandleClick(mTileSpec, msg.arg1);
638                         handleClick((Expandable) msg.obj);
639                     }
640                 } else if (msg.what == SECONDARY_CLICK) {
641                     name = "handleSecondaryClick";
642                     mQSLogger.logHandleSecondaryClick(mTileSpec, msg.arg1);
643                     handleSecondaryClick((Expandable) msg.obj);
644                 } else if (msg.what == LONG_CLICK) {
645                     name = "handleLongClick";
646                     mQSLogger.logHandleLongClick(mTileSpec, msg.arg1);
647                     handleLongClick((Expandable) msg.obj);
648                 } else if (msg.what == REFRESH_STATE) {
649                     name = "handleRefreshState";
650                     handleRefreshState(msg.obj);
651                 } else if (msg.what == USER_SWITCH) {
652                     name = "handleUserSwitch";
653                     handleUserSwitch(msg.arg1);
654                 } else if (msg.what == DESTROY) {
655                     name = "handleDestroy";
656                     handleDestroy();
657                 } else if (msg.what == SET_LISTENING) {
658                     name = "handleSetListeningInternal";
659                     handleSetListeningInternal(msg.obj, msg.arg1 != 0);
660                 } else if (msg.what == STALE) {
661                     name = "handleStale";
662                     handleStale();
663                 } else if (msg.what == INITIALIZE) {
664                     name = "initialize";
665                     handleInitialize();
666                 } else {
667                     throw new IllegalArgumentException("Unknown msg: " + msg.what);
668                 }
669             } catch (Throwable t) {
670                 final String error = "Error in " + name;
671                 Log.w(TAG, error, t);
672             }
673         }
674     }
675 
676     public static class DrawableIcon extends Icon {
677 
678         protected final Drawable mDrawable;
679         protected final Drawable mInvisibleDrawable;
680         private static final String TAG = "QSTileImpl";
681 
DrawableIcon(Drawable drawable)682         public DrawableIcon(Drawable drawable) {
683             mDrawable = drawable;
684             Drawable.ConstantState nullableConstantState = drawable.getConstantState();
685             if (nullableConstantState == null) {
686                 if (!(drawable instanceof SignalDrawable)) {
687                     Log.w(TAG, "DrawableIcon: drawable has null ConstantState"
688                             + " and is not a SignalDrawable");
689                 }
690                 mInvisibleDrawable = drawable;
691             } else {
692                 mInvisibleDrawable = nullableConstantState.newDrawable();
693             }
694         }
695 
696         @Override
getDrawable(Context context)697         public Drawable getDrawable(Context context) {
698             return mDrawable;
699         }
700 
701         @Override
getInvisibleDrawable(Context context)702         public Drawable getInvisibleDrawable(Context context) {
703             return mInvisibleDrawable;
704         }
705 
706         @Override
707         @NonNull
toString()708         public String toString() {
709             return "DrawableIcon";
710         }
711 
712         @Override
equals(@ullable Object other)713         public boolean equals(@Nullable Object other) {
714             // No need to compare equality of the mInvisibleDrawable as that's generated from
715             // mDrawable's constant state.
716             return other instanceof DrawableIcon && ((DrawableIcon) other).mDrawable == mDrawable;
717         }
718 
719         @Override
hashCode()720         public int hashCode() {
721             return Objects.hash(mDrawable);
722         }
723     }
724 
725     public static class DrawableIconWithRes extends DrawableIcon {
726         private final int mId;
727 
DrawableIconWithRes(Drawable drawable, int id)728         public DrawableIconWithRes(Drawable drawable, int id) {
729             super(drawable);
730             mId = id;
731         }
732 
getResourceId()733         public int getResourceId() {
734             return mId;
735         }
736 
737         @Override
equals(Object o)738         public boolean equals(Object o) {
739             return o instanceof DrawableIconWithRes && ((DrawableIconWithRes) o).mId == mId;
740         }
741 
742         @Override
743         @NonNull
toString()744         public String toString() {
745             return String.format("DrawableIconWithRes[resId=0x%08x]", mId);
746         }
747     }
748 
749     public static class ResourceIcon extends Icon {
750         private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
751 
752         protected final int mResId;
753 
ResourceIcon(int resId)754         private ResourceIcon(int resId) {
755             mResId = resId;
756         }
757 
get(int resId)758         public static synchronized Icon get(int resId) {
759             Icon icon = ICONS.get(resId);
760             if (icon == null) {
761                 icon = new ResourceIcon(resId);
762                 ICONS.put(resId, icon);
763             }
764             return icon;
765         }
766 
767         @Override
getDrawable(Context context)768         public Drawable getDrawable(Context context) {
769             return context.getDrawable(mResId);
770         }
771 
772         @Override
getInvisibleDrawable(Context context)773         public Drawable getInvisibleDrawable(Context context) {
774             return context.getDrawable(mResId);
775         }
776 
getResId()777         public int getResId() {
778             return mResId;
779         }
780 
781         @Override
equals(Object o)782         public boolean equals(Object o) {
783             return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;
784         }
785 
786         @Override
787         @NonNull
toString()788         public String toString() {
789             return String.format("ResourceIcon[resId=0x%08x]", mResId);
790         }
791     }
792 
793     protected static class AnimationIcon extends ResourceIcon {
794         private final int mAnimatedResId;
795 
AnimationIcon(int resId, int staticResId)796         public AnimationIcon(int resId, int staticResId) {
797             super(staticResId);
798             mAnimatedResId = resId;
799         }
800 
801         @Override
getDrawable(Context context)802         public Drawable getDrawable(Context context) {
803             // workaround: get a clean state for every new AVD
804             return context.getDrawable(mAnimatedResId).getConstantState().newDrawable();
805         }
806 
807         @Override
808         @NonNull
toString()809         public String toString() {
810             return String.format("AnimationIcon[resId=0x%08x]", mResId);
811         }
812     }
813 
814     /**
815      * Dumps the state of this tile along with its name.
816      *
817      * This may be used for CTS testing of tiles.
818      */
819     @Override
dump(PrintWriter pw, String[] args)820     public void dump(PrintWriter pw, String[] args) {
821         pw.print(this.getClass().getSimpleName() + ":");
822         pw.print("    "); pw.println(getState().toString());
823     }
824 }
825