TileService.java revision 724214a87828ad0fe8a44db796a1f4e74eeae521
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 * The tile mode hasn't been set yet. 93 * @hide 94 */ 95 public static final int TILE_MODE_UNSET = 0; 96 97 /** 98 * Constant to be returned by {@link #onTileAdded}. 99 * <p> 100 * Passive mode is the default mode for tiles. The System will tell the tile 101 * when it is most important to update by putting it in the listening state. 102 */ 103 public static final int TILE_MODE_PASSIVE = 1; 104 105 /** 106 * Constant to be returned by {@link #onTileAdded}. 107 * <p> 108 * Active mode is for tiles which already listen and keep track of their state in their 109 * own process. These tiles may request to send an update to the System while their process 110 * is alive using {@link #requestListeningState}. The System will only bind these tiles 111 * on its own when a click needs to occur. 112 */ 113 public static final int TILE_MODE_ACTIVE = 2; 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_COMPONENT = "android.service.quicksettings.extra.COMPONENT"; 126 127 private final H mHandler = new H(Looper.getMainLooper()); 128 129 private boolean mListening = false; 130 private Tile mTile; 131 private IBinder mToken; 132 private IQSService mService; 133 private Runnable mUnlockRunnable; 134 135 @Override 136 public void onDestroy() { 137 if (mListening) { 138 onStopListening(); 139 mListening = false; 140 } 141 super.onDestroy(); 142 } 143 144 /** 145 * Called when the user adds this tile to Quick Settings. 146 * <p/> 147 * Note that this is not guaranteed to be called between {@link #onCreate()} 148 * and {@link #onStartListening()}, it will only be called when the tile is added 149 * and not on subsequent binds. 150 * 151 * @see #TILE_MODE_PASSIVE 152 * @see #TILE_MODE_ACTIVE 153 */ 154 public int onTileAdded() { 155 return TILE_MODE_PASSIVE; 156 } 157 158 /** 159 * Called when the user removes this tile from Quick Settings. 160 */ 161 public void onTileRemoved() { 162 } 163 164 /** 165 * Called when this tile moves into a listening state. 166 * <p/> 167 * When this tile is in a listening state it is expected to keep the 168 * UI up to date. Any listeners or callbacks needed to keep this tile 169 * up to date should be registered here and unregistered in {@link #onStopListening()}. 170 * 171 * @see #getQsTile() 172 * @see Tile#updateTile() 173 */ 174 public void onStartListening() { 175 } 176 177 /** 178 * Called when this tile moves out of the listening state. 179 */ 180 public void onStopListening() { 181 } 182 183 /** 184 * Called when the user clicks on this tile. 185 */ 186 public void onClick() { 187 } 188 189 /** 190 * Sets an icon to be shown in the status bar. 191 * <p> 192 * The icon will be displayed before all other icons. Can only be called between 193 * {@link #onStartListening} and {@link #onStopListening}. Can only be called by system apps. 194 * 195 * @param icon The icon to be displayed, null to hide 196 * @param contentDescription Content description of the icon to be displayed 197 * @hide 198 */ 199 @SystemApi 200 public final void setStatusIcon(Icon icon, String contentDescription) { 201 if (mService != null) { 202 try { 203 mService.updateStatusIcon(mTile, icon, contentDescription); 204 } catch (RemoteException e) { 205 } 206 } 207 } 208 209 /** 210 * Used to show a dialog. 211 * 212 * This will collapse the Quick Settings panel and show the dialog. 213 * 214 * @param dialog Dialog to show. 215 * 216 * @see #isLocked() 217 */ 218 public final void showDialog(Dialog dialog) { 219 dialog.getWindow().getAttributes().token = mToken; 220 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_QS_DIALOG); 221 dialog.getWindow().getDecorView().addOnAttachStateChangeListener( 222 new OnAttachStateChangeListener() { 223 @Override 224 public void onViewAttachedToWindow(View v) { 225 } 226 227 @Override 228 public void onViewDetachedFromWindow(View v) { 229 try { 230 mService.onDialogHidden(getQsTile()); 231 } catch (RemoteException e) { 232 } 233 } 234 }); 235 dialog.show(); 236 try { 237 mService.onShowDialog(mTile); 238 } catch (RemoteException e) { 239 } 240 } 241 242 /** 243 * Prompts the user to unlock the device before executing the Runnable. 244 * <p> 245 * The user will be prompted for their current security method if applicable 246 * and if successful, runnable will be executed. The Runnable will not be 247 * executed if the user fails to unlock the device or cancels the operation. 248 */ 249 public final void unlockAndRun(Runnable runnable) { 250 mUnlockRunnable = runnable; 251 try { 252 mService.startUnlockAndRun(mTile); 253 } catch (RemoteException e) { 254 } 255 } 256 257 /** 258 * Checks if the device is in a secure state. 259 * 260 * TileServices should detect when the device is secure and change their behavior 261 * accordingly. 262 * 263 * @return true if the device is secure. 264 */ 265 public final boolean isSecure() { 266 try { 267 return mService.isSecure(); 268 } catch (RemoteException e) { 269 return true; 270 } 271 } 272 273 /** 274 * Checks if the lock screen is showing. 275 * 276 * When a device is locked, then {@link #showDialog} will not present a dialog, as it will 277 * be under the lock screen. If the behavior of the Tile is safe to do while locked, 278 * then the user should use {@link #startActivity} to launch an activity on top of the lock 279 * screen, otherwise the tile should use {@link #unlockAndRun(Runnable)} to give the 280 * user their security challenge. 281 * 282 * @return true if the device is locked. 283 */ 284 public final boolean isLocked() { 285 try { 286 return mService.isLocked(); 287 } catch (RemoteException e) { 288 return true; 289 } 290 } 291 292 /** 293 * Start an activity while collapsing the panel. 294 */ 295 public final void startActivityAndCollapse(Intent intent) { 296 startActivity(intent); 297 try { 298 mService.onStartActivity(mTile); 299 } catch (RemoteException e) { 300 } 301 } 302 303 /** 304 * Gets the {@link Tile} for this service. 305 * <p/> 306 * This tile may be used to get or set the current state for this 307 * tile. This tile is only valid for updates between {@link #onStartListening()} 308 * and {@link #onStopListening()}. 309 */ 310 public final Tile getQsTile() { 311 return mTile; 312 } 313 314 @Override 315 public IBinder onBind(Intent intent) { 316 return new IQSTileService.Stub() { 317 @Override 318 public void setQSService(IQSService service) throws RemoteException { 319 mHandler.obtainMessage(H.MSG_SET_SERVICE, service).sendToTarget(); 320 } 321 322 @Override 323 public void setQSTile(Tile tile) throws RemoteException { 324 mHandler.obtainMessage(H.MSG_SET_TILE, tile).sendToTarget(); 325 } 326 327 @Override 328 public void onTileRemoved() throws RemoteException { 329 mHandler.sendEmptyMessage(H.MSG_TILE_REMOVED); 330 } 331 332 @Override 333 public void onTileAdded() throws RemoteException { 334 mHandler.sendEmptyMessage(H.MSG_TILE_ADDED); 335 } 336 337 @Override 338 public void onStopListening() throws RemoteException { 339 mHandler.sendEmptyMessage(H.MSG_STOP_LISTENING); 340 } 341 342 @Override 343 public void onStartListening() throws RemoteException { 344 mHandler.sendEmptyMessage(H.MSG_START_LISTENING); 345 } 346 347 @Override 348 public void onClick(IBinder wtoken) throws RemoteException { 349 mHandler.obtainMessage(H.MSG_TILE_CLICKED, wtoken).sendToTarget(); 350 } 351 352 @Override 353 public void onUnlockComplete() throws RemoteException{ 354 mHandler.sendEmptyMessage(H.MSG_UNLOCK_COMPLETE); 355 } 356 }; 357 } 358 359 private class H extends Handler { 360 private static final int MSG_SET_TILE = 1; 361 private static final int MSG_START_LISTENING = 2; 362 private static final int MSG_STOP_LISTENING = 3; 363 private static final int MSG_TILE_ADDED = 4; 364 private static final int MSG_TILE_REMOVED = 5; 365 private static final int MSG_TILE_CLICKED = 6; 366 private static final int MSG_SET_SERVICE = 7; 367 private static final int MSG_UNLOCK_COMPLETE = 8; 368 369 public H(Looper looper) { 370 super(looper); 371 } 372 373 @Override 374 public void handleMessage(Message msg) { 375 switch (msg.what) { 376 case MSG_SET_SERVICE: 377 mService = (IQSService) msg.obj; 378 if (mTile != null) { 379 mTile.setService(mService); 380 } 381 break; 382 case MSG_SET_TILE: 383 mTile = (Tile) msg.obj; 384 if (mService != null && mTile != null) { 385 mTile.setService(mService); 386 } 387 break; 388 case MSG_TILE_ADDED: 389 int mode = TileService.this.onTileAdded(); 390 if (mService == null) { 391 return; 392 } 393 try { 394 mService.setTileMode(new ComponentName(TileService.this, 395 TileService.this.getClass()), mode); 396 } catch (RemoteException e) { 397 } 398 break; 399 case MSG_TILE_REMOVED: 400 if (mListening) { 401 mListening = false; 402 TileService.this.onStopListening(); 403 } 404 TileService.this.onTileRemoved(); 405 break; 406 case MSG_STOP_LISTENING: 407 if (mListening) { 408 mListening = false; 409 TileService.this.onStopListening(); 410 } 411 break; 412 case MSG_START_LISTENING: 413 if (!mListening) { 414 mListening = true; 415 TileService.this.onStartListening(); 416 } 417 break; 418 case MSG_TILE_CLICKED: 419 mToken = (IBinder) msg.obj; 420 TileService.this.onClick(); 421 break; 422 case MSG_UNLOCK_COMPLETE: 423 if (mUnlockRunnable != null) { 424 mUnlockRunnable.run(); 425 } 426 break; 427 } 428 } 429 } 430 431 /** 432 * Requests that a tile be put in the listening state so it can send an update. 433 * 434 * This method is only applicable to tiles that return {@link #TILE_MODE_ACTIVE} from 435 * {@link #onTileAdded()}, and will do nothing otherwise. 436 */ 437 public static final void requestListeningState(Context context, ComponentName component) { 438 Intent intent = new Intent(ACTION_REQUEST_LISTENING); 439 intent.putExtra(EXTRA_COMPONENT, component); 440 context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE); 441 } 442} 443