• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.qs;
18 
19 import android.app.PendingIntent;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.graphics.drawable.Animatable;
23 import android.graphics.drawable.AnimatedVectorDrawable;
24 import android.graphics.drawable.Drawable;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.util.Log;
29 import android.util.SparseArray;
30 import android.view.View;
31 import android.view.ViewGroup;
32 
33 import com.android.systemui.qs.QSTile.State;
34 import com.android.systemui.statusbar.policy.BluetoothController;
35 import com.android.systemui.statusbar.policy.CastController;
36 import com.android.systemui.statusbar.policy.FlashlightController;
37 import com.android.systemui.statusbar.policy.HotspotController;
38 import com.android.systemui.statusbar.policy.KeyguardMonitor;
39 import com.android.systemui.statusbar.policy.Listenable;
40 import com.android.systemui.statusbar.policy.LocationController;
41 import com.android.systemui.statusbar.policy.NetworkController;
42 import com.android.systemui.statusbar.policy.RotationLockController;
43 import com.android.systemui.statusbar.policy.ZenModeController;
44 
45 import java.util.Collection;
46 import java.util.Objects;
47 
48 /**
49  * Base quick-settings tile, extend this to create a new tile.
50  *
51  * State management done on a looper provided by the host.  Tiles should update state in
52  * handleUpdateState.  Callbacks affecting state should use refreshState to trigger another
53  * state update pass on tile looper.
54  */
55 public abstract class QSTile<TState extends State> implements Listenable {
56     protected final String TAG = "QSTile." + getClass().getSimpleName();
57     protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG);
58 
59     protected final Host mHost;
60     protected final Context mContext;
61     protected final H mHandler;
62     protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
63 
64     private Callback mCallback;
65     protected TState mState = newTileState();
66     private TState mTmpState = newTileState();
67     private boolean mAnnounceNextStateChange;
68 
newTileState()69     abstract protected TState newTileState();
handleClick()70     abstract protected void handleClick();
handleUpdateState(TState state, Object arg)71     abstract protected void handleUpdateState(TState state, Object arg);
72 
73     /**
74      * Declare the category of this tile.
75      *
76      * Categories are defined in {@link com.android.internal.logging.MetricsLogger}
77      * or if there is no relevant existing category you may define one in
78      * {@link com.android.systemui.qs.QSTile}.
79      */
getMetricsCategory()80     abstract public int getMetricsCategory();
81 
QSTile(Host host)82     protected QSTile(Host host) {
83         mHost = host;
84         mContext = host.getContext();
85         mHandler = new H(host.getLooper());
86     }
87 
supportsDualTargets()88     public boolean supportsDualTargets() {
89         return false;
90     }
91 
getHost()92     public Host getHost() {
93         return mHost;
94     }
95 
createTileView(Context context)96     public QSTileView createTileView(Context context) {
97         return new QSTileView(context);
98     }
99 
getDetailAdapter()100     public DetailAdapter getDetailAdapter() {
101         return null; // optional
102     }
103 
104     public interface DetailAdapter {
getTitle()105         int getTitle();
getToggleState()106         Boolean getToggleState();
createDetailView(Context context, View convertView, ViewGroup parent)107         View createDetailView(Context context, View convertView, ViewGroup parent);
getSettingsIntent()108         Intent getSettingsIntent();
setToggleState(boolean state)109         void setToggleState(boolean state);
getMetricsCategory()110         int getMetricsCategory();
111     }
112 
113     // safe to call from any thread
114 
setCallback(Callback callback)115     public void setCallback(Callback callback) {
116         mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
117     }
118 
click()119     public void click() {
120         mHandler.sendEmptyMessage(H.CLICK);
121     }
122 
secondaryClick()123     public void secondaryClick() {
124         mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
125     }
126 
longClick()127     public void longClick() {
128         mHandler.sendEmptyMessage(H.LONG_CLICK);
129     }
130 
showDetail(boolean show)131     public void showDetail(boolean show) {
132         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
133     }
134 
refreshState()135     protected final void refreshState() {
136         refreshState(null);
137     }
138 
refreshState(Object arg)139     protected final void refreshState(Object arg) {
140         mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
141     }
142 
clearState()143     public final void clearState() {
144         mHandler.sendEmptyMessage(H.CLEAR_STATE);
145     }
146 
userSwitch(int newUserId)147     public void userSwitch(int newUserId) {
148         mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
149     }
150 
fireToggleStateChanged(boolean state)151     public void fireToggleStateChanged(boolean state) {
152         mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
153     }
154 
fireScanStateChanged(boolean state)155     public void fireScanStateChanged(boolean state) {
156         mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
157     }
158 
destroy()159     public void destroy() {
160         mHandler.sendEmptyMessage(H.DESTROY);
161     }
162 
getState()163     public TState getState() {
164         return mState;
165     }
166 
setDetailListening(boolean listening)167     public void setDetailListening(boolean listening) {
168         // optional
169     }
170 
171     // call only on tile worker looper
172 
handleSetCallback(Callback callback)173     private void handleSetCallback(Callback callback) {
174         mCallback = callback;
175         handleRefreshState(null);
176     }
177 
handleSecondaryClick()178     protected void handleSecondaryClick() {
179         // optional
180     }
181 
handleLongClick()182     protected void handleLongClick() {
183         // optional
184     }
185 
handleClearState()186     protected void handleClearState() {
187         mTmpState = newTileState();
188         mState = newTileState();
189     }
190 
handleRefreshState(Object arg)191     protected void handleRefreshState(Object arg) {
192         handleUpdateState(mTmpState, arg);
193         final boolean changed = mTmpState.copyTo(mState);
194         if (changed) {
195             handleStateChanged();
196         }
197     }
198 
handleStateChanged()199     private void handleStateChanged() {
200         boolean delayAnnouncement = shouldAnnouncementBeDelayed();
201         if (mCallback != null) {
202             mCallback.onStateChanged(mState);
203             if (mAnnounceNextStateChange && !delayAnnouncement) {
204                 String announcement = composeChangeAnnouncement();
205                 if (announcement != null) {
206                     mCallback.onAnnouncementRequested(announcement);
207                 }
208             }
209         }
210         mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
211     }
212 
shouldAnnouncementBeDelayed()213     protected boolean shouldAnnouncementBeDelayed() {
214         return false;
215     }
216 
composeChangeAnnouncement()217     protected String composeChangeAnnouncement() {
218         return null;
219     }
220 
handleShowDetail(boolean show)221     private void handleShowDetail(boolean show) {
222         if (mCallback != null) {
223             mCallback.onShowDetail(show);
224         }
225     }
226 
handleToggleStateChanged(boolean state)227     private void handleToggleStateChanged(boolean state) {
228         if (mCallback != null) {
229             mCallback.onToggleStateChanged(state);
230         }
231     }
232 
handleScanStateChanged(boolean state)233     private void handleScanStateChanged(boolean state) {
234         if (mCallback != null) {
235             mCallback.onScanStateChanged(state);
236         }
237     }
238 
handleUserSwitch(int newUserId)239     protected void handleUserSwitch(int newUserId) {
240         handleRefreshState(null);
241     }
242 
handleDestroy()243     protected void handleDestroy() {
244         setListening(false);
245         mCallback = null;
246     }
247 
248     protected final class H extends Handler {
249         private static final int SET_CALLBACK = 1;
250         private static final int CLICK = 2;
251         private static final int SECONDARY_CLICK = 3;
252         private static final int LONG_CLICK = 4;
253         private static final int REFRESH_STATE = 5;
254         private static final int SHOW_DETAIL = 6;
255         private static final int USER_SWITCH = 7;
256         private static final int TOGGLE_STATE_CHANGED = 8;
257         private static final int SCAN_STATE_CHANGED = 9;
258         private static final int DESTROY = 10;
259         private static final int CLEAR_STATE = 11;
260 
H(Looper looper)261         private H(Looper looper) {
262             super(looper);
263         }
264 
265         @Override
handleMessage(Message msg)266         public void handleMessage(Message msg) {
267             String name = null;
268             try {
269                 if (msg.what == SET_CALLBACK) {
270                     name = "handleSetCallback";
271                     handleSetCallback((QSTile.Callback)msg.obj);
272                 } else if (msg.what == CLICK) {
273                     name = "handleClick";
274                     mAnnounceNextStateChange = true;
275                     handleClick();
276                 } else if (msg.what == SECONDARY_CLICK) {
277                     name = "handleSecondaryClick";
278                     handleSecondaryClick();
279                 } else if (msg.what == LONG_CLICK) {
280                     name = "handleLongClick";
281                     handleLongClick();
282                 } else if (msg.what == REFRESH_STATE) {
283                     name = "handleRefreshState";
284                     handleRefreshState(msg.obj);
285                 } else if (msg.what == SHOW_DETAIL) {
286                     name = "handleShowDetail";
287                     handleShowDetail(msg.arg1 != 0);
288                 } else if (msg.what == USER_SWITCH) {
289                     name = "handleUserSwitch";
290                     handleUserSwitch(msg.arg1);
291                 } else if (msg.what == TOGGLE_STATE_CHANGED) {
292                     name = "handleToggleStateChanged";
293                     handleToggleStateChanged(msg.arg1 != 0);
294                 } else if (msg.what == SCAN_STATE_CHANGED) {
295                     name = "handleScanStateChanged";
296                     handleScanStateChanged(msg.arg1 != 0);
297                 } else if (msg.what == DESTROY) {
298                     name = "handleDestroy";
299                     handleDestroy();
300                 } else if (msg.what == CLEAR_STATE) {
301                     name = "handleClearState";
302                     handleClearState();
303                 } else {
304                     throw new IllegalArgumentException("Unknown msg: " + msg.what);
305                 }
306             } catch (Throwable t) {
307                 final String error = "Error in " + name;
308                 Log.w(TAG, error, t);
309                 mHost.warn(error, t);
310             }
311         }
312     }
313 
314     public interface Callback {
onStateChanged(State state)315         void onStateChanged(State state);
onShowDetail(boolean show)316         void onShowDetail(boolean show);
onToggleStateChanged(boolean state)317         void onToggleStateChanged(boolean state);
onScanStateChanged(boolean state)318         void onScanStateChanged(boolean state);
onAnnouncementRequested(CharSequence announcement)319         void onAnnouncementRequested(CharSequence announcement);
320     }
321 
322     public interface Host {
startActivityDismissingKeyguard(Intent intent)323         void startActivityDismissingKeyguard(Intent intent);
startActivityDismissingKeyguard(PendingIntent intent)324         void startActivityDismissingKeyguard(PendingIntent intent);
warn(String message, Throwable t)325         void warn(String message, Throwable t);
collapsePanels()326         void collapsePanels();
getLooper()327         Looper getLooper();
getContext()328         Context getContext();
getTiles()329         Collection<QSTile<?>> getTiles();
setCallback(Callback callback)330         void setCallback(Callback callback);
getBluetoothController()331         BluetoothController getBluetoothController();
getLocationController()332         LocationController getLocationController();
getRotationLockController()333         RotationLockController getRotationLockController();
getNetworkController()334         NetworkController getNetworkController();
getZenModeController()335         ZenModeController getZenModeController();
getHotspotController()336         HotspotController getHotspotController();
getCastController()337         CastController getCastController();
getFlashlightController()338         FlashlightController getFlashlightController();
getKeyguardMonitor()339         KeyguardMonitor getKeyguardMonitor();
340 
341         public interface Callback {
onTilesChanged()342             void onTilesChanged();
343         }
344     }
345 
346     public static abstract class Icon {
getDrawable(Context context)347         abstract public Drawable getDrawable(Context context);
348 
349         @Override
hashCode()350         public int hashCode() {
351             return Icon.class.hashCode();
352         }
353     }
354 
355     public static class ResourceIcon extends Icon {
356         private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
357 
358         protected final int mResId;
359 
ResourceIcon(int resId)360         private ResourceIcon(int resId) {
361             mResId = resId;
362         }
363 
get(int resId)364         public static Icon get(int resId) {
365             Icon icon = ICONS.get(resId);
366             if (icon == null) {
367                 icon = new ResourceIcon(resId);
368                 ICONS.put(resId, icon);
369             }
370             return icon;
371         }
372 
373         @Override
getDrawable(Context context)374         public Drawable getDrawable(Context context) {
375             Drawable d = context.getDrawable(mResId);
376             if (d instanceof Animatable) {
377                 ((Animatable) d).start();
378             }
379             return d;
380         }
381 
382         @Override
equals(Object o)383         public boolean equals(Object o) {
384             return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;
385         }
386 
387         @Override
toString()388         public String toString() {
389             return String.format("ResourceIcon[resId=0x%08x]", mResId);
390         }
391     }
392 
393     protected class AnimationIcon extends ResourceIcon {
394         private boolean mAllowAnimation;
395 
AnimationIcon(int resId)396         public AnimationIcon(int resId) {
397             super(resId);
398         }
399 
setAllowAnimation(boolean allowAnimation)400         public void setAllowAnimation(boolean allowAnimation) {
401             mAllowAnimation = allowAnimation;
402         }
403 
404         @Override
getDrawable(Context context)405         public Drawable getDrawable(Context context) {
406             // workaround: get a clean state for every new AVD
407             final AnimatedVectorDrawable d = (AnimatedVectorDrawable) context.getDrawable(mResId)
408                     .getConstantState().newDrawable();
409             d.start();
410             if (mAllowAnimation) {
411                 mAllowAnimation = false;
412             } else {
413                 d.stop(); // skip directly to end state
414             }
415             return d;
416         }
417     }
418 
419     protected enum UserBoolean {
420         USER_TRUE(true, true),
421         USER_FALSE(true, false),
422         BACKGROUND_TRUE(false, true),
423         BACKGROUND_FALSE(false, false);
424         public final boolean value;
425         public final boolean userInitiated;
UserBoolean(boolean userInitiated, boolean value)426         private UserBoolean(boolean userInitiated, boolean value) {
427             this.value = value;
428             this.userInitiated = userInitiated;
429         }
430     }
431 
432     public static class State {
433         public boolean visible;
434         public Icon icon;
435         public String label;
436         public String contentDescription;
437         public String dualLabelContentDescription;
438         public boolean autoMirrorDrawable = true;
439 
copyTo(State other)440         public boolean copyTo(State other) {
441             if (other == null) throw new IllegalArgumentException();
442             if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
443             final boolean changed = other.visible != visible
444                     || !Objects.equals(other.icon, icon)
445                     || !Objects.equals(other.label, label)
446                     || !Objects.equals(other.contentDescription, contentDescription)
447                     || !Objects.equals(other.autoMirrorDrawable, autoMirrorDrawable)
448                     || !Objects.equals(other.dualLabelContentDescription,
449                     dualLabelContentDescription);
450             other.visible = visible;
451             other.icon = icon;
452             other.label = label;
453             other.contentDescription = contentDescription;
454             other.dualLabelContentDescription = dualLabelContentDescription;
455             other.autoMirrorDrawable = autoMirrorDrawable;
456             return changed;
457         }
458 
459         @Override
toString()460         public String toString() {
461             return toStringBuilder().toString();
462         }
463 
toStringBuilder()464         protected StringBuilder toStringBuilder() {
465             final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
466             sb.append("visible=").append(visible);
467             sb.append(",icon=").append(icon);
468             sb.append(",label=").append(label);
469             sb.append(",contentDescription=").append(contentDescription);
470             sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
471             sb.append(",autoMirrorDrawable=").append(autoMirrorDrawable);
472             return sb.append(']');
473         }
474     }
475 
476     public static class BooleanState extends State {
477         public boolean value;
478 
479         @Override
copyTo(State other)480         public boolean copyTo(State other) {
481             final BooleanState o = (BooleanState) other;
482             final boolean changed = super.copyTo(other) || o.value != value;
483             o.value = value;
484             return changed;
485         }
486 
487         @Override
toStringBuilder()488         protected StringBuilder toStringBuilder() {
489             final StringBuilder rt = super.toStringBuilder();
490             rt.insert(rt.length() - 1, ",value=" + value);
491             return rt;
492         }
493     }
494 
495     public static final class SignalState extends State {
496         public boolean enabled;
497         public boolean connected;
498         public boolean activityIn;
499         public boolean activityOut;
500         public int overlayIconId;
501         public boolean filter;
502         public boolean isOverlayIconWide;
503 
504         @Override
copyTo(State other)505         public boolean copyTo(State other) {
506             final SignalState o = (SignalState) other;
507             final boolean changed = o.enabled != enabled
508                     || o.connected != connected || o.activityIn != activityIn
509                     || o.activityOut != activityOut
510                     || o.overlayIconId != overlayIconId
511                     || o.isOverlayIconWide != isOverlayIconWide;
512             o.enabled = enabled;
513             o.connected = connected;
514             o.activityIn = activityIn;
515             o.activityOut = activityOut;
516             o.overlayIconId = overlayIconId;
517             o.filter = filter;
518             o.isOverlayIconWide = isOverlayIconWide;
519             return super.copyTo(other) || changed;
520         }
521 
522         @Override
toStringBuilder()523         protected StringBuilder toStringBuilder() {
524             final StringBuilder rt = super.toStringBuilder();
525             rt.insert(rt.length() - 1, ",enabled=" + enabled);
526             rt.insert(rt.length() - 1, ",connected=" + connected);
527             rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
528             rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
529             rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
530             rt.insert(rt.length() - 1, ",filter=" + filter);
531             rt.insert(rt.length() - 1, ",wideOverlayIcon=" + isOverlayIconWide);
532             return rt;
533         }
534     }
535 }
536