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