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