blob: 5a84db547e1e9ffee80a69ba3b4eba9f1a237177 [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
John Spurlockaf8d6c42014-05-07 17:49:08 -0400152 // call only on tile worker looper
153
154 private void handleSetCallback(Callback callback) {
155 mCallback = callback;
156 handleRefreshState(null);
157 }
158
159 protected void handleSecondaryClick() {
160 // optional
161 }
162
John Spurlockc247b8f2014-11-06 23:06:25 -0500163 protected void handleLongClick() {
164 // optional
165 }
166
John Spurlockaf8d6c42014-05-07 17:49:08 -0400167 protected void handleRefreshState(Object arg) {
168 handleUpdateState(mTmpState, arg);
169 final boolean changed = mTmpState.copyTo(mState);
170 if (changed) {
171 handleStateChanged();
172 }
173 }
174
175 private void handleStateChanged() {
Selim Cinek4fda7b22014-08-18 22:07:25 +0200176 boolean delayAnnouncement = shouldAnnouncementBeDelayed();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400177 if (mCallback != null) {
178 mCallback.onStateChanged(mState);
Selim Cinek4fda7b22014-08-18 22:07:25 +0200179 if (mAnnounceNextStateChange && !delayAnnouncement) {
180 String announcement = composeChangeAnnouncement();
181 if (announcement != null) {
182 mCallback.onAnnouncementRequested(announcement);
183 }
184 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400185 }
Selim Cinek4fda7b22014-08-18 22:07:25 +0200186 mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
187 }
188
189 protected boolean shouldAnnouncementBeDelayed() {
190 return false;
191 }
192
193 protected String composeChangeAnnouncement() {
194 return null;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400195 }
196
197 private void handleShowDetail(boolean show) {
198 if (mCallback != null) {
199 mCallback.onShowDetail(show);
200 }
201 }
202
John Spurlock7f8f22a2014-07-02 18:54:17 -0400203 private void handleToggleStateChanged(boolean state) {
204 if (mCallback != null) {
205 mCallback.onToggleStateChanged(state);
206 }
207 }
208
John Spurlock486b78e2014-07-07 08:37:56 -0400209 private void handleScanStateChanged(boolean state) {
210 if (mCallback != null) {
211 mCallback.onScanStateChanged(state);
212 }
213 }
214
John Spurlockaf8d6c42014-05-07 17:49:08 -0400215 protected void handleUserSwitch(int newUserId) {
216 handleRefreshState(null);
217 }
218
John Spurlockbceed062014-08-10 18:04:16 -0400219 protected void handleDestroy() {
220 setListening(false);
221 mCallback = null;
222 }
223
John Spurlockaf8d6c42014-05-07 17:49:08 -0400224 protected final class H extends Handler {
225 private static final int SET_CALLBACK = 1;
226 private static final int CLICK = 2;
227 private static final int SECONDARY_CLICK = 3;
John Spurlockc247b8f2014-11-06 23:06:25 -0500228 private static final int LONG_CLICK = 4;
229 private static final int REFRESH_STATE = 5;
230 private static final int SHOW_DETAIL = 6;
231 private static final int USER_SWITCH = 7;
232 private static final int TOGGLE_STATE_CHANGED = 8;
233 private static final int SCAN_STATE_CHANGED = 9;
234 private static final int DESTROY = 10;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400235
236 private H(Looper looper) {
237 super(looper);
238 }
239
240 @Override
241 public void handleMessage(Message msg) {
242 String name = null;
243 try {
244 if (msg.what == SET_CALLBACK) {
245 name = "handleSetCallback";
246 handleSetCallback((QSTile.Callback)msg.obj);
247 } else if (msg.what == CLICK) {
248 name = "handleClick";
Selim Cinek4fda7b22014-08-18 22:07:25 +0200249 mAnnounceNextStateChange = true;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400250 handleClick();
251 } else if (msg.what == SECONDARY_CLICK) {
252 name = "handleSecondaryClick";
253 handleSecondaryClick();
John Spurlockc247b8f2014-11-06 23:06:25 -0500254 } else if (msg.what == LONG_CLICK) {
255 name = "handleLongClick";
256 handleLongClick();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400257 } else if (msg.what == REFRESH_STATE) {
258 name = "handleRefreshState";
259 handleRefreshState(msg.obj);
260 } else if (msg.what == SHOW_DETAIL) {
261 name = "handleShowDetail";
262 handleShowDetail(msg.arg1 != 0);
263 } else if (msg.what == USER_SWITCH) {
264 name = "handleUserSwitch";
265 handleUserSwitch(msg.arg1);
John Spurlock7f8f22a2014-07-02 18:54:17 -0400266 } else if (msg.what == TOGGLE_STATE_CHANGED) {
267 name = "handleToggleStateChanged";
268 handleToggleStateChanged(msg.arg1 != 0);
John Spurlock486b78e2014-07-07 08:37:56 -0400269 } else if (msg.what == SCAN_STATE_CHANGED) {
270 name = "handleScanStateChanged";
271 handleScanStateChanged(msg.arg1 != 0);
John Spurlockbceed062014-08-10 18:04:16 -0400272 } else if (msg.what == DESTROY) {
273 name = "handleDestroy";
274 handleDestroy();
275 } else {
276 throw new IllegalArgumentException("Unknown msg: " + msg.what);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400277 }
278 } catch (Throwable t) {
279 final String error = "Error in " + name;
280 Log.w(TAG, error, t);
281 mHost.warn(error, t);
282 }
283 }
284 }
285
286 public interface Callback {
287 void onStateChanged(State state);
288 void onShowDetail(boolean show);
John Spurlock7f8f22a2014-07-02 18:54:17 -0400289 void onToggleStateChanged(boolean state);
John Spurlock486b78e2014-07-07 08:37:56 -0400290 void onScanStateChanged(boolean state);
Selim Cinek4fda7b22014-08-18 22:07:25 +0200291 void onAnnouncementRequested(CharSequence announcement);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400292 }
293
294 public interface Host {
295 void startSettingsActivity(Intent intent);
296 void warn(String message, Throwable t);
297 void collapsePanels();
298 Looper getLooper();
299 Context getContext();
John Spurlockbceed062014-08-10 18:04:16 -0400300 Collection<QSTile<?>> getTiles();
301 void setCallback(Callback callback);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400302 BluetoothController getBluetoothController();
303 LocationController getLocationController();
304 RotationLockController getRotationLockController();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400305 NetworkController getNetworkController();
306 ZenModeController getZenModeController();
Jason Monk51e4dc02014-07-22 12:00:47 -0400307 HotspotController getHotspotController();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400308 CastController getCastController();
Adrian Roosb83777b2014-06-30 15:11:53 +0200309 FlashlightController getFlashlightController();
John Spurlock657c62c2014-07-22 12:18:09 -0400310 KeyguardMonitor getKeyguardMonitor();
John Spurlockbceed062014-08-10 18:04:16 -0400311
312 public interface Callback {
313 void onTilesChanged();
314 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400315 }
316
John Spurlock2d695812014-10-30 13:25:21 -0400317 public static abstract class Icon {
318 abstract public Drawable getDrawable(Context context);
319
320 @Override
321 public int hashCode() {
322 return Icon.class.hashCode();
323 }
324 }
325
326 public static class ResourceIcon extends Icon {
327 private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
328
Andrew Flynna478d702015-04-14 23:33:45 -0400329 protected final int mResId;
John Spurlock2d695812014-10-30 13:25:21 -0400330
331 private ResourceIcon(int resId) {
332 mResId = resId;
333 }
334
335 public static Icon get(int resId) {
336 Icon icon = ICONS.get(resId);
337 if (icon == null) {
338 icon = new ResourceIcon(resId);
339 ICONS.put(resId, icon);
340 }
341 return icon;
342 }
343
344 @Override
345 public Drawable getDrawable(Context context) {
Andrew Flynna478d702015-04-14 23:33:45 -0400346 Drawable d = context.getDrawable(mResId);
347 if (d instanceof Animatable) {
348 ((Animatable) d).start();
349 }
350 return d;
John Spurlock2d695812014-10-30 13:25:21 -0400351 }
352
353 @Override
354 public boolean equals(Object o) {
355 return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;
356 }
357
358 @Override
359 public String toString() {
360 return String.format("ResourceIcon[resId=0x%08x]", mResId);
361 }
362 }
363
364 protected class AnimationIcon extends ResourceIcon {
365 private boolean mAllowAnimation;
366
367 public AnimationIcon(int resId) {
368 super(resId);
369 }
370
371 public void setAllowAnimation(boolean allowAnimation) {
372 mAllowAnimation = allowAnimation;
373 }
374
375 @Override
376 public Drawable getDrawable(Context context) {
377 // workaround: get a clean state for every new AVD
Andrew Flynna478d702015-04-14 23:33:45 -0400378 final AnimatedVectorDrawable d = (AnimatedVectorDrawable) context.getDrawable(mResId)
John Spurlock2d695812014-10-30 13:25:21 -0400379 .getConstantState().newDrawable();
380 d.start();
381 if (mAllowAnimation) {
382 mAllowAnimation = false;
383 } else {
384 d.stop(); // skip directly to end state
385 }
386 return d;
387 }
388 }
389
390 protected enum UserBoolean {
391 USER_TRUE(true, true),
392 USER_FALSE(true, false),
393 BACKGROUND_TRUE(false, true),
394 BACKGROUND_FALSE(false, false);
395 public final boolean value;
396 public final boolean userInitiated;
397 private UserBoolean(boolean userInitiated, boolean value) {
398 this.value = value;
399 this.userInitiated = userInitiated;
400 }
401 }
402
John Spurlockaf8d6c42014-05-07 17:49:08 -0400403 public static class State {
404 public boolean visible;
John Spurlock2d695812014-10-30 13:25:21 -0400405 public Icon icon;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400406 public String label;
407 public String contentDescription;
Selim Cineke5557a92014-08-15 19:59:23 +0200408 public String dualLabelContentDescription;
Selim Cinek06d3bca2014-08-26 17:29:20 +0200409 public boolean autoMirrorDrawable = true;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400410
411 public boolean copyTo(State other) {
412 if (other == null) throw new IllegalArgumentException();
413 if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
414 final boolean changed = other.visible != visible
John Spurlockaf8d6c42014-05-07 17:49:08 -0400415 || !Objects.equals(other.icon, icon)
416 || !Objects.equals(other.label, label)
Selim Cineke5557a92014-08-15 19:59:23 +0200417 || !Objects.equals(other.contentDescription, contentDescription)
Selim Cinek06d3bca2014-08-26 17:29:20 +0200418 || !Objects.equals(other.autoMirrorDrawable, autoMirrorDrawable)
Selim Cineke5557a92014-08-15 19:59:23 +0200419 || !Objects.equals(other.dualLabelContentDescription,
420 dualLabelContentDescription);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400421 other.visible = visible;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400422 other.icon = icon;
423 other.label = label;
424 other.contentDescription = contentDescription;
Selim Cineke5557a92014-08-15 19:59:23 +0200425 other.dualLabelContentDescription = dualLabelContentDescription;
Selim Cinek06d3bca2014-08-26 17:29:20 +0200426 other.autoMirrorDrawable = autoMirrorDrawable;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400427 return changed;
428 }
429
430 @Override
431 public String toString() {
432 return toStringBuilder().toString();
433 }
434
435 protected StringBuilder toStringBuilder() {
John Spurlock2d695812014-10-30 13:25:21 -0400436 final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
John Spurlockaf8d6c42014-05-07 17:49:08 -0400437 sb.append("visible=").append(visible);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400438 sb.append(",icon=").append(icon);
439 sb.append(",label=").append(label);
440 sb.append(",contentDescription=").append(contentDescription);
Selim Cineke5557a92014-08-15 19:59:23 +0200441 sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
Selim Cinek06d3bca2014-08-26 17:29:20 +0200442 sb.append(",autoMirrorDrawable=").append(autoMirrorDrawable);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400443 return sb.append(']');
444 }
445 }
446
447 public static class BooleanState extends State {
448 public boolean value;
449
450 @Override
451 public boolean copyTo(State other) {
452 final BooleanState o = (BooleanState) other;
453 final boolean changed = super.copyTo(other) || o.value != value;
454 o.value = value;
455 return changed;
456 }
457
458 @Override
459 protected StringBuilder toStringBuilder() {
460 final StringBuilder rt = super.toStringBuilder();
461 rt.insert(rt.length() - 1, ",value=" + value);
462 return rt;
463 }
464 }
465
466 public static final class SignalState extends State {
467 public boolean enabled;
468 public boolean connected;
469 public boolean activityIn;
470 public boolean activityOut;
471 public int overlayIconId;
John Spurlock899f4392014-06-11 10:59:11 -0400472 public boolean filter;
Jorim Jaggic737b9b2014-09-08 23:57:20 +0200473 public boolean isOverlayIconWide;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400474
475 @Override
476 public boolean copyTo(State other) {
477 final SignalState o = (SignalState) other;
478 final boolean changed = o.enabled != enabled
479 || o.connected != connected || o.activityIn != activityIn
480 || o.activityOut != activityOut
Jorim Jaggic737b9b2014-09-08 23:57:20 +0200481 || o.overlayIconId != overlayIconId
482 || o.isOverlayIconWide != isOverlayIconWide;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400483 o.enabled = enabled;
484 o.connected = connected;
485 o.activityIn = activityIn;
486 o.activityOut = activityOut;
487 o.overlayIconId = overlayIconId;
John Spurlock899f4392014-06-11 10:59:11 -0400488 o.filter = filter;
Jorim Jaggic737b9b2014-09-08 23:57:20 +0200489 o.isOverlayIconWide = isOverlayIconWide;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400490 return super.copyTo(other) || changed;
491 }
492
493 @Override
494 protected StringBuilder toStringBuilder() {
495 final StringBuilder rt = super.toStringBuilder();
496 rt.insert(rt.length() - 1, ",enabled=" + enabled);
497 rt.insert(rt.length() - 1, ",connected=" + connected);
498 rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
499 rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
500 rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
John Spurlock899f4392014-06-11 10:59:11 -0400501 rt.insert(rt.length() - 1, ",filter=" + filter);
Jorim Jaggic737b9b2014-09-08 23:57:20 +0200502 rt.insert(rt.length() - 1, ",wideOverlayIcon=" + isOverlayIconWide);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400503 return rt;
504 }
505 }
506}