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