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