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