• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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