TileService.java revision fe8f6826ce3c2beeb1fce54c67978ce69f849407
1/*
2 * Copyright (C) 2015 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 */
16package android.service.quicksettings;
17
18import android.Manifest;
19import android.app.Dialog;
20import android.app.Service;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.os.Handler;
25import android.os.IBinder;
26import android.os.Looper;
27import android.os.Message;
28import android.os.RemoteException;
29import android.view.WindowManager;
30
31/**
32 * A TileService provides the user a tile that can be added to Quick Settings.
33 * Quick Settings is a space provided that allows the user to change settings and
34 * take quick actions without leaving the context of their current app.
35 *
36 * <p>The lifecycle of a TileService is different from some other services in
37 * that it may be unbound during parts of its lifecycle.  Any of the following
38 * lifecycle events can happen indepently in a separate binding/creation of the
39 * service.</p>
40 *
41 * <ul>
42 * <li>When a tile is added by the user its TileService will be bound to and
43 * {@link #onTileAdded()} will be called.</li>
44 *
45 * <li>When a tile should be up to date and listing will be indicated by
46 * {@link #onStartListening()} and {@link #onStopListening()}.</li>
47 *
48 * <li>When the user removes a tile from Quick Settings {@link #onTileRemoved()}
49 * will be called.</li>
50 * </ul>
51 * <p>TileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
52 * and require the permission "android.permission.BIND_QUICK_SETTINGS_TILE".
53 * The label and icon for the service will be used as the default label and
54 * icon for the tile. Here is an example TileService declaration.</p>
55 * <pre class="prettyprint">
56 * {@literal
57 * <service
58 *     android:name=".MyQSTileService"
59 *     android:label="@string/my_default_tile_label"
60 *     android:icon="@drawable/my_default_icon_label"
61 *     android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
62 *     <intent-filter>
63 *         <action android:name="android.service.quicksettings.action.QS_TILE" />
64 *     </intent-filter>
65 * </service>}
66 * </pre>
67 *
68 * @see Tile Tile for details about the UI of a Quick Settings Tile.
69 */
70public class TileService extends Service {
71
72    /**
73     * Action that identifies a Service as being a TileService.
74     */
75    public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
76
77    /**
78     * The tile mode hasn't been set yet.
79     * @hide
80     */
81    public static final int TILE_MODE_UNSET = 0;
82
83    /**
84     * Constant to be returned by {@link #onTileAdded}.
85     * <p>
86     * Passive mode is the default mode for tiles.  The System will tell the tile
87     * when it is most important to update by putting it in the listening state.
88     */
89    public static final int TILE_MODE_PASSIVE = 1;
90
91    /**
92     * Constant to be returned by {@link #onTileAdded}.
93     * <p>
94     * Active mode is for tiles which already listen and keep track of their state in their
95     * own process.  These tiles may request to send an update to the System while their process
96     * is alive using {@link #requestListeningState}.  The System will only bind these tiles
97     * on its own when a click needs to occur.
98     */
99    public static final int TILE_MODE_ACTIVE = 2;
100
101    /**
102     * Used to notify SysUI that Listening has be requested.
103     * @hide
104     */
105    public static final String ACTION_REQUEST_LISTENING
106            = "android.service.quicksettings.action.REQUEST_LISTENING";
107
108    /**
109     * @hide
110     */
111    public static final String EXTRA_COMPONENT = "android.service.quicksettings.extra.COMPONENT";
112
113    private final H mHandler = new H(Looper.getMainLooper());
114
115    private boolean mListening = false;
116    private Tile mTile;
117    private IBinder mToken;
118    private IQSService mService;
119
120    @Override
121    public void onDestroy() {
122        if (mListening) {
123            onStopListening();
124            mListening = false;
125        }
126        super.onDestroy();
127    }
128
129    /**
130     * Called when the user adds this tile to Quick Settings.
131     * <p/>
132     * Note that this is not guaranteed to be called between {@link #onCreate()}
133     * and {@link #onStartListening()}, it will only be called when the tile is added
134     * and not on subsequent binds.
135     *
136     * @see #TILE_MODE_PASSIVE
137     * @see #TILE_MODE_ACTIVE
138     */
139    public int onTileAdded() {
140        return TILE_MODE_PASSIVE;
141    }
142
143    /**
144     * Called when the user removes this tile from Quick Settings.
145     */
146    public void onTileRemoved() {
147    }
148
149    /**
150     * Called when this tile moves into a listening state.
151     * <p/>
152     * When this tile is in a listening state it is expected to keep the
153     * UI up to date.  Any listeners or callbacks needed to keep this tile
154     * up to date should be registered here and unregistered in {@link #onStopListening()}.
155     *
156     * @see #getQsTile()
157     * @see Tile#updateTile()
158     */
159    public void onStartListening() {
160    }
161
162    /**
163     * Called when this tile moves out of the listening state.
164     */
165    public void onStopListening() {
166    }
167
168    /**
169     * Called when the user clicks on this tile.
170     */
171    public void onClick() {
172    }
173
174    /**
175     * Used to show a dialog.
176     *
177     * This will collapse the Quick Settings panel and show the dialog.
178     *
179     * @param dialog Dialog to show.
180     */
181    public final void showDialog(Dialog dialog) {
182        dialog.getWindow().getAttributes().token = mToken;
183        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_QS_DIALOG);
184        dialog.show();
185        try {
186            mService.onShowDialog(mTile);
187        } catch (RemoteException e) {
188        }
189    }
190
191    /**
192     * Gets the {@link Tile} for this service.
193     * <p/>
194     * This tile may be used to get or set the current state for this
195     * tile. This tile is only valid for updates between {@link #onStartListening()}
196     * and {@link #onStopListening()}.
197     */
198    public final Tile getQsTile() {
199        return mTile;
200    }
201
202    @Override
203    public IBinder onBind(Intent intent) {
204        return new IQSTileService.Stub() {
205            @Override
206            public void setQSService(IQSService service) throws RemoteException {
207                mHandler.obtainMessage(H.MSG_SET_SERVICE, service).sendToTarget();
208            }
209
210            @Override
211            public void setQSTile(Tile tile) throws RemoteException {
212                mHandler.obtainMessage(H.MSG_SET_TILE, tile).sendToTarget();
213            }
214
215            @Override
216            public void onTileRemoved() throws RemoteException {
217                mHandler.sendEmptyMessage(H.MSG_TILE_REMOVED);
218            }
219
220            @Override
221            public void onTileAdded() throws RemoteException {
222                mHandler.sendEmptyMessage(H.MSG_TILE_ADDED);
223            }
224
225            @Override
226            public void onStopListening() throws RemoteException {
227                mHandler.sendEmptyMessage(H.MSG_STOP_LISTENING);
228            }
229
230            @Override
231            public void onStartListening() throws RemoteException {
232                mHandler.sendEmptyMessage(H.MSG_START_LISTENING);
233            }
234
235            @Override
236            public void onClick(IBinder wtoken) throws RemoteException {
237                mHandler.obtainMessage(H.MSG_TILE_CLICKED, wtoken).sendToTarget();
238            }
239        };
240    }
241
242    private class H extends Handler {
243        private static final int MSG_SET_TILE = 1;
244        private static final int MSG_START_LISTENING = 2;
245        private static final int MSG_STOP_LISTENING = 3;
246        private static final int MSG_TILE_ADDED = 4;
247        private static final int MSG_TILE_REMOVED = 5;
248        private static final int MSG_TILE_CLICKED = 6;
249        private static final int MSG_SET_SERVICE = 7;
250
251        public H(Looper looper) {
252            super(looper);
253        }
254
255        @Override
256        public void handleMessage(Message msg) {
257            switch (msg.what) {
258                case MSG_SET_SERVICE:
259                    mService = (IQSService) msg.obj;
260                    if (mTile != null) {
261                        mTile.setService(mService);
262                    }
263                    break;
264                case MSG_SET_TILE:
265                    mTile = (Tile) msg.obj;
266                    if (mService != null && mTile != null) {
267                        mTile.setService(mService);
268                    }
269                    break;
270                case MSG_TILE_ADDED:
271                    int mode = TileService.this.onTileAdded();
272                    if (mService == null) {
273                        return;
274                    }
275                    try {
276                        mService.setTileMode(new ComponentName(TileService.this,
277                                TileService.this.getClass()), mode);
278                    } catch (RemoteException e) {
279                    }
280                    break;
281                case MSG_TILE_REMOVED:
282                    TileService.this.onTileRemoved();
283                    break;
284                case MSG_STOP_LISTENING:
285                    if (mListening) {
286                        mListening = false;
287                        TileService.this.onStopListening();
288                    }
289                    break;
290                case MSG_START_LISTENING:
291                    if (!mListening) {
292                        mListening = true;
293                        TileService.this.onStartListening();
294                    }
295                    break;
296                case MSG_TILE_CLICKED:
297                    mToken = (IBinder) msg.obj;
298                    TileService.this.onClick();
299                    break;
300            }
301        }
302    }
303
304    /**
305     * Requests that a tile be put in the listening state so it can send an update.
306     *
307     * This method is only applicable to tiles that return {@link #TILE_MODE_ACTIVE} from
308     * {@link #onTileAdded()}, and will do nothing otherwise.
309     */
310    public static final void requestListeningState(Context context, ComponentName component) {
311        Intent intent = new Intent(ACTION_REQUEST_LISTENING);
312        intent.putExtra(EXTRA_COMPONENT, component);
313        context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
314    }
315}
316