OSDN Git Service

2a66484fb556f542c56d81fb9cffbcb64d59be43
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / qs / QSTile.java
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
67     abstract protected TState newTileState();
68     abstract protected void handleClick();
69     abstract protected void handleUpdateState(TState state, Object arg);
70
71     protected QSTile(Host host) {
72         mHost = host;
73         mContext = host.getContext();
74         mHandler = new H(host.getLooper());
75     }
76
77     public boolean supportsDualTargets() {
78         return false;
79     }
80
81     public Host getHost() {
82         return mHost;
83     }
84
85     public QSTileView createTileView(Context context) {
86         return new QSTileView(context);
87     }
88
89     public DetailAdapter getDetailAdapter() {
90         return null; // optional
91     }
92
93     public interface DetailAdapter {
94         int getTitle();
95         Boolean getToggleState();
96         View createDetailView(Context context, View convertView, ViewGroup parent);
97         Intent getSettingsIntent();
98         void setToggleState(boolean state);
99     }
100
101     // safe to call from any thread
102
103     public void setCallback(Callback callback) {
104         mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
105     }
106
107     public void click() {
108         mHandler.sendEmptyMessage(H.CLICK);
109     }
110
111     public void secondaryClick() {
112         mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
113     }
114
115     public void showDetail(boolean show) {
116         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
117     }
118
119     protected final void refreshState() {
120         refreshState(null);
121     }
122
123     protected final void refreshState(Object arg) {
124         mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
125     }
126
127     public void userSwitch(int newUserId) {
128         mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
129     }
130
131     public void fireToggleStateChanged(boolean state) {
132         mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
133     }
134
135     public void fireScanStateChanged(boolean state) {
136         mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
137     }
138
139     public void destroy() {
140         mHandler.sendEmptyMessage(H.DESTROY);
141     }
142
143     public TState getState() {
144         return mState;
145     }
146
147     // call only on tile worker looper
148
149     private void handleSetCallback(Callback callback) {
150         mCallback = callback;
151         handleRefreshState(null);
152     }
153
154     protected void handleSecondaryClick() {
155         // optional
156     }
157
158     protected void handleRefreshState(Object arg) {
159         handleUpdateState(mTmpState, arg);
160         final boolean changed = mTmpState.copyTo(mState);
161         if (changed) {
162             handleStateChanged();
163         }
164     }
165
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);
174                 }
175             }
176         }
177         mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
178     }
179
180     protected boolean shouldAnnouncementBeDelayed() {
181         return false;
182     }
183
184     protected String composeChangeAnnouncement() {
185         return null;
186     }
187
188     private void handleShowDetail(boolean show) {
189         if (mCallback != null) {
190             mCallback.onShowDetail(show);
191         }
192     }
193
194     private void handleToggleStateChanged(boolean state) {
195         if (mCallback != null) {
196             mCallback.onToggleStateChanged(state);
197         }
198     }
199
200     private void handleScanStateChanged(boolean state) {
201         if (mCallback != null) {
202             mCallback.onScanStateChanged(state);
203         }
204     }
205
206     protected void handleUserSwitch(int newUserId) {
207         handleRefreshState(null);
208     }
209
210     protected void handleDestroy() {
211         setListening(false);
212         mCallback = null;
213     }
214
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;
225
226         private H(Looper looper) {
227             super(looper);
228         }
229
230         @Override
231         public void handleMessage(Message msg) {
232             String name = null;
233             try {
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;
240                     handleClick();
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";
261                     handleDestroy();
262                 } else {
263                     throw new IllegalArgumentException("Unknown msg: " + msg.what);
264                 }
265             } catch (Throwable t) {
266                 final String error = "Error in " + name;
267                 Log.w(TAG, error, t);
268                 mHost.warn(error, t);
269             }
270         }
271     }
272
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);
279     }
280
281     public interface Host {
282         void startSettingsActivity(Intent intent);
283         void warn(String message, Throwable t);
284         void collapsePanels();
285         Looper getLooper();
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();
298
299         public interface Callback {
300             void onTilesChanged();
301         }
302     }
303
304     public static abstract class Icon {
305         abstract public Drawable getDrawable(Context context);
306
307         @Override
308         public int hashCode() {
309             return Icon.class.hashCode();
310         }
311     }
312
313     public static class ResourceIcon extends Icon {
314         private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
315
316         private final int mResId;
317
318         private ResourceIcon(int resId) {
319             mResId = resId;
320         }
321
322         public static Icon get(int resId) {
323             Icon icon = ICONS.get(resId);
324             if (icon == null) {
325                 icon = new ResourceIcon(resId);
326                 ICONS.put(resId, icon);
327             }
328             return icon;
329         }
330
331         @Override
332         public Drawable getDrawable(Context context) {
333             return context.getDrawable(mResId);
334         }
335
336         @Override
337         public boolean equals(Object o) {
338             return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;
339         }
340
341         @Override
342         public String toString() {
343             return String.format("ResourceIcon[resId=0x%08x]", mResId);
344         }
345     }
346
347     protected class AnimationIcon extends ResourceIcon {
348         private boolean mAllowAnimation;
349
350         public AnimationIcon(int resId) {
351             super(resId);
352         }
353
354         public void setAllowAnimation(boolean allowAnimation) {
355             mAllowAnimation = allowAnimation;
356         }
357
358         @Override
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();
363             d.start();
364             if (mAllowAnimation) {
365                 mAllowAnimation = false;
366             } else {
367                 d.stop(); // skip directly to end state
368             }
369             return d;
370         }
371     }
372
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) {
381             this.value = value;
382             this.userInitiated = userInitiated;
383         }
384     }
385
386     public static class State {
387         public boolean visible;
388         public Icon icon;
389         public String label;
390         public String contentDescription;
391         public String dualLabelContentDescription;
392         public boolean autoMirrorDrawable = true;
393
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;
405             other.icon = icon;
406             other.label = label;
407             other.contentDescription = contentDescription;
408             other.dualLabelContentDescription = dualLabelContentDescription;
409             other.autoMirrorDrawable = autoMirrorDrawable;
410             return changed;
411         }
412
413         @Override
414         public String toString() {
415             return toStringBuilder().toString();
416         }
417
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(']');
427         }
428     }
429
430     public static class BooleanState extends State {
431         public boolean value;
432
433         @Override
434         public boolean copyTo(State other) {
435             final BooleanState o = (BooleanState) other;
436             final boolean changed = super.copyTo(other) || o.value != value;
437             o.value = value;
438             return changed;
439         }
440
441         @Override
442         protected StringBuilder toStringBuilder() {
443             final StringBuilder rt = super.toStringBuilder();
444             rt.insert(rt.length() - 1, ",value=" + value);
445             return rt;
446         }
447     }
448
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;
457
458         @Override
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;
466             o.enabled = enabled;
467             o.connected = connected;
468             o.activityIn = activityIn;
469             o.activityOut = activityOut;
470             o.overlayIconId = overlayIconId;
471             o.filter = filter;
472             o.isOverlayIconWide = isOverlayIconWide;
473             return super.copyTo(other) || changed;
474         }
475
476         @Override
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);
486             return rt;
487         }
488     }
489 }