File: TileService.java

package info (click to toggle)
android-platform-frameworks-base 1%3A10.0.0%2Br36-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 321,788 kB
  • sloc: java: 962,234; cpp: 274,314; xml: 242,770; python: 5,060; sh: 1,432; ansic: 494; makefile: 47; sed: 19
file content (471 lines) | stat: -rw-r--r-- 16,370 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
/*
 * 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";

    /**
     * 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) {
        Intent intent = new Intent(ACTION_REQUEST_LISTENING);
        intent.putExtra(Intent.EXTRA_COMPONENT_NAME, component);
        intent.setPackage("com.android.systemui");
        context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
    }
}