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