QSTile.java revision 30c305ce6283ce1380ad91ef0d221696b32d5a6b
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.Listenable;
34import com.android.systemui.statusbar.policy.LocationController;
35import com.android.systemui.statusbar.policy.NetworkController;
36import com.android.systemui.statusbar.policy.RotationLockController;
37import com.android.systemui.statusbar.policy.TetheringController;
38import com.android.systemui.statusbar.policy.ZenModeController;
39import com.android.systemui.volume.VolumeComponent;
40
41import java.util.List;
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 = false;
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 View createDetailView(Context context, ViewGroup root) {
87        return null; // optional
88    }
89
90    // safe to call from any thread
91
92    public void setCallback(Callback callback) {
93        mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
94    }
95
96    public void click() {
97        mHandler.sendEmptyMessage(H.CLICK);
98    }
99
100    public void secondaryClick() {
101        mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
102    }
103
104    public void showDetail(boolean show) {
105        mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
106    }
107
108    protected final void refreshState() {
109        refreshState(null);
110    }
111
112    protected final void refreshState(Object arg) {
113        mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
114    }
115
116    public void userSwitch(int newUserId) {
117        mHandler.obtainMessage(H.USER_SWITCH, newUserId).sendToTarget();
118    }
119
120    // call only on tile worker looper
121
122    private void handleSetCallback(Callback callback) {
123        mCallback = callback;
124        handleRefreshState(null);
125    }
126
127    protected void handleSecondaryClick() {
128        // optional
129    }
130
131    protected void handleRefreshState(Object arg) {
132        handleUpdateState(mTmpState, arg);
133        final boolean changed = mTmpState.copyTo(mState);
134        if (changed) {
135            handleStateChanged();
136        }
137    }
138
139    private void handleStateChanged() {
140        if (mCallback != null) {
141            mCallback.onStateChanged(mState);
142        }
143    }
144
145    private void handleShowDetail(boolean show) {
146        if (mCallback != null) {
147            mCallback.onShowDetail(show);
148        }
149    }
150
151    protected void handleUserSwitch(int newUserId) {
152        handleRefreshState(null);
153    }
154
155    protected final class H extends Handler {
156        private static final int SET_CALLBACK = 1;
157        private static final int CLICK = 2;
158        private static final int SECONDARY_CLICK = 3;
159        private static final int REFRESH_STATE = 4;
160        private static final int SHOW_DETAIL = 5;
161        private static final int USER_SWITCH = 6;
162
163        private H(Looper looper) {
164            super(looper);
165        }
166
167        @Override
168        public void handleMessage(Message msg) {
169            String name = null;
170            try {
171                if (msg.what == SET_CALLBACK) {
172                    name = "handleSetCallback";
173                    handleSetCallback((QSTile.Callback)msg.obj);
174                } else if (msg.what == CLICK) {
175                    name = "handleClick";
176                    handleClick();
177                } else if (msg.what == SECONDARY_CLICK) {
178                    name = "handleSecondaryClick";
179                    handleSecondaryClick();
180                } else if (msg.what == REFRESH_STATE) {
181                    name = "handleRefreshState";
182                    handleRefreshState(msg.obj);
183                } else if (msg.what == SHOW_DETAIL) {
184                    name = "handleShowDetail";
185                    handleShowDetail(msg.arg1 != 0);
186                } else if (msg.what == USER_SWITCH) {
187                    name = "handleUserSwitch";
188                    handleUserSwitch(msg.arg1);
189                }
190            } catch (Throwable t) {
191                final String error = "Error in " + name;
192                Log.w(TAG, error, t);
193                mHost.warn(error, t);
194            }
195        }
196    }
197
198    public interface Callback {
199        void onStateChanged(State state);
200        void onShowDetail(boolean show);
201    }
202
203    public interface Host {
204        void startSettingsActivity(Intent intent);
205        void warn(String message, Throwable t);
206        void collapsePanels();
207        Looper getLooper();
208        Context getContext();
209        BluetoothController getBluetoothController();
210        LocationController getLocationController();
211        RotationLockController getRotationLockController();
212        List<QSTile<?>> getTiles();
213        NetworkController getNetworkController();
214        ZenModeController getZenModeController();
215        TetheringController getTetheringController();
216        CastController getCastController();
217        VolumeComponent getVolumeComponent();
218        FlashlightController getFlashlightController();
219    }
220
221    public static class State {
222        public boolean visible;
223        public int iconId;
224        public Drawable icon;
225        public String label;
226        public String contentDescription;
227
228        public boolean copyTo(State other) {
229            if (other == null) throw new IllegalArgumentException();
230            if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
231            final boolean changed = other.visible != visible
232                    || other.iconId != iconId
233                    || !Objects.equals(other.icon, icon)
234                    || !Objects.equals(other.label, label)
235                    || !Objects.equals(other.contentDescription, contentDescription);
236            other.visible = visible;
237            other.iconId = iconId;
238            other.icon = icon;
239            other.label = label;
240            other.contentDescription = contentDescription;
241            return changed;
242        }
243
244        @Override
245        public String toString() {
246            return toStringBuilder().toString();
247        }
248
249        protected StringBuilder toStringBuilder() {
250            final StringBuilder sb = new StringBuilder(  getClass().getSimpleName()).append('[');
251            sb.append("visible=").append(visible);
252            sb.append(",iconId=").append(iconId);
253            sb.append(",icon=").append(icon);
254            sb.append(",label=").append(label);
255            sb.append(",contentDescription=").append(contentDescription);
256            return sb.append(']');
257        }
258    }
259
260    public static class BooleanState extends State {
261        public boolean value;
262
263        @Override
264        public boolean copyTo(State other) {
265            final BooleanState o = (BooleanState) other;
266            final boolean changed = super.copyTo(other) || o.value != value;
267            o.value = value;
268            return changed;
269        }
270
271        @Override
272        protected StringBuilder toStringBuilder() {
273            final StringBuilder rt = super.toStringBuilder();
274            rt.insert(rt.length() - 1, ",value=" + value);
275            return rt;
276        }
277    }
278
279    public static final class SignalState extends State {
280        public boolean enabled;
281        public boolean connected;
282        public boolean activityIn;
283        public boolean activityOut;
284        public int overlayIconId;
285        public boolean filter;
286
287        @Override
288        public boolean copyTo(State other) {
289            final SignalState o = (SignalState) other;
290            final boolean changed = o.enabled != enabled
291                    || o.connected != connected || o.activityIn != activityIn
292                    || o.activityOut != activityOut
293                    || o.overlayIconId != overlayIconId;
294            o.enabled = enabled;
295            o.connected = connected;
296            o.activityIn = activityIn;
297            o.activityOut = activityOut;
298            o.overlayIconId = overlayIconId;
299            o.filter = filter;
300            return super.copyTo(other) || changed;
301        }
302
303        @Override
304        protected StringBuilder toStringBuilder() {
305            final StringBuilder rt = super.toStringBuilder();
306            rt.insert(rt.length() - 1, ",enabled=" + enabled);
307            rt.insert(rt.length() - 1, ",connected=" + connected);
308            rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
309            rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
310            rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
311            rt.insert(rt.length() - 1, ",filter=" + filter);
312            return rt;
313        }
314    }
315}
316