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