TileService.java revision 9429513cc3ea6e58e330865bd621b57cb3477551
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.annotation.SystemApi;
20import android.app.Dialog;
21import android.app.Service;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.graphics.drawable.Icon;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.Looper;
29import android.os.Message;
30import android.os.RemoteException;
31import android.view.WindowManager;
32
33/**
34 * A TileService provides the user a tile that can be added to Quick Settings.
35 * Quick Settings is a space provided that allows the user to change settings and
36 * take quick actions without leaving the context of their current app.
37 *
38 * <p>The lifecycle of a TileService is different from some other services in
39 * that it may be unbound during parts of its lifecycle.  Any of the following
40 * lifecycle events can happen indepently in a separate binding/creation of the
41 * service.</p>
42 *
43 * <ul>
44 * <li>When a tile is added by the user its TileService will be bound to and
45 * {@link #onTileAdded()} will be called.</li>
46 *
47 * <li>When a tile should be up to date and listing will be indicated by
48 * {@link #onStartListening()} and {@link #onStopListening()}.</li>
49 *
50 * <li>When the user removes a tile from Quick Settings {@link #onTileRemoved()}
51 * will be called.</li>
52 * </ul>
53 * <p>TileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
54 * and require the permission "android.permission.BIND_QUICK_SETTINGS_TILE".
55 * The label and icon for the service will be used as the default label and
56 * icon for the tile. Here is an example TileService declaration.</p>
57 * <pre class="prettyprint">
58 * {@literal
59 * <service
60 *     android:name=".MyQSTileService"
61 *     android:label="@string/my_default_tile_label"
62 *     android:icon="@drawable/my_default_icon_label"
63 *     android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
64 *     <intent-filter>
65 *         <action android:name="android.service.quicksettings.action.QS_TILE" />
66 *     </intent-filter>
67 * </service>}
68 * </pre>
69 *
70 * @see Tile Tile for details about the UI of a Quick Settings Tile.
71 */
72public class TileService extends Service {
73
74    /**
75     * Action that identifies a Service as being a TileService.
76     */
77    public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
78
79    /**
80     * The tile mode hasn't been set yet.
81     * @hide
82     */
83    public static final int TILE_MODE_UNSET = 0;
84
85    /**
86     * Constant to be returned by {@link #onTileAdded}.
87     * <p>
88     * Passive mode is the default mode for tiles.  The System will tell the tile
89     * when it is most important to update by putting it in the listening state.
90     */
91    public static final int TILE_MODE_PASSIVE = 1;
92
93    /**
94     * Constant to be returned by {@link #onTileAdded}.
95     * <p>
96     * Active mode is for tiles which already listen and keep track of their state in their
97     * own process.  These tiles may request to send an update to the System while their process
98     * is alive using {@link #requestListeningState}.  The System will only bind these tiles
99     * on its own when a click needs to occur.
100     */
101    public static final int TILE_MODE_ACTIVE = 2;
102
103    /**
104     * Used to notify SysUI that Listening has be requested.
105     * @hide
106     */
107    public static final String ACTION_REQUEST_LISTENING
108            = "android.service.quicksettings.action.REQUEST_LISTENING";
109
110    /**
111     * @hide
112     */
113    public static final String EXTRA_COMPONENT = "android.service.quicksettings.extra.COMPONENT";
114
115    private final H mHandler = new H(Looper.getMainLooper());
116
117    private boolean mListening = false;
118    private Tile mTile;
119    private IBinder mToken;
120    private IQSService mService;
121    private Runnable mUnlockRunnable;
122
123    @Override
124    public void onDestroy() {
125        if (mListening) {
126            onStopListening();
127            mListening = false;
128        }
129        super.onDestroy();
130    }
131
132    /**
133     * Called when the user adds this tile to Quick Settings.
134     * <p/>
135     * Note that this is not guaranteed to be called between {@link #onCreate()}
136     * and {@link #onStartListening()}, it will only be called when the tile is added
137     * and not on subsequent binds.
138     *
139     * @see #TILE_MODE_PASSIVE
140     * @see #TILE_MODE_ACTIVE
141     */
142    public int onTileAdded() {
143        return TILE_MODE_PASSIVE;
144    }
145
146    /**
147     * Called when the user removes this tile from Quick Settings.
148     */
149    public void onTileRemoved() {
150    }
151
152    /**
153     * Called when this tile moves into a listening state.
154     * <p/>
155     * When this tile is in a listening state it is expected to keep the
156     * UI up to date.  Any listeners or callbacks needed to keep this tile
157     * up to date should be registered here and unregistered in {@link #onStopListening()}.
158     *
159     * @see #getQsTile()
160     * @see Tile#updateTile()
161     */
162    public void onStartListening() {
163    }
164
165    /**
166     * Called when this tile moves out of the listening state.
167     */
168    public void onStopListening() {
169    }
170
171    /**
172     * Called when the user clicks on this tile.
173     */
174    public void onClick() {
175    }
176
177    /**
178     * Sets an icon to be shown in the status bar.
179     * <p>
180     * The icon will be displayed before all other icons.  Can only be called between
181     * {@link #onStartListening} and {@link #onStopListening}.  Can only be called by system apps.
182     *
183     * @param icon The icon to be displayed, null to hide
184     * @param contentDescription Content description of the icon to be displayed
185     * @hide
186     */
187    @SystemApi
188    public final void setStatusIcon(Icon icon, String contentDescription) {
189        if (mService != null) {
190            try {
191                mService.updateStatusIcon(mTile, icon, contentDescription);
192            } catch (RemoteException e) {
193            }
194        }
195    }
196
197    /**
198     * Used to show a dialog.
199     *
200     * This will collapse the Quick Settings panel and show the dialog.
201     *
202     * @param dialog Dialog to show.
203     *
204     * @see #isLocked()
205     */
206    public final void showDialog(Dialog dialog) {
207        dialog.getWindow().getAttributes().token = mToken;
208        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_QS_DIALOG);
209        dialog.show();
210        try {
211            mService.onShowDialog(mTile);
212        } catch (RemoteException e) {
213        }
214    }
215
216    /**
217     * Prompts the user to unlock the device before executing the Runnable.
218     * <p>
219     * The user will be prompted for their current security method if applicable
220     * and if successful, runnable will be executed.  The Runnable will not be
221     * executed if the user fails to unlock the device or cancels the operation.
222     */
223    public final void unlockAndRun(Runnable runnable) {
224        mUnlockRunnable = runnable;
225        try {
226            mService.startUnlockAndRun(mTile);
227        } catch (RemoteException e) {
228        }
229    }
230
231    /**
232     * Checks if the device is in a secure state.
233     *
234     * TileServices should detect when the device is secure and change their behavior
235     * accordingly.
236     *
237     * @return true if the device is secure.
238     */
239    public final boolean isSecure() {
240        try {
241            return mService.isSecure();
242        } catch (RemoteException e) {
243            return true;
244        }
245    }
246
247    /**
248     * Checks if the lock screen is showing.
249     *
250     * When a device is locked, then {@link #showDialog} will not present a dialog, as it will
251     * be under the lock screen. If the behavior of the Tile is safe to do while locked,
252     * then the user should use {@link #startActivity} to launch an activity on top of the lock
253     * screen, otherwise the tile should use {@link #unlockAndRun(Runnable)} to give the
254     * user their security challenge.
255     *
256     * @return true if the device is locked.
257     */
258    public final boolean isLocked() {
259        try {
260            return mService.isLocked();
261        } catch (RemoteException e) {
262            return true;
263        }
264    }
265
266    /**
267     * Start an activity while collapsing the panel.
268     */
269    public final void startActivityAndCollapse(Intent intent) {
270        startActivity(intent);
271        try {
272            mService.onStartActivity(mTile);
273        } catch (RemoteException e) {
274        }
275    }
276
277    /**
278     * Gets the {@link Tile} for this service.
279     * <p/>
280     * This tile may be used to get or set the current state for this
281     * tile. This tile is only valid for updates between {@link #onStartListening()}
282     * and {@link #onStopListening()}.
283     */
284    public final Tile getQsTile() {
285        return mTile;
286    }
287
288    @Override
289    public IBinder onBind(Intent intent) {
290        return new IQSTileService.Stub() {
291            @Override
292            public void setQSService(IQSService service) throws RemoteException {
293                mHandler.obtainMessage(H.MSG_SET_SERVICE, service).sendToTarget();
294            }
295
296            @Override
297            public void setQSTile(Tile tile) throws RemoteException {
298                mHandler.obtainMessage(H.MSG_SET_TILE, tile).sendToTarget();
299            }
300
301            @Override
302            public void onTileRemoved() throws RemoteException {
303                mHandler.sendEmptyMessage(H.MSG_TILE_REMOVED);
304            }
305
306            @Override
307            public void onTileAdded() throws RemoteException {
308                mHandler.sendEmptyMessage(H.MSG_TILE_ADDED);
309            }
310
311            @Override
312            public void onStopListening() throws RemoteException {
313                mHandler.sendEmptyMessage(H.MSG_STOP_LISTENING);
314            }
315
316            @Override
317            public void onStartListening() throws RemoteException {
318                mHandler.sendEmptyMessage(H.MSG_START_LISTENING);
319            }
320
321            @Override
322            public void onClick(IBinder wtoken) throws RemoteException {
323                mHandler.obtainMessage(H.MSG_TILE_CLICKED, wtoken).sendToTarget();
324            }
325
326            @Override
327            public void onUnlockComplete() throws RemoteException{
328                mHandler.sendEmptyMessage(H.MSG_UNLOCK_COMPLETE);
329            }
330        };
331    }
332
333    private class H extends Handler {
334        private static final int MSG_SET_TILE = 1;
335        private static final int MSG_START_LISTENING = 2;
336        private static final int MSG_STOP_LISTENING = 3;
337        private static final int MSG_TILE_ADDED = 4;
338        private static final int MSG_TILE_REMOVED = 5;
339        private static final int MSG_TILE_CLICKED = 6;
340        private static final int MSG_SET_SERVICE = 7;
341        private static final int MSG_UNLOCK_COMPLETE = 8;
342
343        public H(Looper looper) {
344            super(looper);
345        }
346
347        @Override
348        public void handleMessage(Message msg) {
349            switch (msg.what) {
350                case MSG_SET_SERVICE:
351                    mService = (IQSService) msg.obj;
352                    if (mTile != null) {
353                        mTile.setService(mService);
354                    }
355                    break;
356                case MSG_SET_TILE:
357                    mTile = (Tile) msg.obj;
358                    if (mService != null && mTile != null) {
359                        mTile.setService(mService);
360                    }
361                    break;
362                case MSG_TILE_ADDED:
363                    int mode = TileService.this.onTileAdded();
364                    if (mService == null) {
365                        return;
366                    }
367                    try {
368                        mService.setTileMode(new ComponentName(TileService.this,
369                                TileService.this.getClass()), mode);
370                    } catch (RemoteException e) {
371                    }
372                    break;
373                case MSG_TILE_REMOVED:
374                    if (mListening) {
375                        mListening = false;
376                        TileService.this.onStopListening();
377                    }
378                    TileService.this.onTileRemoved();
379                    break;
380                case MSG_STOP_LISTENING:
381                    if (mListening) {
382                        mListening = false;
383                        TileService.this.onStopListening();
384                    }
385                    break;
386                case MSG_START_LISTENING:
387                    if (!mListening) {
388                        mListening = true;
389                        TileService.this.onStartListening();
390                    }
391                    break;
392                case MSG_TILE_CLICKED:
393                    mToken = (IBinder) msg.obj;
394                    TileService.this.onClick();
395                    break;
396                case MSG_UNLOCK_COMPLETE:
397                    if (mUnlockRunnable != null) {
398                        mUnlockRunnable.run();
399                    }
400                    break;
401            }
402        }
403    }
404
405    /**
406     * Requests that a tile be put in the listening state so it can send an update.
407     *
408     * This method is only applicable to tiles that return {@link #TILE_MODE_ACTIVE} from
409     * {@link #onTileAdded()}, and will do nothing otherwise.
410     */
411    public static final void requestListeningState(Context context, ComponentName component) {
412        Intent intent = new Intent(ACTION_REQUEST_LISTENING);
413        intent.putExtra(EXTRA_COMPONENT, component);
414        context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
415    }
416}
417