blob: 9b50ef5e1a387f50b2b5c6f9565551619db02b8b [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.quicksettings;
import android.Manifest;
import android.app.Dialog;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.view.WindowManager;
/**
* A TileService provides the user a tile that can be added to Quick Settings.
* Quick Settings is a space provided that allows the user to change settings and
* take quick actions without leaving the context of their current app.
*
* <p>The lifecycle of a TileService is different from some other services in
* that it may be unbound during parts of its lifecycle. Any of the following
* lifecycle events can happen indepently in a separate binding/creation of the
* service.</p>
*
* <ul>
* <li>When a tile is added by the user its TileService will be bound to and
* {@link #onTileAdded()} will be called.</li>
*
* <li>When a tile should be up to date and listing will be indicated by
* {@link #onStartListening()} and {@link #onStopListening()}.</li>
*
* <li>When the user removes a tile from Quick Settings {@link #onTileRemoved()}
* will be called.</li>
* </ul>
* <p>TileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
* and require the permission "android.permission.BIND_QUICK_SETTINGS_TILE".
* The label and icon for the service will be used as the default label and
* icon for the tile. Here is an example TileService declaration.</p>
* <pre class="prettyprint">
* {@literal
* <service
* android:name=".MyQSTileService"
* android:label="@string/my_default_tile_label"
* android:icon="@drawable/my_default_icon_label"
* android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
* <intent-filter>
* <action android:name="android.service.quicksettings.action.QS_TILE" />
* </intent-filter>
* </service>}
* </pre>
*
* @see Tile Tile for details about the UI of a Quick Settings Tile.
*/
public class TileService extends Service {
/**
* Action that identifies a Service as being a TileService.
*/
public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
/**
* The tile mode hasn't been set yet.
* @hide
*/
public static final int TILE_MODE_UNSET = 0;
/**
* Constant to be returned by {@link #onTileAdded}.
* <p>
* Passive mode is the default mode for tiles. The System will tell the tile
* when it is most important to update by putting it in the listening state.
*/
public static final int TILE_MODE_PASSIVE = 1;
/**
* Constant to be returned by {@link #onTileAdded}.
* <p>
* Active mode is for tiles which already listen and keep track of their state in their
* own process. These tiles may request to send an update to the System while their process
* is alive using {@link #requestListeningState}. The System will only bind these tiles
* on its own when a click needs to occur.
*/
public static final int TILE_MODE_ACTIVE = 2;
/**
* Used to notify SysUI that Listening has be requested.
* @hide
*/
public static final String ACTION_REQUEST_LISTENING
= "android.service.quicksettings.action.REQUEST_LISTENING";
/**
* @hide
*/
public static final String EXTRA_COMPONENT = "android.service.quicksettings.extra.COMPONENT";
private final H mHandler = new H(Looper.getMainLooper());
private boolean mListening = false;
private Tile mTile;
private IBinder mToken;
private IQSService mService;
@Override
public void onDestroy() {
if (mListening) {
onStopListening();
mListening = false;
}
super.onDestroy();
}
/**
* Called when the user adds this tile to Quick Settings.
* <p/>
* Note that this is not guaranteed to be called between {@link #onCreate()}
* and {@link #onStartListening()}, it will only be called when the tile is added
* and not on subsequent binds.
*
* @see #TILE_MODE_PASSIVE
* @see #TILE_MODE_ACTIVE
*/
public int onTileAdded() {
return TILE_MODE_PASSIVE;
}
/**
* Called when the user removes this tile from Quick Settings.
*/
public void onTileRemoved() {
}
/**
* Called when this tile moves into a listening state.
* <p/>
* When this tile is in a listening state it is expected to keep the
* UI up to date. Any listeners or callbacks needed to keep this tile
* up to date should be registered here and unregistered in {@link #onStopListening()}.
*
* @see #getQsTile()
* @see Tile#updateTile()
*/
public void onStartListening() {
}
/**
* Called when this tile moves out of the listening state.
*/
public void onStopListening() {
}
/**
* Called when the user clicks on this tile.
*/
public void onClick() {
}
/**
* Used to show a dialog.
*
* This will collapse the Quick Settings panel and show the dialog.
*
* @param dialog Dialog to show.
*/
public final void showDialog(Dialog dialog) {
dialog.getWindow().getAttributes().token = mToken;
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_QS_DIALOG);
dialog.show();
try {
mService.onShowDialog(mTile);
} catch (RemoteException e) {
}
}
/**
* Gets the {@link Tile} for this service.
* <p/>
* This tile may be used to get or set the current state for this
* tile. This tile is only valid for updates between {@link #onStartListening()}
* and {@link #onStopListening()}.
*/
public final Tile getQsTile() {
return mTile;
}
@Override
public IBinder onBind(Intent intent) {
return new IQSTileService.Stub() {
@Override
public void setQSService(IQSService service) throws RemoteException {
mHandler.obtainMessage(H.MSG_SET_SERVICE, service).sendToTarget();
}
@Override
public void setQSTile(Tile tile) throws RemoteException {
mHandler.obtainMessage(H.MSG_SET_TILE, tile).sendToTarget();
}
@Override
public void onTileRemoved() throws RemoteException {
mHandler.sendEmptyMessage(H.MSG_TILE_REMOVED);
}
@Override
public void onTileAdded() throws RemoteException {
mHandler.sendEmptyMessage(H.MSG_TILE_ADDED);
}
@Override
public void onStopListening() throws RemoteException {
mHandler.sendEmptyMessage(H.MSG_STOP_LISTENING);
}
@Override
public void onStartListening() throws RemoteException {
mHandler.sendEmptyMessage(H.MSG_START_LISTENING);
}
@Override
public void onClick(IBinder wtoken) throws RemoteException {
mHandler.obtainMessage(H.MSG_TILE_CLICKED, wtoken).sendToTarget();
}
};
}
private class H extends Handler {
private static final int MSG_SET_TILE = 1;
private static final int MSG_START_LISTENING = 2;
private static final int MSG_STOP_LISTENING = 3;
private static final int MSG_TILE_ADDED = 4;
private static final int MSG_TILE_REMOVED = 5;
private static final int MSG_TILE_CLICKED = 6;
private static final int MSG_SET_SERVICE = 7;
public H(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_SERVICE:
mService = (IQSService) msg.obj;
if (mTile != null) {
mTile.setService(mService);
}
break;
case MSG_SET_TILE:
mTile = (Tile) msg.obj;
if (mService != null && mTile != null) {
mTile.setService(mService);
}
break;
case MSG_TILE_ADDED:
int mode = TileService.this.onTileAdded();
if (mService == null) {
return;
}
try {
mService.setTileMode(new ComponentName(TileService.this,
TileService.this.getClass()), mode);
} catch (RemoteException e) {
}
break;
case MSG_TILE_REMOVED:
if (mListening) {
mListening = false;
TileService.this.onStopListening();
}
TileService.this.onTileRemoved();
break;
case MSG_STOP_LISTENING:
if (mListening) {
mListening = false;
TileService.this.onStopListening();
}
break;
case MSG_START_LISTENING:
if (!mListening) {
mListening = true;
TileService.this.onStartListening();
}
break;
case MSG_TILE_CLICKED:
mToken = (IBinder) msg.obj;
TileService.this.onClick();
break;
}
}
}
/**
* Requests that a tile be put in the listening state so it can send an update.
*
* This method is only applicable to tiles that return {@link #TILE_MODE_ACTIVE} from
* {@link #onTileAdded()}, and will do nothing otherwise.
*/
public static final void requestListeningState(Context context, ComponentName component) {
Intent intent = new Intent(ACTION_REQUEST_LISTENING);
intent.putExtra(EXTRA_COMPONENT, component);
context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
}
}