2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.qs;
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;
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;
43 import java.util.Collection;
44 import java.util.Objects;
47 * Base quick-settings tile, extend this to create a new tile.
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.
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);
57 protected final Host mHost;
58 protected final Context mContext;
59 protected final H mHandler;
60 protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
62 private Callback mCallback;
63 protected final TState mState = newTileState();
64 private final TState mTmpState = newTileState();
65 private boolean mAnnounceNextStateChange;
67 abstract protected TState newTileState();
68 abstract protected void handleClick();
69 abstract protected void handleUpdateState(TState state, Object arg);
71 protected QSTile(Host host) {
73 mContext = host.getContext();
74 mHandler = new H(host.getLooper());
77 public boolean supportsDualTargets() {
81 public Host getHost() {
85 public QSTileView createTileView(Context context) {
86 return new QSTileView(context);
89 public DetailAdapter getDetailAdapter() {
90 return null; // optional
93 public interface DetailAdapter {
95 Boolean getToggleState();
96 View createDetailView(Context context, View convertView, ViewGroup parent);
97 Intent getSettingsIntent();
98 void setToggleState(boolean state);
101 // safe to call from any thread
103 public void setCallback(Callback callback) {
104 mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
107 public void click() {
108 mHandler.sendEmptyMessage(H.CLICK);
111 public void secondaryClick() {
112 mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
115 public void showDetail(boolean show) {
116 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
119 protected final void refreshState() {
123 protected final void refreshState(Object arg) {
124 mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
127 public void userSwitch(int newUserId) {
128 mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
131 public void fireToggleStateChanged(boolean state) {
132 mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
135 public void fireScanStateChanged(boolean state) {
136 mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
139 public void destroy() {
140 mHandler.sendEmptyMessage(H.DESTROY);
143 public TState getState() {
147 // call only on tile worker looper
149 private void handleSetCallback(Callback callback) {
150 mCallback = callback;
151 handleRefreshState(null);
154 protected void handleSecondaryClick() {
158 protected void handleRefreshState(Object arg) {
159 handleUpdateState(mTmpState, arg);
160 final boolean changed = mTmpState.copyTo(mState);
162 handleStateChanged();
166 private void handleStateChanged() {
167 boolean delayAnnouncement = shouldAnnouncementBeDelayed();
168 if (mCallback != null) {
169 mCallback.onStateChanged(mState);
170 if (mAnnounceNextStateChange && !delayAnnouncement) {
171 String announcement = composeChangeAnnouncement();
172 if (announcement != null) {
173 mCallback.onAnnouncementRequested(announcement);
177 mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
180 protected boolean shouldAnnouncementBeDelayed() {
184 protected String composeChangeAnnouncement() {
188 private void handleShowDetail(boolean show) {
189 if (mCallback != null) {
190 mCallback.onShowDetail(show);
194 private void handleToggleStateChanged(boolean state) {
195 if (mCallback != null) {
196 mCallback.onToggleStateChanged(state);
200 private void handleScanStateChanged(boolean state) {
201 if (mCallback != null) {
202 mCallback.onScanStateChanged(state);
206 protected void handleUserSwitch(int newUserId) {
207 handleRefreshState(null);
210 protected void handleDestroy() {
215 protected final class H extends Handler {
216 private static final int SET_CALLBACK = 1;
217 private static final int CLICK = 2;
218 private static final int SECONDARY_CLICK = 3;
219 private static final int REFRESH_STATE = 4;
220 private static final int SHOW_DETAIL = 5;
221 private static final int USER_SWITCH = 6;
222 private static final int TOGGLE_STATE_CHANGED = 7;
223 private static final int SCAN_STATE_CHANGED = 8;
224 private static final int DESTROY = 9;
226 private H(Looper looper) {
231 public void handleMessage(Message msg) {
234 if (msg.what == SET_CALLBACK) {
235 name = "handleSetCallback";
236 handleSetCallback((QSTile.Callback)msg.obj);
237 } else if (msg.what == CLICK) {
238 name = "handleClick";
239 mAnnounceNextStateChange = true;
241 } else if (msg.what == SECONDARY_CLICK) {
242 name = "handleSecondaryClick";
243 handleSecondaryClick();
244 } else if (msg.what == REFRESH_STATE) {
245 name = "handleRefreshState";
246 handleRefreshState(msg.obj);
247 } else if (msg.what == SHOW_DETAIL) {
248 name = "handleShowDetail";
249 handleShowDetail(msg.arg1 != 0);
250 } else if (msg.what == USER_SWITCH) {
251 name = "handleUserSwitch";
252 handleUserSwitch(msg.arg1);
253 } else if (msg.what == TOGGLE_STATE_CHANGED) {
254 name = "handleToggleStateChanged";
255 handleToggleStateChanged(msg.arg1 != 0);
256 } else if (msg.what == SCAN_STATE_CHANGED) {
257 name = "handleScanStateChanged";
258 handleScanStateChanged(msg.arg1 != 0);
259 } else if (msg.what == DESTROY) {
260 name = "handleDestroy";
263 throw new IllegalArgumentException("Unknown msg: " + msg.what);
265 } catch (Throwable t) {
266 final String error = "Error in " + name;
267 Log.w(TAG, error, t);
268 mHost.warn(error, t);
273 public interface Callback {
274 void onStateChanged(State state);
275 void onShowDetail(boolean show);
276 void onToggleStateChanged(boolean state);
277 void onScanStateChanged(boolean state);
278 void onAnnouncementRequested(CharSequence announcement);
281 public interface Host {
282 void startSettingsActivity(Intent intent);
283 void warn(String message, Throwable t);
284 void collapsePanels();
286 Context getContext();
287 Collection<QSTile<?>> getTiles();
288 void setCallback(Callback callback);
289 BluetoothController getBluetoothController();
290 LocationController getLocationController();
291 RotationLockController getRotationLockController();
292 NetworkController getNetworkController();
293 ZenModeController getZenModeController();
294 HotspotController getHotspotController();
295 CastController getCastController();
296 FlashlightController getFlashlightController();
297 KeyguardMonitor getKeyguardMonitor();
299 public interface Callback {
300 void onTilesChanged();
304 public static abstract class Icon {
305 abstract public Drawable getDrawable(Context context);
308 public int hashCode() {
309 return Icon.class.hashCode();
313 public static class ResourceIcon extends Icon {
314 private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
316 private final int mResId;
318 private ResourceIcon(int resId) {
322 public static Icon get(int resId) {
323 Icon icon = ICONS.get(resId);
325 icon = new ResourceIcon(resId);
326 ICONS.put(resId, icon);
332 public Drawable getDrawable(Context context) {
333 return context.getDrawable(mResId);
337 public boolean equals(Object o) {
338 return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;
342 public String toString() {
343 return String.format("ResourceIcon[resId=0x%08x]", mResId);
347 protected class AnimationIcon extends ResourceIcon {
348 private boolean mAllowAnimation;
350 public AnimationIcon(int resId) {
354 public void setAllowAnimation(boolean allowAnimation) {
355 mAllowAnimation = allowAnimation;
359 public Drawable getDrawable(Context context) {
360 // workaround: get a clean state for every new AVD
361 final AnimatedVectorDrawable d = (AnimatedVectorDrawable) super.getDrawable(context)
362 .getConstantState().newDrawable();
364 if (mAllowAnimation) {
365 mAllowAnimation = false;
367 d.stop(); // skip directly to end state
373 protected enum UserBoolean {
374 USER_TRUE(true, true),
375 USER_FALSE(true, false),
376 BACKGROUND_TRUE(false, true),
377 BACKGROUND_FALSE(false, false);
378 public final boolean value;
379 public final boolean userInitiated;
380 private UserBoolean(boolean userInitiated, boolean value) {
382 this.userInitiated = userInitiated;
386 public static class State {
387 public boolean visible;
390 public String contentDescription;
391 public String dualLabelContentDescription;
392 public boolean autoMirrorDrawable = true;
394 public boolean copyTo(State other) {
395 if (other == null) throw new IllegalArgumentException();
396 if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
397 final boolean changed = other.visible != visible
398 || !Objects.equals(other.icon, icon)
399 || !Objects.equals(other.label, label)
400 || !Objects.equals(other.contentDescription, contentDescription)
401 || !Objects.equals(other.autoMirrorDrawable, autoMirrorDrawable)
402 || !Objects.equals(other.dualLabelContentDescription,
403 dualLabelContentDescription);
404 other.visible = visible;
407 other.contentDescription = contentDescription;
408 other.dualLabelContentDescription = dualLabelContentDescription;
409 other.autoMirrorDrawable = autoMirrorDrawable;
414 public String toString() {
415 return toStringBuilder().toString();
418 protected StringBuilder toStringBuilder() {
419 final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
420 sb.append("visible=").append(visible);
421 sb.append(",icon=").append(icon);
422 sb.append(",label=").append(label);
423 sb.append(",contentDescription=").append(contentDescription);
424 sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
425 sb.append(",autoMirrorDrawable=").append(autoMirrorDrawable);
426 return sb.append(']');
430 public static class BooleanState extends State {
431 public boolean value;
434 public boolean copyTo(State other) {
435 final BooleanState o = (BooleanState) other;
436 final boolean changed = super.copyTo(other) || o.value != value;
442 protected StringBuilder toStringBuilder() {
443 final StringBuilder rt = super.toStringBuilder();
444 rt.insert(rt.length() - 1, ",value=" + value);
449 public static final class SignalState extends State {
450 public boolean enabled;
451 public boolean connected;
452 public boolean activityIn;
453 public boolean activityOut;
454 public int overlayIconId;
455 public boolean filter;
456 public boolean isOverlayIconWide;
459 public boolean copyTo(State other) {
460 final SignalState o = (SignalState) other;
461 final boolean changed = o.enabled != enabled
462 || o.connected != connected || o.activityIn != activityIn
463 || o.activityOut != activityOut
464 || o.overlayIconId != overlayIconId
465 || o.isOverlayIconWide != isOverlayIconWide;
467 o.connected = connected;
468 o.activityIn = activityIn;
469 o.activityOut = activityOut;
470 o.overlayIconId = overlayIconId;
472 o.isOverlayIconWide = isOverlayIconWide;
473 return super.copyTo(other) || changed;
477 protected StringBuilder toStringBuilder() {
478 final StringBuilder rt = super.toStringBuilder();
479 rt.insert(rt.length() - 1, ",enabled=" + enabled);
480 rt.insert(rt.length() - 1, ",connected=" + connected);
481 rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
482 rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
483 rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
484 rt.insert(rt.length() - 1, ",filter=" + filter);
485 rt.insert(rt.length() - 1, ",wideOverlayIcon=" + isOverlayIconWide);