| /* |
| * 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.annotation.SdkConstant; |
| import android.annotation.SdkConstant.SdkConstantType; |
| import android.annotation.SystemApi; |
| import android.annotation.TestApi; |
| import android.app.Dialog; |
| import android.app.Service; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.graphics.drawable.Icon; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.View.OnAttachStateChangeListener; |
| import android.view.WindowManager; |
| |
| import com.android.internal.R; |
| |
| /** |
| * 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 { |
| |
| private static final String TAG = "TileService"; |
| private static final boolean DEBUG = false; |
| |
| /** |
| * An activity that provides a user interface for adjusting TileService |
| * preferences. Optional but recommended for apps that implement a |
| * TileService. |
| * <p> |
| * This intent may also define a {@link Intent#EXTRA_COMPONENT_NAME} value |
| * to indicate the {@link ComponentName} that caused the preferences to be |
| * opened. |
| * <p> |
| * To ensure that the activity can only be launched through quick settings |
| * UI provided by this service, apps can protect it with the |
| * BIND_QUICK_SETTINGS_TILE permission. |
| */ |
| @SdkConstant(SdkConstantType.INTENT_CATEGORY) |
| public static final String ACTION_QS_TILE_PREFERENCES |
| = "android.service.quicksettings.action.QS_TILE_PREFERENCES"; |
| |
| /** |
| * Action that identifies a Service as being a TileService. |
| */ |
| public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE"; |
| |
| /** |
| * Meta-data for tile definition to set a tile into active mode. |
| * <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. |
| * |
| * To make a TileService an active tile, set this meta-data to true on the TileService's |
| * manifest declaration. |
| * <pre class="prettyprint"> |
| * {@literal |
| * <meta-data android:name="android.service.quicksettings.ACTIVE_TILE" |
| * android:value="true" /> |
| * } |
| * </pre> |
| */ |
| public static final String META_DATA_ACTIVE_TILE |
| = "android.service.quicksettings.ACTIVE_TILE"; |
| |
| /** |
| * Meta-data for a tile to mark is toggleable. |
| * <p> |
| * Toggleable tiles support switch tile behavior in accessibility. This is |
| * the behavior of most of the framework tiles. |
| * |
| * To indicate that a TileService is toggleable, set this meta-data to true on the |
| * TileService's manifest declaration. |
| * <pre class="prettyprint"> |
| * {@literal |
| * <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE" |
| * android:value="true" /> |
| * } |
| * </pre> |
| */ |
| public static final String META_DATA_TOGGLEABLE_TILE = |
| "android.service.quicksettings.TOGGLEABLE_TILE"; |
| |
| /** |
| * 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_SERVICE = "service"; |
| |
| /** |
| * @hide |
| */ |
| public static final String EXTRA_TOKEN = "token"; |
| |
| /** |
| * @hide |
| */ |
| public static final String EXTRA_STATE = "state"; |
| |
| private final H mHandler = new H(Looper.getMainLooper()); |
| |
| private boolean mListening = false; |
| private Tile mTile; |
| private IBinder mToken; |
| private IQSService mService; |
| private Runnable mUnlockRunnable; |
| private IBinder mTileToken; |
| |
| @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. |
| */ |
| public void onTileAdded() { |
| } |
| |
| /** |
| * 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() { |
| } |
| |
| /** |
| * Sets an icon to be shown in the status bar. |
| * <p> |
| * The icon will be displayed before all other icons. Can only be called between |
| * {@link #onStartListening} and {@link #onStopListening}. Can only be called by system apps. |
| * |
| * @param icon The icon to be displayed, null to hide |
| * @param contentDescription Content description of the icon to be displayed |
| * @hide |
| */ |
| @SystemApi |
| public final void setStatusIcon(Icon icon, String contentDescription) { |
| if (mService != null) { |
| try { |
| mService.updateStatusIcon(mTileToken, icon, contentDescription); |
| } catch (RemoteException e) { |
| } |
| } |
| } |
| |
| /** |
| * Used to show a dialog. |
| * |
| * This will collapse the Quick Settings panel and show the dialog. |
| * |
| * @param dialog Dialog to show. |
| * |
| * @see #isLocked() |
| */ |
| public final void showDialog(Dialog dialog) { |
| dialog.getWindow().getAttributes().token = mToken; |
| dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_QS_DIALOG); |
| dialog.getWindow().getDecorView().addOnAttachStateChangeListener( |
| new OnAttachStateChangeListener() { |
| @Override |
| public void onViewAttachedToWindow(View v) { |
| } |
| |
| @Override |
| public void onViewDetachedFromWindow(View v) { |
| try { |
| mService.onDialogHidden(mTileToken); |
| } catch (RemoteException e) { |
| } |
| } |
| }); |
| dialog.show(); |
| try { |
| mService.onShowDialog(mTileToken); |
| } catch (RemoteException e) { |
| } |
| } |
| |
| /** |
| * Prompts the user to unlock the device before executing the Runnable. |
| * <p> |
| * The user will be prompted for their current security method if applicable |
| * and if successful, runnable will be executed. The Runnable will not be |
| * executed if the user fails to unlock the device or cancels the operation. |
| */ |
| public final void unlockAndRun(Runnable runnable) { |
| mUnlockRunnable = runnable; |
| try { |
| mService.startUnlockAndRun(mTileToken); |
| } catch (RemoteException e) { |
| } |
| } |
| |
| /** |
| * Checks if the device is in a secure state. |
| * |
| * TileServices should detect when the device is secure and change their behavior |
| * accordingly. |
| * |
| * @return true if the device is secure. |
| */ |
| public final boolean isSecure() { |
| try { |
| return mService.isSecure(); |
| } catch (RemoteException e) { |
| return true; |
| } |
| } |
| |
| /** |
| * Checks if the lock screen is showing. |
| * |
| * When a device is locked, then {@link #showDialog} will not present a dialog, as it will |
| * be under the lock screen. If the behavior of the Tile is safe to do while locked, |
| * then the user should use {@link #startActivity} to launch an activity on top of the lock |
| * screen, otherwise the tile should use {@link #unlockAndRun(Runnable)} to give the |
| * user their security challenge. |
| * |
| * @return true if the device is locked. |
| */ |
| public final boolean isLocked() { |
| try { |
| return mService.isLocked(); |
| } catch (RemoteException e) { |
| return true; |
| } |
| } |
| |
| /** |
| * Start an activity while collapsing the panel. |
| */ |
| public final void startActivityAndCollapse(Intent intent) { |
| startActivity(intent); |
| try { |
| mService.onStartActivity(mTileToken); |
| } 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) { |
| mService = IQSService.Stub.asInterface(intent.getIBinderExtra(EXTRA_SERVICE)); |
| mTileToken = intent.getIBinderExtra(EXTRA_TOKEN); |
| try { |
| mTile = mService.getTile(mTileToken); |
| } catch (RemoteException e) { |
| throw new RuntimeException("Unable to reach IQSService", e); |
| } |
| if (mTile != null) { |
| mTile.setService(mService, mTileToken); |
| mHandler.sendEmptyMessage(H.MSG_START_SUCCESS); |
| } |
| return new IQSTileService.Stub() { |
| @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(); |
| } |
| |
| @Override |
| public void onUnlockComplete() throws RemoteException{ |
| mHandler.sendEmptyMessage(H.MSG_UNLOCK_COMPLETE); |
| } |
| }; |
| } |
| |
| private class H extends Handler { |
| private static final int MSG_START_LISTENING = 1; |
| private static final int MSG_STOP_LISTENING = 2; |
| private static final int MSG_TILE_ADDED = 3; |
| private static final int MSG_TILE_REMOVED = 4; |
| private static final int MSG_TILE_CLICKED = 5; |
| private static final int MSG_UNLOCK_COMPLETE = 6; |
| private static final int MSG_START_SUCCESS = 7; |
| private final String mTileServiceName; |
| |
| public H(Looper looper) { |
| super(looper); |
| mTileServiceName = TileService.this.getClass().getSimpleName(); |
| } |
| |
| private void logMessage(String message) { |
| Log.d(TAG, mTileServiceName + " Handler - " + message); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_TILE_ADDED: |
| if (DEBUG) logMessage("MSG_TILE_ADDED"); |
| TileService.this.onTileAdded(); |
| break; |
| case MSG_TILE_REMOVED: |
| if (DEBUG) logMessage("MSG_TILE_REMOVED"); |
| if (mListening) { |
| mListening = false; |
| TileService.this.onStopListening(); |
| } |
| TileService.this.onTileRemoved(); |
| break; |
| case MSG_STOP_LISTENING: |
| if (DEBUG) logMessage("MSG_STOP_LISTENING"); |
| if (mListening) { |
| mListening = false; |
| TileService.this.onStopListening(); |
| } |
| break; |
| case MSG_START_LISTENING: |
| if (DEBUG) logMessage("MSG_START_LISTENING"); |
| if (!mListening) { |
| mListening = true; |
| TileService.this.onStartListening(); |
| } |
| break; |
| case MSG_TILE_CLICKED: |
| if (DEBUG) logMessage("MSG_TILE_CLICKED"); |
| mToken = (IBinder) msg.obj; |
| TileService.this.onClick(); |
| break; |
| case MSG_UNLOCK_COMPLETE: |
| if (DEBUG) logMessage("MSG_UNLOCK_COMPLETE"); |
| if (mUnlockRunnable != null) { |
| mUnlockRunnable.run(); |
| } |
| break; |
| case MSG_START_SUCCESS: |
| if (DEBUG) logMessage("MSG_START_SUCCESS"); |
| try { |
| mService.onStartSuccessful(mTileToken); |
| } catch (RemoteException e) { |
| } |
| break; |
| } |
| } |
| } |
| |
| /** |
| * @return True if the device supports quick settings and its assocated APIs. |
| * @hide |
| */ |
| @TestApi |
| public static boolean isQuickSettingsSupported() { |
| return Resources.getSystem().getBoolean(R.bool.config_quickSettingsSupported); |
| } |
| |
| /** |
| * Requests that a tile be put in the listening state so it can send an update. |
| * |
| * This method is only applicable to tiles that have {@link #META_DATA_ACTIVE_TILE} defined |
| * as true on their TileService Manifest declaration, and will do nothing otherwise. |
| */ |
| public static final void requestListeningState(Context context, ComponentName component) { |
| final ComponentName sysuiComponent = ComponentName.unflattenFromString( |
| context.getResources().getString( |
| com.android.internal.R.string.config_systemUIServiceComponent)); |
| Intent intent = new Intent(ACTION_REQUEST_LISTENING); |
| intent.putExtra(Intent.EXTRA_COMPONENT_NAME, component); |
| intent.setPackage(sysuiComponent.getPackageName()); |
| context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE); |
| } |
| } |