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