blob: c02465b3f55568837dd6fbb412376f2c05904bdc [file] [log] [blame]
Jason Monkbbadff82015-11-06 15:47:26 -05001/*
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
Jason Monkfe8f6822015-12-21 15:12:01 -050018import android.Manifest;
Jason Monk66c89c12016-01-06 08:51:26 -050019import android.annotation.SystemApi;
Jason Monk8f7f3182015-11-18 16:35:14 -050020import android.app.Dialog;
Jason Monkbbadff82015-11-06 15:47:26 -050021import android.app.Service;
Jason Monkfe8f6822015-12-21 15:12:01 -050022import android.content.ComponentName;
23import android.content.Context;
Jason Monkbbadff82015-11-06 15:47:26 -050024import android.content.Intent;
Jason Monk66c89c12016-01-06 08:51:26 -050025import android.graphics.drawable.Icon;
Jason Monkbbadff82015-11-06 15:47:26 -050026import android.os.Handler;
27import android.os.IBinder;
28import android.os.Looper;
29import android.os.Message;
30import android.os.RemoteException;
Jason Monk8f7f3182015-11-18 16:35:14 -050031import android.view.WindowManager;
Jason Monkbbadff82015-11-06 15:47:26 -050032
33/**
Jason Monkd5a204f2015-12-21 08:50:01 -050034 * A TileService provides the user a tile that can be added to Quick Settings.
Jason Monkbbadff82015-11-06 15:47:26 -050035 * 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 *
Jason Monkd5a204f2015-12-21 08:50:01 -050038 * <p>The lifecycle of a TileService is different from some other services in
Jason Monkbbadff82015-11-06 15:47:26 -050039 * 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>
Jason Monkd5a204f2015-12-21 08:50:01 -050044 * <li>When a tile is added by the user its TileService will be bound to and
Jason Monkbbadff82015-11-06 15:47:26 -050045 * {@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 *
Jason Monkfe8f6822015-12-21 15:12:01 -050050 * <li>When the user removes a tile from Quick Settings {@link #onTileRemoved()}
Jason Monkbbadff82015-11-06 15:47:26 -050051 * will be called.</li>
52 * </ul>
Jason Monkd5a204f2015-12-21 08:50:01 -050053 * <p>TileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
Jason Monkbbadff82015-11-06 15:47:26 -050054 * 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
Jason Monkd5a204f2015-12-21 08:50:01 -050056 * icon for the tile. Here is an example TileService declaration.</p>
Jason Monkbbadff82015-11-06 15:47:26 -050057 * <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>
Jason Monk8f7f3182015-11-18 16:35:14 -050065 * <action android:name="android.service.quicksettings.action.QS_TILE" />
Jason Monkbbadff82015-11-06 15:47:26 -050066 * </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 /**
Jason Monkd5a204f2015-12-21 08:50:01 -050075 * Action that identifies a Service as being a TileService.
Jason Monkbbadff82015-11-06 15:47:26 -050076 */
77 public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
78
Jason Monkfe8f6822015-12-21 15:12:01 -050079 /**
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
Jason Monkbbadff82015-11-06 15:47:26 -0500115 private final H mHandler = new H(Looper.getMainLooper());
116
117 private boolean mListening = false;
118 private Tile mTile;
Jason Monk8f7f3182015-11-18 16:35:14 -0500119 private IBinder mToken;
Jason Monkfe8f6822015-12-21 15:12:01 -0500120 private IQSService mService;
Jason Monk94295132016-01-12 11:27:02 -0500121 private Runnable mUnlockRunnable;
Jason Monkbbadff82015-11-06 15:47:26 -0500122
Jason Monk161ccb52015-12-17 16:43:07 -0500123 @Override
124 public void onDestroy() {
125 if (mListening) {
126 onStopListening();
127 mListening = false;
128 }
129 super.onDestroy();
130 }
131
Jason Monkbbadff82015-11-06 15:47:26 -0500132 /**
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.
Jason Monkfe8f6822015-12-21 15:12:01 -0500138 *
139 * @see #TILE_MODE_PASSIVE
140 * @see #TILE_MODE_ACTIVE
Jason Monkbbadff82015-11-06 15:47:26 -0500141 */
Jason Monkfe8f6822015-12-21 15:12:01 -0500142 public int onTileAdded() {
143 return TILE_MODE_PASSIVE;
Jason Monkbbadff82015-11-06 15:47:26 -0500144 }
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 /**
Jason Monk66c89c12016-01-06 08:51:26 -0500178 * 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 /**
Jason Monk8f7f3182015-11-18 16:35:14 -0500198 * 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.
Jason Monk94295132016-01-12 11:27:02 -0500203 *
204 * @see #isLocked()
Jason Monk8f7f3182015-11-18 16:35:14 -0500205 */
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();
Jason Monkfe8f6822015-12-21 15:12:01 -0500210 try {
211 mService.onShowDialog(mTile);
212 } catch (RemoteException e) {
213 }
Jason Monk8f7f3182015-11-18 16:35:14 -0500214 }
215
216 /**
Jason Monk94295132016-01-12 11:27:02 -0500217 * 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 /**
Jason Monkbbadff82015-11-06 15:47:26 -0500278 * 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
Jason Monkfe8f6822015-12-21 15:12:01 -0500292 public void setQSService(IQSService service) throws RemoteException {
293 mHandler.obtainMessage(H.MSG_SET_SERVICE, service).sendToTarget();
294 }
295
296 @Override
Jason Monkbbadff82015-11-06 15:47:26 -0500297 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
Jason Monk8f7f3182015-11-18 16:35:14 -0500322 public void onClick(IBinder wtoken) throws RemoteException {
323 mHandler.obtainMessage(H.MSG_TILE_CLICKED, wtoken).sendToTarget();
Jason Monkbbadff82015-11-06 15:47:26 -0500324 }
Jason Monk94295132016-01-12 11:27:02 -0500325
326 @Override
327 public void onUnlockComplete() throws RemoteException{
328 mHandler.sendEmptyMessage(H.MSG_UNLOCK_COMPLETE);
329 }
Jason Monkbbadff82015-11-06 15:47:26 -0500330 };
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;
Jason Monkfe8f6822015-12-21 15:12:01 -0500340 private static final int MSG_SET_SERVICE = 7;
Jason Monk94295132016-01-12 11:27:02 -0500341 private static final int MSG_UNLOCK_COMPLETE = 8;
Jason Monkbbadff82015-11-06 15:47:26 -0500342
343 public H(Looper looper) {
344 super(looper);
345 }
346
347 @Override
348 public void handleMessage(Message msg) {
349 switch (msg.what) {
Jason Monkfe8f6822015-12-21 15:12:01 -0500350 case MSG_SET_SERVICE:
351 mService = (IQSService) msg.obj;
352 if (mTile != null) {
353 mTile.setService(mService);
354 }
355 break;
Jason Monkbbadff82015-11-06 15:47:26 -0500356 case MSG_SET_TILE:
357 mTile = (Tile) msg.obj;
Jason Monkfe8f6822015-12-21 15:12:01 -0500358 if (mService != null && mTile != null) {
359 mTile.setService(mService);
360 }
Jason Monkbbadff82015-11-06 15:47:26 -0500361 break;
362 case MSG_TILE_ADDED:
Jason Monkfe8f6822015-12-21 15:12:01 -0500363 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 }
Jason Monkbbadff82015-11-06 15:47:26 -0500372 break;
373 case MSG_TILE_REMOVED:
Jason Monk51c444b2016-01-06 16:32:29 -0500374 if (mListening) {
375 mListening = false;
376 TileService.this.onStopListening();
377 }
Jason Monk161ccb52015-12-17 16:43:07 -0500378 TileService.this.onTileRemoved();
Jason Monkbbadff82015-11-06 15:47:26 -0500379 break;
Jason Monk8f7f3182015-11-18 16:35:14 -0500380 case MSG_STOP_LISTENING:
Jason Monkbbadff82015-11-06 15:47:26 -0500381 if (mListening) {
382 mListening = false;
383 TileService.this.onStopListening();
384 }
385 break;
Jason Monk8f7f3182015-11-18 16:35:14 -0500386 case MSG_START_LISTENING:
Jason Monkbbadff82015-11-06 15:47:26 -0500387 if (!mListening) {
388 mListening = true;
389 TileService.this.onStartListening();
390 }
391 break;
392 case MSG_TILE_CLICKED:
Jason Monk8f7f3182015-11-18 16:35:14 -0500393 mToken = (IBinder) msg.obj;
Jason Monkbbadff82015-11-06 15:47:26 -0500394 TileService.this.onClick();
395 break;
Jason Monk94295132016-01-12 11:27:02 -0500396 case MSG_UNLOCK_COMPLETE:
397 if (mUnlockRunnable != null) {
398 mUnlockRunnable.run();
399 }
400 break;
Jason Monkbbadff82015-11-06 15:47:26 -0500401 }
402 }
403 }
Jason Monkfe8f6822015-12-21 15:12:01 -0500404
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 }
Jason Monkbbadff82015-11-06 15:47:26 -0500416}