blob: b9574dc12ec3a31d317c3ed8f4dd4d502cc5b0ff [file] [log] [blame]
John Spurlockaf8d6c42014-05-07 17:49:08 -04001/*
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
17package com.android.systemui.qs;
18
19import android.content.Context;
20import android.content.Intent;
Andrew Flynna478d702015-04-14 23:33:45 -040021import android.graphics.drawable.Animatable;
John Spurlock2d695812014-10-30 13:25:21 -040022import android.graphics.drawable.AnimatedVectorDrawable;
John Spurlock444eb2e2014-05-14 13:32:14 -040023import android.graphics.drawable.Drawable;
John Spurlockaf8d6c42014-05-07 17:49:08 -040024import android.os.Handler;
25import android.os.Looper;
26import android.os.Message;
27import android.util.Log;
John Spurlock2d695812014-10-30 13:25:21 -040028import android.util.SparseArray;
John Spurlockaf8d6c42014-05-07 17:49:08 -040029import android.view.View;
30import android.view.ViewGroup;
31
32import com.android.systemui.qs.QSTile.State;
33import com.android.systemui.statusbar.policy.BluetoothController;
34import com.android.systemui.statusbar.policy.CastController;
Adrian Roosb83777b2014-06-30 15:11:53 +020035import com.android.systemui.statusbar.policy.FlashlightController;
John Spurlock657c62c2014-07-22 12:18:09 -040036import com.android.systemui.statusbar.policy.KeyguardMonitor;
John Spurlockccb6b9a2014-05-17 15:54:40 -040037import com.android.systemui.statusbar.policy.Listenable;
John Spurlockaf8d6c42014-05-07 17:49:08 -040038import com.android.systemui.statusbar.policy.LocationController;
39import com.android.systemui.statusbar.policy.NetworkController;
40import com.android.systemui.statusbar.policy.RotationLockController;
Jason Monk51e4dc02014-07-22 12:00:47 -040041import com.android.systemui.statusbar.policy.HotspotController;
John Spurlockaf8d6c42014-05-07 17:49:08 -040042import com.android.systemui.statusbar.policy.ZenModeController;
43
John Spurlockbceed062014-08-10 18:04:16 -040044import java.util.Collection;
John Spurlockaf8d6c42014-05-07 17:49:08 -040045import java.util.Objects;
46
47/**
48 * Base quick-settings tile, extend this to create a new tile.
49 *
50 * State management done on a looper provided by the host. Tiles should update state in
51 * handleUpdateState. Callbacks affecting state should use refreshState to trigger another
52 * state update pass on tile looper.
53 */
John Spurlockccb6b9a2014-05-17 15:54:40 -040054public abstract class QSTile<TState extends State> implements Listenable {
55 protected final String TAG = "QSTile." + getClass().getSimpleName();
John Spurlock486b78e2014-07-07 08:37:56 -040056 protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG);
John Spurlockaf8d6c42014-05-07 17:49:08 -040057
58 protected final Host mHost;
59 protected final Context mContext;
60 protected final H mHandler;
John Spurlock76c43b92014-05-13 21:10:51 -040061 protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
John Spurlockaf8d6c42014-05-07 17:49:08 -040062
63 private Callback mCallback;
64 protected final TState mState = newTileState();
65 private final TState mTmpState = newTileState();
Selim Cinek4fda7b22014-08-18 22:07:25 +020066 private boolean mAnnounceNextStateChange;
John Spurlockaf8d6c42014-05-07 17:49:08 -040067
68 abstract protected TState newTileState();
69 abstract protected void handleClick();
70 abstract protected void handleUpdateState(TState state, Object arg);
71
72 protected QSTile(Host host) {
73 mHost = host;
74 mContext = host.getContext();
75 mHandler = new H(host.getLooper());
76 }
77
John Spurlockccb6b9a2014-05-17 15:54:40 -040078 public boolean supportsDualTargets() {
79 return false;
80 }
81
John Spurlockaf8d6c42014-05-07 17:49:08 -040082 public Host getHost() {
83 return mHost;
84 }
85
86 public QSTileView createTileView(Context context) {
87 return new QSTileView(context);
88 }
89
John Spurlock7f8f22a2014-07-02 18:54:17 -040090 public DetailAdapter getDetailAdapter() {
John Spurlockaf8d6c42014-05-07 17:49:08 -040091 return null; // optional
92 }
93
John Spurlock7f8f22a2014-07-02 18:54:17 -040094 public interface DetailAdapter {
95 int getTitle();
96 Boolean getToggleState();
97 View createDetailView(Context context, View convertView, ViewGroup parent);
98 Intent getSettingsIntent();
99 void setToggleState(boolean state);
100 }
101
John Spurlockaf8d6c42014-05-07 17:49:08 -0400102 // safe to call from any thread
103
104 public void setCallback(Callback callback) {
105 mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
106 }
107
108 public void click() {
109 mHandler.sendEmptyMessage(H.CLICK);
110 }
111
112 public void secondaryClick() {
113 mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
114 }
115
John Spurlockc247b8f2014-11-06 23:06:25 -0500116 public void longClick() {
117 mHandler.sendEmptyMessage(H.LONG_CLICK);
118 }
119
John Spurlockaf8d6c42014-05-07 17:49:08 -0400120 public void showDetail(boolean show) {
121 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
122 }
123
124 protected final void refreshState() {
125 refreshState(null);
126 }
127
128 protected final void refreshState(Object arg) {
129 mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
130 }
131
132 public void userSwitch(int newUserId) {
Adrian Roos32d88e82014-09-24 17:08:22 +0200133 mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400134 }
135
John Spurlock7f8f22a2014-07-02 18:54:17 -0400136 public void fireToggleStateChanged(boolean state) {
137 mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
138 }
139
John Spurlock486b78e2014-07-07 08:37:56 -0400140 public void fireScanStateChanged(boolean state) {
141 mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
142 }
143
John Spurlockbceed062014-08-10 18:04:16 -0400144 public void destroy() {
145 mHandler.sendEmptyMessage(H.DESTROY);
146 }
147
148 public TState getState() {
149 return mState;
150 }
151
Jason Monk0d6a1c42015-04-20 16:38:51 -0400152 public void setDetailListening(boolean listening) {
153 // optional
154 }
155
John Spurlockaf8d6c42014-05-07 17:49:08 -0400156 // call only on tile worker looper
157
158 private void handleSetCallback(Callback callback) {
159 mCallback = callback;
160 handleRefreshState(null);
161 }
162
163 protected void handleSecondaryClick() {
164 // optional
165 }
166
John Spurlockc247b8f2014-11-06 23:06:25 -0500167 protected void handleLongClick() {
168 // optional
169 }
170
John Spurlockaf8d6c42014-05-07 17:49:08 -0400171 protected void handleRefreshState(Object arg) {
172 handleUpdateState(mTmpState, arg);
173 final boolean changed = mTmpState.copyTo(mState);
174 if (changed) {
175 handleStateChanged();
176 }
177 }
178
179 private void handleStateChanged() {
Selim Cinek4fda7b22014-08-18 22:07:25 +0200180 boolean delayAnnouncement = shouldAnnouncementBeDelayed();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400181 if (mCallback != null) {
182 mCallback.onStateChanged(mState);
Selim Cinek4fda7b22014-08-18 22:07:25 +0200183 if (mAnnounceNextStateChange && !delayAnnouncement) {
184 String announcement = composeChangeAnnouncement();
185 if (announcement != null) {
186 mCallback.onAnnouncementRequested(announcement);
187 }
188 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400189 }
Selim Cinek4fda7b22014-08-18 22:07:25 +0200190 mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
191 }
192
193 protected boolean shouldAnnouncementBeDelayed() {
194 return false;
195 }
196
197 protected String composeChangeAnnouncement() {
198 return null;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400199 }
200
201 private void handleShowDetail(boolean show) {
202 if (mCallback != null) {
203 mCallback.onShowDetail(show);
204 }
205 }
206
John Spurlock7f8f22a2014-07-02 18:54:17 -0400207 private void handleToggleStateChanged(boolean state) {
208 if (mCallback != null) {
209 mCallback.onToggleStateChanged(state);
210 }
211 }
212
John Spurlock486b78e2014-07-07 08:37:56 -0400213 private void handleScanStateChanged(boolean state) {
214 if (mCallback != null) {
215 mCallback.onScanStateChanged(state);
216 }
217 }
218
John Spurlockaf8d6c42014-05-07 17:49:08 -0400219 protected void handleUserSwitch(int newUserId) {
220 handleRefreshState(null);
221 }
222
John Spurlockbceed062014-08-10 18:04:16 -0400223 protected void handleDestroy() {
224 setListening(false);
225 mCallback = null;
226 }
227
John Spurlockaf8d6c42014-05-07 17:49:08 -0400228 protected final class H extends Handler {
229 private static final int SET_CALLBACK = 1;
230 private static final int CLICK = 2;
231 private static final int SECONDARY_CLICK = 3;
John Spurlockc247b8f2014-11-06 23:06:25 -0500232 private static final int LONG_CLICK = 4;
233 private static final int REFRESH_STATE = 5;
234 private static final int SHOW_DETAIL = 6;
235 private static final int USER_SWITCH = 7;
236 private static final int TOGGLE_STATE_CHANGED = 8;
237 private static final int SCAN_STATE_CHANGED = 9;
238 private static final int DESTROY = 10;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400239
240 private H(Looper looper) {
241 super(looper);
242 }
243
244 @Override
245 public void handleMessage(Message msg) {
246 String name = null;
247 try {
248 if (msg.what == SET_CALLBACK) {
249 name = "handleSetCallback";
250 handleSetCallback((QSTile.Callback)msg.obj);
251 } else if (msg.what == CLICK) {
252 name = "handleClick";
Selim Cinek4fda7b22014-08-18 22:07:25 +0200253 mAnnounceNextStateChange = true;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400254 handleClick();
255 } else if (msg.what == SECONDARY_CLICK) {
256 name = "handleSecondaryClick";
257 handleSecondaryClick();
John Spurlockc247b8f2014-11-06 23:06:25 -0500258 } else if (msg.what == LONG_CLICK) {
259 name = "handleLongClick";
260 handleLongClick();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400261 } else if (msg.what == REFRESH_STATE) {
262 name = "handleRefreshState";
263 handleRefreshState(msg.obj);
264 } else if (msg.what == SHOW_DETAIL) {
265 name = "handleShowDetail";
266 handleShowDetail(msg.arg1 != 0);
267 } else if (msg.what == USER_SWITCH) {
268 name = "handleUserSwitch";
269 handleUserSwitch(msg.arg1);
John Spurlock7f8f22a2014-07-02 18:54:17 -0400270 } else if (msg.what == TOGGLE_STATE_CHANGED) {
271 name = "handleToggleStateChanged";
272 handleToggleStateChanged(msg.arg1 != 0);
John Spurlock486b78e2014-07-07 08:37:56 -0400273 } else if (msg.what == SCAN_STATE_CHANGED) {
274 name = "handleScanStateChanged";
275 handleScanStateChanged(msg.arg1 != 0);
John Spurlockbceed062014-08-10 18:04:16 -0400276 } else if (msg.what == DESTROY) {
277 name = "handleDestroy";
278 handleDestroy();
279 } else {
280 throw new IllegalArgumentException("Unknown msg: " + msg.what);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400281 }
282 } catch (Throwable t) {
283 final String error = "Error in " + name;
284 Log.w(TAG, error, t);
285 mHost.warn(error, t);
286 }
287 }
288 }
289
290 public interface Callback {
291 void onStateChanged(State state);
292 void onShowDetail(boolean show);
John Spurlock7f8f22a2014-07-02 18:54:17 -0400293 void onToggleStateChanged(boolean state);
John Spurlock486b78e2014-07-07 08:37:56 -0400294 void onScanStateChanged(boolean state);
Selim Cinek4fda7b22014-08-18 22:07:25 +0200295 void onAnnouncementRequested(CharSequence announcement);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400296 }
297
298 public interface Host {
299 void startSettingsActivity(Intent intent);
300 void warn(String message, Throwable t);
301 void collapsePanels();
302 Looper getLooper();
303 Context getContext();
John Spurlockbceed062014-08-10 18:04:16 -0400304 Collection<QSTile<?>> getTiles();
305 void setCallback(Callback callback);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400306 BluetoothController getBluetoothController();
307 LocationController getLocationController();
308 RotationLockController getRotationLockController();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400309 NetworkController getNetworkController();
310 ZenModeController getZenModeController();
Jason Monk51e4dc02014-07-22 12:00:47 -0400311 HotspotController getHotspotController();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400312 CastController getCastController();
Adrian Roosb83777b2014-06-30 15:11:53 +0200313 FlashlightController getFlashlightController();
John Spurlock657c62c2014-07-22 12:18:09 -0400314 KeyguardMonitor getKeyguardMonitor();
John Spurlockbceed062014-08-10 18:04:16 -0400315
316 public interface Callback {
317 void onTilesChanged();
318 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400319 }
320
John Spurlock2d695812014-10-30 13:25:21 -0400321 public static abstract class Icon {
322 abstract public Drawable getDrawable(Context context);
323
324 @Override
325 public int hashCode() {
326 return Icon.class.hashCode();
327 }
328 }
329
330 public static class ResourceIcon extends Icon {
331 private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
332
Andrew Flynna478d702015-04-14 23:33:45 -0400333 protected final int mResId;
John Spurlock2d695812014-10-30 13:25:21 -0400334
335 private ResourceIcon(int resId) {
336 mResId = resId;
337 }
338
339 public static Icon get(int resId) {
340 Icon icon = ICONS.get(resId);
341 if (icon == null) {
342 icon = new ResourceIcon(resId);
343 ICONS.put(resId, icon);
344 }
345 return icon;
346 }
347
348 @Override
349 public Drawable getDrawable(Context context) {
Andrew Flynna478d702015-04-14 23:33:45 -0400350 Drawable d = context.getDrawable(mResId);
351 if (d instanceof Animatable) {
352 ((Animatable) d).start();
353 }
354 return d;
John Spurlock2d695812014-10-30 13:25:21 -0400355 }
356
357 @Override
358 public boolean equals(Object o) {
359 return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;
360 }
361
362 @Override
363 public String toString() {
364 return String.format("ResourceIcon[resId=0x%08x]", mResId);
365 }
366 }
367
368 protected class AnimationIcon extends ResourceIcon {
369 private boolean mAllowAnimation;
370
371 public AnimationIcon(int resId) {
372 super(resId);
373 }
374
375 public void setAllowAnimation(boolean allowAnimation) {
376 mAllowAnimation = allowAnimation;
377 }
378
379 @Override
380 public Drawable getDrawable(Context context) {
381 // workaround: get a clean state for every new AVD
Andrew Flynna478d702015-04-14 23:33:45 -0400382 final AnimatedVectorDrawable d = (AnimatedVectorDrawable) context.getDrawable(mResId)
John Spurlock2d695812014-10-30 13:25:21 -0400383 .getConstantState().newDrawable();
384 d.start();
385 if (mAllowAnimation) {
386 mAllowAnimation = false;
387 } else {
388 d.stop(); // skip directly to end state
389 }
390 return d;
391 }
392 }
393
394 protected enum UserBoolean {
395 USER_TRUE(true, true),
396 USER_FALSE(true, false),
397 BACKGROUND_TRUE(false, true),
398 BACKGROUND_FALSE(false, false);
399 public final boolean value;
400 public final boolean userInitiated;
401 private UserBoolean(boolean userInitiated, boolean value) {
402 this.value = value;
403 this.userInitiated = userInitiated;
404 }
405 }
406
John Spurlockaf8d6c42014-05-07 17:49:08 -0400407 public static class State {
408 public boolean visible;
John Spurlock2d695812014-10-30 13:25:21 -0400409 public Icon icon;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400410 public String label;
411 public String contentDescription;
Selim Cineke5557a92014-08-15 19:59:23 +0200412 public String dualLabelContentDescription;
Selim Cinek06d3bca2014-08-26 17:29:20 +0200413 public boolean autoMirrorDrawable = true;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400414
415 public boolean copyTo(State other) {
416 if (other == null) throw new IllegalArgumentException();
417 if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
418 final boolean changed = other.visible != visible
John Spurlockaf8d6c42014-05-07 17:49:08 -0400419 || !Objects.equals(other.icon, icon)
420 || !Objects.equals(other.label, label)
Selim Cineke5557a92014-08-15 19:59:23 +0200421 || !Objects.equals(other.contentDescription, contentDescription)
Selim Cinek06d3bca2014-08-26 17:29:20 +0200422 || !Objects.equals(other.autoMirrorDrawable, autoMirrorDrawable)
Selim Cineke5557a92014-08-15 19:59:23 +0200423 || !Objects.equals(other.dualLabelContentDescription,
424 dualLabelContentDescription);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400425 other.visible = visible;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400426 other.icon = icon;
427 other.label = label;
428 other.contentDescription = contentDescription;
Selim Cineke5557a92014-08-15 19:59:23 +0200429 other.dualLabelContentDescription = dualLabelContentDescription;
Selim Cinek06d3bca2014-08-26 17:29:20 +0200430 other.autoMirrorDrawable = autoMirrorDrawable;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400431 return changed;
432 }
433
434 @Override
435 public String toString() {
436 return toStringBuilder().toString();
437 }
438
439 protected StringBuilder toStringBuilder() {
John Spurlock2d695812014-10-30 13:25:21 -0400440 final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
John Spurlockaf8d6c42014-05-07 17:49:08 -0400441 sb.append("visible=").append(visible);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400442 sb.append(",icon=").append(icon);
443 sb.append(",label=").append(label);
444 sb.append(",contentDescription=").append(contentDescription);
Selim Cineke5557a92014-08-15 19:59:23 +0200445 sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
Selim Cinek06d3bca2014-08-26 17:29:20 +0200446 sb.append(",autoMirrorDrawable=").append(autoMirrorDrawable);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400447 return sb.append(']');
448 }
449 }
450
451 public static class BooleanState extends State {
452 public boolean value;
453
454 @Override
455 public boolean copyTo(State other) {
456 final BooleanState o = (BooleanState) other;
457 final boolean changed = super.copyTo(other) || o.value != value;
458 o.value = value;
459 return changed;
460 }
461
462 @Override
463 protected StringBuilder toStringBuilder() {
464 final StringBuilder rt = super.toStringBuilder();
465 rt.insert(rt.length() - 1, ",value=" + value);
466 return rt;
467 }
468 }
469
470 public static final class SignalState extends State {
471 public boolean enabled;
472 public boolean connected;
473 public boolean activityIn;
474 public boolean activityOut;
475 public int overlayIconId;
John Spurlock899f4392014-06-11 10:59:11 -0400476 public boolean filter;
Jorim Jaggic737b9b2014-09-08 23:57:20 +0200477 public boolean isOverlayIconWide;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400478
479 @Override
480 public boolean copyTo(State other) {
481 final SignalState o = (SignalState) other;
482 final boolean changed = o.enabled != enabled
483 || o.connected != connected || o.activityIn != activityIn
484 || o.activityOut != activityOut
Jorim Jaggic737b9b2014-09-08 23:57:20 +0200485 || o.overlayIconId != overlayIconId
486 || o.isOverlayIconWide != isOverlayIconWide;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400487 o.enabled = enabled;
488 o.connected = connected;
489 o.activityIn = activityIn;
490 o.activityOut = activityOut;
491 o.overlayIconId = overlayIconId;
John Spurlock899f4392014-06-11 10:59:11 -0400492 o.filter = filter;
Jorim Jaggic737b9b2014-09-08 23:57:20 +0200493 o.isOverlayIconWide = isOverlayIconWide;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400494 return super.copyTo(other) || changed;
495 }
496
497 @Override
498 protected StringBuilder toStringBuilder() {
499 final StringBuilder rt = super.toStringBuilder();
500 rt.insert(rt.length() - 1, ",enabled=" + enabled);
501 rt.insert(rt.length() - 1, ",connected=" + connected);
502 rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
503 rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
504 rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
John Spurlock899f4392014-06-11 10:59:11 -0400505 rt.insert(rt.length() - 1, ",filter=" + filter);
Jorim Jaggic737b9b2014-09-08 23:57:20 +0200506 rt.insert(rt.length() - 1, ",wideOverlayIcon=" + isOverlayIconWide);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400507 return rt;
508 }
509 }
510}