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.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
    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      */
    167     public void onTileAdded() {
    168     }
    169 
    170     /**
    171      * Called when the user removes this tile from Quick Settings.
    172      */
    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      */
    186     public void onStartListening() {
    187     }
    188 
    189     /**
    190      * Called when this tile moves out of the listening state.
    191      */
    192     public void onStopListening() {
    193     }
    194 
    195     /**
    196      * Called when the user clicks on this tile.
    197      */
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    322     public final Tile getQsTile() {
    323         return mTile;
    324     }
    325 
    326     @Override
    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