QSTile.java revision 899f439a29ab1e609e7ba2aea3e9de3afef35c50
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.R;
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;
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    private final int mFeedbackStartDelay;
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        mFeedbackStartDelay = mContext.getResources().getInteger(R.integer.feedback_start_delay);
74    }
75
76    public boolean supportsDualTargets() {
77        return false;
78    }
79
80    public Host getHost() {
81        return mHost;
82    }
83
84    public QSTileView createTileView(Context context) {
85        return new QSTileView(context);
86    }
87
88    public View createDetailView(Context context, ViewGroup root) {
89        return null; // optional
90    }
91
92    // safe to call from any thread
93
94    public void setCallback(Callback callback) {
95        mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
96    }
97
98    public void click() {
99        mHandler.sendEmptyMessage(H.CLICK);
100    }
101
102    public void secondaryClick() {
103        mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
104    }
105
106    public void showDetail(boolean show) {
107        mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
108    }
109
110    protected final void refreshState() {
111        refreshState(null);
112    }
113
114    protected final void refreshState(Object arg) {
115        mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
116    }
117
118    public void userSwitch(int newUserId) {
119        mHandler.obtainMessage(H.USER_SWITCH, newUserId).sendToTarget();
120    }
121
122    protected void postAfterFeedback(Runnable runnable) {
123        mHandler.postDelayed(runnable, mFeedbackStartDelay);
124    }
125
126    // call only on tile worker looper
127
128    private void handleSetCallback(Callback callback) {
129        mCallback = callback;
130        handleRefreshState(null);
131    }
132
133    protected void handleSecondaryClick() {
134        // optional
135    }
136
137    protected void handleRefreshState(Object arg) {
138        handleUpdateState(mTmpState, arg);
139        final boolean changed = mTmpState.copyTo(mState);
140        if (changed) {
141            handleStateChanged();
142        }
143    }
144
145    private void handleStateChanged() {
146        if (mCallback != null) {
147            mCallback.onStateChanged(mState);
148        }
149    }
150
151    private void handleShowDetail(boolean show) {
152        if (mCallback != null) {
153            mCallback.onShowDetail(show);
154        }
155    }
156
157    protected void handleUserSwitch(int newUserId) {
158        handleRefreshState(null);
159    }
160
161    protected final class H extends Handler {
162        private static final int SET_CALLBACK = 1;
163        private static final int CLICK = 2;
164        private static final int SECONDARY_CLICK = 3;
165        private static final int REFRESH_STATE = 4;
166        private static final int SHOW_DETAIL = 5;
167        private static final int USER_SWITCH = 6;
168
169        private H(Looper looper) {
170            super(looper);
171        }
172
173        @Override
174        public void handleMessage(Message msg) {
175            String name = null;
176            try {
177                if (msg.what == SET_CALLBACK) {
178                    name = "handleSetCallback";
179                    handleSetCallback((QSTile.Callback)msg.obj);
180                } else if (msg.what == CLICK) {
181                    name = "handleClick";
182                    handleClick();
183                } else if (msg.what == SECONDARY_CLICK) {
184                    name = "handleSecondaryClick";
185                    handleSecondaryClick();
186                } else if (msg.what == REFRESH_STATE) {
187                    name = "handleRefreshState";
188                    handleRefreshState(msg.obj);
189                } else if (msg.what == SHOW_DETAIL) {
190                    name = "handleShowDetail";
191                    handleShowDetail(msg.arg1 != 0);
192                } else if (msg.what == USER_SWITCH) {
193                    name = "handleUserSwitch";
194                    handleUserSwitch(msg.arg1);
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        BluetoothController getBluetoothController();
216        LocationController getLocationController();
217        RotationLockController getRotationLockController();
218        List<QSTile<?>> getTiles();
219        NetworkController getNetworkController();
220        ZenModeController getZenModeController();
221        TetheringController getTetheringController();
222        CastController getCastController();
223        VolumeComponent getVolumeComponent();
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        public boolean filter;
291
292        @Override
293        public boolean copyTo(State other) {
294            final SignalState o = (SignalState) other;
295            final boolean changed = o.enabled != enabled
296                    || o.connected != connected || o.activityIn != activityIn
297                    || o.activityOut != activityOut
298                    || o.overlayIconId != overlayIconId;
299            o.enabled = enabled;
300            o.connected = connected;
301            o.activityIn = activityIn;
302            o.activityOut = activityOut;
303            o.overlayIconId = overlayIconId;
304            o.filter = filter;
305            return super.copyTo(other) || changed;
306        }
307
308        @Override
309        protected StringBuilder toStringBuilder() {
310            final StringBuilder rt = super.toStringBuilder();
311            rt.insert(rt.length() - 1, ",enabled=" + enabled);
312            rt.insert(rt.length() - 1, ",connected=" + connected);
313            rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
314            rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
315            rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
316            rt.insert(rt.length() - 1, ",filter=" + filter);
317            return rt;
318        }
319    }
320}
321