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