QSTile.java revision e5557a972ca190cb82026a5dd0c53f4d119fa05a
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
17package com.android.systemui.qs;
18
19import android.content.Context;
20import android.content.Intent;
21import android.graphics.drawable.Drawable;
22import android.os.Handler;
23import android.os.Looper;
24import android.os.Message;
25import android.util.Log;
26import android.view.View;
27import android.view.ViewGroup;
28
29import com.android.systemui.qs.QSTile.State;
30import com.android.systemui.statusbar.policy.BluetoothController;
31import com.android.systemui.statusbar.policy.CastController;
32import com.android.systemui.statusbar.policy.FlashlightController;
33import com.android.systemui.statusbar.policy.KeyguardMonitor;
34import com.android.systemui.statusbar.policy.Listenable;
35import com.android.systemui.statusbar.policy.LocationController;
36import com.android.systemui.statusbar.policy.NetworkController;
37import com.android.systemui.statusbar.policy.RotationLockController;
38import com.android.systemui.statusbar.policy.HotspotController;
39import com.android.systemui.statusbar.policy.ZenModeController;
40
41import java.util.Collection;
42import java.util.Objects;
43
44/**
45 * Base quick-settings tile, extend this to create a new tile.
46 *
47 * State management done on a looper provided by the host.  Tiles should update state in
48 * handleUpdateState.  Callbacks affecting state should use refreshState to trigger another
49 * state update pass on tile looper.
50 */
51public abstract class QSTile<TState extends State> implements Listenable {
52    protected final String TAG = "QSTile." + getClass().getSimpleName();
53    protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG);
54
55    protected final Host mHost;
56    protected final Context mContext;
57    protected final H mHandler;
58    protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
59
60    private Callback mCallback;
61    protected final TState mState = newTileState();
62    private final TState mTmpState = newTileState();
63
64    abstract protected TState newTileState();
65    abstract protected void handleClick();
66    abstract protected void handleUpdateState(TState state, Object arg);
67
68    protected QSTile(Host host) {
69        mHost = host;
70        mContext = host.getContext();
71        mHandler = new H(host.getLooper());
72    }
73
74    public boolean supportsDualTargets() {
75        return false;
76    }
77
78    public Host getHost() {
79        return mHost;
80    }
81
82    public QSTileView createTileView(Context context) {
83        return new QSTileView(context);
84    }
85
86    public DetailAdapter getDetailAdapter() {
87        return null; // optional
88    }
89
90    public interface DetailAdapter {
91        int getTitle();
92        Boolean getToggleState();
93        View createDetailView(Context context, View convertView, ViewGroup parent);
94        Intent getSettingsIntent();
95        void setToggleState(boolean state);
96    }
97
98    // safe to call from any thread
99
100    public void setCallback(Callback callback) {
101        mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
102    }
103
104    public void click() {
105        mHandler.sendEmptyMessage(H.CLICK);
106    }
107
108    public void secondaryClick() {
109        mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
110    }
111
112    public void showDetail(boolean show) {
113        mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
114    }
115
116    protected final void refreshState() {
117        refreshState(null);
118    }
119
120    protected final void refreshState(Object arg) {
121        mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
122    }
123
124    public void userSwitch(int newUserId) {
125        mHandler.obtainMessage(H.USER_SWITCH, newUserId).sendToTarget();
126    }
127
128    public void fireToggleStateChanged(boolean state) {
129        mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
130    }
131
132    public void fireScanStateChanged(boolean state) {
133        mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
134    }
135
136    public void destroy() {
137        mHandler.sendEmptyMessage(H.DESTROY);
138    }
139
140    public TState getState() {
141        return mState;
142    }
143
144    // call only on tile worker looper
145
146    private void handleSetCallback(Callback callback) {
147        mCallback = callback;
148        handleRefreshState(null);
149    }
150
151    protected void handleSecondaryClick() {
152        // optional
153    }
154
155    protected void handleRefreshState(Object arg) {
156        handleUpdateState(mTmpState, arg);
157        final boolean changed = mTmpState.copyTo(mState);
158        if (changed) {
159            handleStateChanged();
160        }
161    }
162
163    private void handleStateChanged() {
164        if (mCallback != null) {
165            mCallback.onStateChanged(mState);
166        }
167    }
168
169    private void handleShowDetail(boolean show) {
170        if (mCallback != null) {
171            mCallback.onShowDetail(show);
172        }
173    }
174
175    private void handleToggleStateChanged(boolean state) {
176        if (mCallback != null) {
177            mCallback.onToggleStateChanged(state);
178        }
179    }
180
181    private void handleScanStateChanged(boolean state) {
182        if (mCallback != null) {
183            mCallback.onScanStateChanged(state);
184        }
185    }
186
187    protected void handleUserSwitch(int newUserId) {
188        handleRefreshState(null);
189    }
190
191    protected void handleDestroy() {
192        setListening(false);
193        mCallback = null;
194    }
195
196    protected final class H extends Handler {
197        private static final int SET_CALLBACK = 1;
198        private static final int CLICK = 2;
199        private static final int SECONDARY_CLICK = 3;
200        private static final int REFRESH_STATE = 4;
201        private static final int SHOW_DETAIL = 5;
202        private static final int USER_SWITCH = 6;
203        private static final int TOGGLE_STATE_CHANGED = 7;
204        private static final int SCAN_STATE_CHANGED = 8;
205        private static final int DESTROY = 9;
206
207        private H(Looper looper) {
208            super(looper);
209        }
210
211        @Override
212        public void handleMessage(Message msg) {
213            String name = null;
214            try {
215                if (msg.what == SET_CALLBACK) {
216                    name = "handleSetCallback";
217                    handleSetCallback((QSTile.Callback)msg.obj);
218                } else if (msg.what == CLICK) {
219                    name = "handleClick";
220                    handleClick();
221                } else if (msg.what == SECONDARY_CLICK) {
222                    name = "handleSecondaryClick";
223                    handleSecondaryClick();
224                } else if (msg.what == REFRESH_STATE) {
225                    name = "handleRefreshState";
226                    handleRefreshState(msg.obj);
227                } else if (msg.what == SHOW_DETAIL) {
228                    name = "handleShowDetail";
229                    handleShowDetail(msg.arg1 != 0);
230                } else if (msg.what == USER_SWITCH) {
231                    name = "handleUserSwitch";
232                    handleUserSwitch(msg.arg1);
233                } else if (msg.what == TOGGLE_STATE_CHANGED) {
234                    name = "handleToggleStateChanged";
235                    handleToggleStateChanged(msg.arg1 != 0);
236                } else if (msg.what == SCAN_STATE_CHANGED) {
237                    name = "handleScanStateChanged";
238                    handleScanStateChanged(msg.arg1 != 0);
239                } else if (msg.what == DESTROY) {
240                    name = "handleDestroy";
241                    handleDestroy();
242                } else {
243                    throw new IllegalArgumentException("Unknown msg: " + msg.what);
244                }
245            } catch (Throwable t) {
246                final String error = "Error in " + name;
247                Log.w(TAG, error, t);
248                mHost.warn(error, t);
249            }
250        }
251    }
252
253    public interface Callback {
254        void onStateChanged(State state);
255        void onShowDetail(boolean show);
256        void onToggleStateChanged(boolean state);
257        void onScanStateChanged(boolean state);
258    }
259
260    public interface Host {
261        void startSettingsActivity(Intent intent);
262        void warn(String message, Throwable t);
263        void collapsePanels();
264        Looper getLooper();
265        Context getContext();
266        Collection<QSTile<?>> getTiles();
267        void setCallback(Callback callback);
268        BluetoothController getBluetoothController();
269        LocationController getLocationController();
270        RotationLockController getRotationLockController();
271        NetworkController getNetworkController();
272        ZenModeController getZenModeController();
273        HotspotController getHotspotController();
274        CastController getCastController();
275        FlashlightController getFlashlightController();
276        KeyguardMonitor getKeyguardMonitor();
277
278        public interface Callback {
279            void onTilesChanged();
280        }
281    }
282
283    public static class State {
284        public boolean visible;
285        public int iconId;
286        public Drawable icon;
287        public String label;
288        public String contentDescription;
289        public String dualLabelContentDescription;
290
291        public boolean copyTo(State other) {
292            if (other == null) throw new IllegalArgumentException();
293            if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
294            final boolean changed = other.visible != visible
295                    || other.iconId != iconId
296                    || !Objects.equals(other.icon, icon)
297                    || !Objects.equals(other.label, label)
298                    || !Objects.equals(other.contentDescription, contentDescription)
299                    || !Objects.equals(other.dualLabelContentDescription,
300                    dualLabelContentDescription);
301            other.visible = visible;
302            other.iconId = iconId;
303            other.icon = icon;
304            other.label = label;
305            other.contentDescription = contentDescription;
306            other.dualLabelContentDescription = dualLabelContentDescription;
307            return changed;
308        }
309
310        @Override
311        public String toString() {
312            return toStringBuilder().toString();
313        }
314
315        protected StringBuilder toStringBuilder() {
316            final StringBuilder sb = new StringBuilder(  getClass().getSimpleName()).append('[');
317            sb.append("visible=").append(visible);
318            sb.append(",iconId=").append(iconId);
319            sb.append(",icon=").append(icon);
320            sb.append(",label=").append(label);
321            sb.append(",contentDescription=").append(contentDescription);
322            sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
323            return sb.append(']');
324        }
325    }
326
327    public static class BooleanState extends State {
328        public boolean value;
329
330        @Override
331        public boolean copyTo(State other) {
332            final BooleanState o = (BooleanState) other;
333            final boolean changed = super.copyTo(other) || o.value != value;
334            o.value = value;
335            return changed;
336        }
337
338        @Override
339        protected StringBuilder toStringBuilder() {
340            final StringBuilder rt = super.toStringBuilder();
341            rt.insert(rt.length() - 1, ",value=" + value);
342            return rt;
343        }
344    }
345
346    public static final class SignalState extends State {
347        public boolean enabled;
348        public boolean connected;
349        public boolean activityIn;
350        public boolean activityOut;
351        public int overlayIconId;
352        public boolean filter;
353
354        @Override
355        public boolean copyTo(State other) {
356            final SignalState o = (SignalState) other;
357            final boolean changed = o.enabled != enabled
358                    || o.connected != connected || o.activityIn != activityIn
359                    || o.activityOut != activityOut
360                    || o.overlayIconId != overlayIconId;
361            o.enabled = enabled;
362            o.connected = connected;
363            o.activityIn = activityIn;
364            o.activityOut = activityOut;
365            o.overlayIconId = overlayIconId;
366            o.filter = filter;
367            return super.copyTo(other) || changed;
368        }
369
370        @Override
371        protected StringBuilder toStringBuilder() {
372            final StringBuilder rt = super.toStringBuilder();
373            rt.insert(rt.length() - 1, ",enabled=" + enabled);
374            rt.insert(rt.length() - 1, ",connected=" + connected);
375            rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
376            rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
377            rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
378            rt.insert(rt.length() - 1, ",filter=" + filter);
379            return rt;
380        }
381    }
382}
383