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 * Used to notify SysUI that Listening has be requested. 130 * @hide 131 */ 132 public static final String ACTION_REQUEST_LISTENING 133 = "android.service.quicksettings.action.REQUEST_LISTENING"; 134 135 /** 136 * @hide 137 */ 138 public static final String EXTRA_SERVICE = "service"; 139 140 /** 141 * @hide 142 */ 143 public static final String EXTRA_TOKEN = "token"; 144 145 /** 146 * @hide 147 */ 148 public static final String EXTRA_STATE = "state"; 149 150 private final H mHandler = new H(Looper.getMainLooper()); 151 152 private boolean mListening = false; 153 private Tile mTile; 154 private IBinder mToken; 155 private IQSService mService; 156 private Runnable mUnlockRunnable; 157 private IBinder mTileToken; 158 159 @Override onDestroy()160 public void onDestroy() { 161 if (mListening) { 162 onStopListening(); 163 mListening = false; 164 } 165 super.onDestroy(); 166 } 167 168 /** 169 * Called when the user adds this tile to Quick Settings. 170 * <p/> 171 * Note that this is not guaranteed to be called between {@link #onCreate()} 172 * and {@link #onStartListening()}, it will only be called when the tile is added 173 * and not on subsequent binds. 174 */ onTileAdded()175 public void onTileAdded() { 176 } 177 178 /** 179 * Called when the user removes this tile from Quick Settings. 180 */ onTileRemoved()181 public void onTileRemoved() { 182 } 183 184 /** 185 * Called when this tile moves into a listening state. 186 * <p/> 187 * When this tile is in a listening state it is expected to keep the 188 * UI up to date. Any listeners or callbacks needed to keep this tile 189 * up to date should be registered here and unregistered in {@link #onStopListening()}. 190 * 191 * @see #getQsTile() 192 * @see Tile#updateTile() 193 */ onStartListening()194 public void onStartListening() { 195 } 196 197 /** 198 * Called when this tile moves out of the listening state. 199 */ onStopListening()200 public void onStopListening() { 201 } 202 203 /** 204 * Called when the user clicks on this tile. 205 */ onClick()206 public void onClick() { 207 } 208 209 /** 210 * Sets an icon to be shown in the status bar. 211 * <p> 212 * The icon will be displayed before all other icons. Can only be called between 213 * {@link #onStartListening} and {@link #onStopListening}. Can only be called by system apps. 214 * 215 * @param icon The icon to be displayed, null to hide 216 * @param contentDescription Content description of the icon to be displayed 217 * @hide 218 */ 219 @SystemApi setStatusIcon(Icon icon, String contentDescription)220 public final void setStatusIcon(Icon icon, String contentDescription) { 221 if (mService != null) { 222 try { 223 mService.updateStatusIcon(mTileToken, icon, contentDescription); 224 } catch (RemoteException e) { 225 } 226 } 227 } 228 229 /** 230 * Used to show a dialog. 231 * 232 * This will collapse the Quick Settings panel and show the dialog. 233 * 234 * @param dialog Dialog to show. 235 * 236 * @see #isLocked() 237 */ showDialog(Dialog dialog)238 public final void showDialog(Dialog dialog) { 239 dialog.getWindow().getAttributes().token = mToken; 240 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_QS_DIALOG); 241 dialog.getWindow().getDecorView().addOnAttachStateChangeListener( 242 new OnAttachStateChangeListener() { 243 @Override 244 public void onViewAttachedToWindow(View v) { 245 } 246 247 @Override 248 public void onViewDetachedFromWindow(View v) { 249 try { 250 mService.onDialogHidden(mTileToken); 251 } catch (RemoteException e) { 252 } 253 } 254 }); 255 dialog.show(); 256 try { 257 mService.onShowDialog(mTileToken); 258 } catch (RemoteException e) { 259 } 260 } 261 262 /** 263 * Prompts the user to unlock the device before executing the Runnable. 264 * <p> 265 * The user will be prompted for their current security method if applicable 266 * and if successful, runnable will be executed. The Runnable will not be 267 * executed if the user fails to unlock the device or cancels the operation. 268 */ unlockAndRun(Runnable runnable)269 public final void unlockAndRun(Runnable runnable) { 270 mUnlockRunnable = runnable; 271 try { 272 mService.startUnlockAndRun(mTileToken); 273 } catch (RemoteException e) { 274 } 275 } 276 277 /** 278 * Checks if the device is in a secure state. 279 * 280 * TileServices should detect when the device is secure and change their behavior 281 * accordingly. 282 * 283 * @return true if the device is secure. 284 */ isSecure()285 public final boolean isSecure() { 286 try { 287 return mService.isSecure(); 288 } catch (RemoteException e) { 289 return true; 290 } 291 } 292 293 /** 294 * Checks if the lock screen is showing. 295 * 296 * When a device is locked, then {@link #showDialog} will not present a dialog, as it will 297 * be under the lock screen. If the behavior of the Tile is safe to do while locked, 298 * then the user should use {@link #startActivity} to launch an activity on top of the lock 299 * screen, otherwise the tile should use {@link #unlockAndRun(Runnable)} to give the 300 * user their security challenge. 301 * 302 * @return true if the device is locked. 303 */ isLocked()304 public final boolean isLocked() { 305 try { 306 return mService.isLocked(); 307 } catch (RemoteException e) { 308 return true; 309 } 310 } 311 312 /** 313 * Start an activity while collapsing the panel. 314 */ startActivityAndCollapse(Intent intent)315 public final void startActivityAndCollapse(Intent intent) { 316 startActivity(intent); 317 try { 318 mService.onStartActivity(mTileToken); 319 } catch (RemoteException e) { 320 } 321 } 322 323 /** 324 * Gets the {@link Tile} for this service. 325 * <p/> 326 * This tile may be used to get or set the current state for this 327 * tile. This tile is only valid for updates between {@link #onStartListening()} 328 * and {@link #onStopListening()}. 329 */ getQsTile()330 public final Tile getQsTile() { 331 return mTile; 332 } 333 334 @Override onBind(Intent intent)335 public IBinder onBind(Intent intent) { 336 mService = IQSService.Stub.asInterface(intent.getIBinderExtra(EXTRA_SERVICE)); 337 mTileToken = intent.getIBinderExtra(EXTRA_TOKEN); 338 try { 339 mTile = mService.getTile(mTileToken); 340 } catch (RemoteException e) { 341 throw new RuntimeException("Unable to reach IQSService", e); 342 } 343 if (mTile != null) { 344 mTile.setService(mService, mTileToken); 345 mHandler.sendEmptyMessage(H.MSG_START_SUCCESS); 346 } 347 return new IQSTileService.Stub() { 348 @Override 349 public void onTileRemoved() throws RemoteException { 350 mHandler.sendEmptyMessage(H.MSG_TILE_REMOVED); 351 } 352 353 @Override 354 public void onTileAdded() throws RemoteException { 355 mHandler.sendEmptyMessage(H.MSG_TILE_ADDED); 356 } 357 358 @Override 359 public void onStopListening() throws RemoteException { 360 mHandler.sendEmptyMessage(H.MSG_STOP_LISTENING); 361 } 362 363 @Override 364 public void onStartListening() throws RemoteException { 365 mHandler.sendEmptyMessage(H.MSG_START_LISTENING); 366 } 367 368 @Override 369 public void onClick(IBinder wtoken) throws RemoteException { 370 mHandler.obtainMessage(H.MSG_TILE_CLICKED, wtoken).sendToTarget(); 371 } 372 373 @Override 374 public void onUnlockComplete() throws RemoteException{ 375 mHandler.sendEmptyMessage(H.MSG_UNLOCK_COMPLETE); 376 } 377 }; 378 } 379 380 private class H extends Handler { 381 private static final int MSG_START_LISTENING = 1; 382 private static final int MSG_STOP_LISTENING = 2; 383 private static final int MSG_TILE_ADDED = 3; 384 private static final int MSG_TILE_REMOVED = 4; 385 private static final int MSG_TILE_CLICKED = 5; 386 private static final int MSG_UNLOCK_COMPLETE = 6; 387 private static final int MSG_START_SUCCESS = 7; 388 private final String mTileServiceName; 389 390 public H(Looper looper) { 391 super(looper); 392 mTileServiceName = TileService.this.getClass().getSimpleName(); 393 } 394 395 private void logMessage(String message) { 396 Log.d(TAG, mTileServiceName + " Handler - " + message); 397 } 398 399 @Override 400 public void handleMessage(Message msg) { 401 switch (msg.what) { 402 case MSG_TILE_ADDED: 403 if (DEBUG) logMessage("MSG_TILE_ADDED"); 404 TileService.this.onTileAdded(); 405 break; 406 case MSG_TILE_REMOVED: 407 if (DEBUG) logMessage("MSG_TILE_REMOVED"); 408 if (mListening) { 409 mListening = false; 410 TileService.this.onStopListening(); 411 } 412 TileService.this.onTileRemoved(); 413 break; 414 case MSG_STOP_LISTENING: 415 if (DEBUG) logMessage("MSG_STOP_LISTENING"); 416 if (mListening) { 417 mListening = false; 418 TileService.this.onStopListening(); 419 } 420 break; 421 case MSG_START_LISTENING: 422 if (DEBUG) logMessage("MSG_START_LISTENING"); 423 if (!mListening) { 424 mListening = true; 425 TileService.this.onStartListening(); 426 } 427 break; 428 case MSG_TILE_CLICKED: 429 if (DEBUG) logMessage("MSG_TILE_CLICKED"); 430 mToken = (IBinder) msg.obj; 431 TileService.this.onClick(); 432 break; 433 case MSG_UNLOCK_COMPLETE: 434 if (DEBUG) logMessage("MSG_UNLOCK_COMPLETE"); 435 if (mUnlockRunnable != null) { 436 mUnlockRunnable.run(); 437 } 438 break; 439 case MSG_START_SUCCESS: 440 if (DEBUG) logMessage("MSG_START_SUCCESS"); 441 try { 442 mService.onStartSuccessful(mTileToken); 443 } catch (RemoteException e) { 444 } 445 break; 446 } 447 } 448 } 449 450 /** 451 * @return True if the device supports quick settings and its assocated APIs. 452 * @hide 453 */ 454 @TestApi 455 public static boolean isQuickSettingsSupported() { 456 return Resources.getSystem().getBoolean(R.bool.config_quickSettingsSupported); 457 } 458 459 /** 460 * Requests that a tile be put in the listening state so it can send an update. 461 * 462 * This method is only applicable to tiles that have {@link #META_DATA_ACTIVE_TILE} defined 463 * as true on their TileService Manifest declaration, and will do nothing otherwise. 464 */ 465 public static final void requestListeningState(Context context, ComponentName component) { 466 Intent intent = new Intent(ACTION_REQUEST_LISTENING); 467 intent.putExtra(Intent.EXTRA_COMPONENT_NAME, component); 468 intent.setPackage("com.android.systemui"); 469 context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE); 470 } 471 } 472