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