1/*
2 * Copyright (C) 2013 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
17package android.support.v4.content;
18
19import android.content.BroadcastReceiver;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.os.PowerManager;
24import android.util.Log;
25import android.util.SparseArray;
26
27/**
28 * Helper for the common pattern of implementing a {@link BroadcastReceiver}
29 * that receives a device wakeup event and then passes the work off
30 * to a {@link android.app.Service}, while ensuring that the
31 * device does not go back to sleep during the transition.
32 *
33 * <p>This class takes care of creating and managing a partial wake lock
34 * for you; you must request the {@link android.Manifest.permission#WAKE_LOCK}
35 * permission to use it.</p>
36 *
37 * <h3>Example</h3>
38 *
39 * <p>A {@link WakefulBroadcastReceiver} uses the method
40 * {@link WakefulBroadcastReceiver#startWakefulService startWakefulService()}
41 * to start the service that does the work. This method is comparable to
42 * {@link android.content.Context#startService startService()}, except that
43 * the {@link WakefulBroadcastReceiver} is holding a wake lock when the service
44 * starts. The intent that is passed with
45 * {@link WakefulBroadcastReceiver#startWakefulService startWakefulService()}
46 * holds an extra identifying the wake lock.</p>
47 *
48 * {@sample development/samples/Support4Demos/src/com/example/android/supportv4/content/SimpleWakefulReceiver.java complete}
49 *
50 * <p>The service (in this example, an {@link android.app.IntentService}) does
51 * some work. When it is finished, it releases the wake lock by calling
52 * {@link WakefulBroadcastReceiver#completeWakefulIntent
53 * completeWakefulIntent(intent)}. The intent it passes as a parameter
54 * is the same intent that the {@link WakefulBroadcastReceiver} originally
55 * passed in.</p>
56 *
57 * {@sample development/samples/Support4Demos/src/com/example/android/supportv4/content/SimpleWakefulService.java complete}
58 */
59public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
60    private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid";
61
62    private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks
63            = new SparseArray<PowerManager.WakeLock>();
64    private static int mNextId = 1;
65
66    /**
67     * Do a {@link android.content.Context#startService(android.content.Intent)
68     * Context.startService}, but holding a wake lock while the service starts.
69     * This will modify the Intent to hold an extra identifying the wake lock;
70     * when the service receives it in {@link android.app.Service#onStartCommand
71     * Service.onStartCommand}, it should pass back the Intent it receives there to
72     * {@link #completeWakefulIntent(android.content.Intent)} in order to release
73     * the wake lock.
74     *
75     * @param context The Context in which it operate.
76     * @param intent The Intent with which to start the service, as per
77     * {@link android.content.Context#startService(android.content.Intent)
78     * Context.startService}.
79     */
80    public static ComponentName startWakefulService(Context context, Intent intent) {
81        synchronized (mActiveWakeLocks) {
82            int id = mNextId;
83            mNextId++;
84            if (mNextId <= 0) {
85                mNextId = 1;
86            }
87
88            intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
89            ComponentName comp = context.startService(intent);
90            if (comp == null) {
91                return null;
92            }
93
94            PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
95            PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
96                    "wake:" + comp.flattenToShortString());
97            wl.setReferenceCounted(false);
98            wl.acquire(60*1000);
99            mActiveWakeLocks.put(id, wl);
100            return comp;
101        }
102    }
103
104    /**
105     * Finish the execution from a previous {@link #startWakefulService}.  Any wake lock
106     * that was being held will now be released.
107     *
108     * @param intent The Intent as originally generated by {@link #startWakefulService}.
109     * @return Returns true if the intent is associated with a wake lock that is
110     * now released; returns false if there was no wake lock specified for it.
111     */
112    public static boolean completeWakefulIntent(Intent intent) {
113        final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
114        if (id == 0) {
115            return false;
116        }
117        synchronized (mActiveWakeLocks) {
118            PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
119            if (wl != null) {
120                wl.release();
121                mActiveWakeLocks.remove(id);
122                return true;
123            }
124            // We return true whether or not we actually found the wake lock
125            // the return code is defined to indicate whether the Intent contained
126            // an identifier for a wake lock that it was supposed to match.
127            // We just log a warning here if there is no wake lock found, which could
128            // happen for example if this function is called twice on the same
129            // intent or the process is killed and restarted before processing the intent.
130            Log.w("WakefulBroadcastReceiver", "No active wake lock id #" + id);
131            return true;
132        }
133    }
134}
135