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