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