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