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