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