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