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