1/*
2 * Copyright 2018 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 androidx.legacy.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 * This helper is for an old 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 * <p>Wakelocks held by this class are reported to tools as
38 * {@code "androidx.core:wake:<component-name>"}.</p>
39 *
40 * <h3>Example</h3>
41 *
42 * <p>A {@link WakefulBroadcastReceiver} uses the method
43 * {@link WakefulBroadcastReceiver#startWakefulService startWakefulService()}
44 * to start the service that does the work. This method is comparable to
45 * {@link android.content.Context#startService startService()}, except that
46 * the {@link WakefulBroadcastReceiver} is holding a wake lock when the service
47 * starts. The intent that is passed with
48 * {@link WakefulBroadcastReceiver#startWakefulService startWakefulService()}
49 * holds an extra identifying the wake lock.</p>
50 *
51 * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/content/SimpleWakefulReceiver.java
52 *      complete}
53 *
54 * <p>The service (in this example, an {@link android.app.IntentService}) does
55 * some work. When it is finished, it releases the wake lock by calling
56 * {@link WakefulBroadcastReceiver#completeWakefulIntent
57 * completeWakefulIntent(intent)}. The intent it passes as a parameter
58 * is the same intent that the {@link WakefulBroadcastReceiver} originally
59 * passed in.</p>
60 *
61 * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/content/SimpleWakefulService.java
62 *      complete}
63 *
64 * @deprecated As of {@link android.os.Build.VERSION_CODES#O Android O}, background check
65 * restrictions make this class no longer generally useful.  (It is generally not safe to
66 * start a service from the receipt of a broadcast, because you don't have any guarantees
67 * that your app is in the foreground at this point and thus allowed to do so.)  Instead,
68 * developers should use android.app.job.JobScheduler to schedule a job, and this
69 * does not require that the app hold a wake lock while doing so (the system will take
70 * care of holding a wake lock for the job).
71 */
72@Deprecated
73public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
74    private static final String EXTRA_WAKE_LOCK_ID = "androidx.contentpager.content.wakelockid";
75
76    private static final SparseArray<PowerManager.WakeLock> sActiveWakeLocks = new SparseArray<>();
77    private static int mNextId = 1;
78
79    /**
80     * Do a {@link android.content.Context#startService(android.content.Intent)
81     * Context.startService}, but holding a wake lock while the service starts.
82     * This will modify the Intent to hold an extra identifying the wake lock;
83     * when the service receives it in {@link android.app.Service#onStartCommand
84     * Service.onStartCommand}, it should pass back the Intent it receives there to
85     * {@link #completeWakefulIntent(android.content.Intent)} in order to release
86     * the wake lock.
87     *
88     * @param context The Context in which it operate.
89     * @param intent The Intent with which to start the service, as per
90     * {@link android.content.Context#startService(android.content.Intent)
91     * Context.startService}.
92     */
93    public static ComponentName startWakefulService(Context context, Intent intent) {
94        synchronized (sActiveWakeLocks) {
95            int id = mNextId;
96            mNextId++;
97            if (mNextId <= 0) {
98                mNextId = 1;
99            }
100
101            intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
102            ComponentName comp = context.startService(intent);
103            if (comp == null) {
104                return null;
105            }
106
107            PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
108            PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
109                    "androidx.core:wake:" + comp.flattenToShortString());
110            wl.setReferenceCounted(false);
111            wl.acquire(60 * 1000);
112            sActiveWakeLocks.put(id, wl);
113            return comp;
114        }
115    }
116
117    /**
118     * Finish the execution from a previous {@link #startWakefulService}.  Any wake lock
119     * that was being held will now be released.
120     *
121     * @param intent The Intent as originally generated by {@link #startWakefulService}.
122     * @return Returns true if the intent is associated with a wake lock that is
123     * now released; returns false if there was no wake lock specified for it.
124     */
125    public static boolean completeWakefulIntent(Intent intent) {
126        final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
127        if (id == 0) {
128            return false;
129        }
130        synchronized (sActiveWakeLocks) {
131            PowerManager.WakeLock wl = sActiveWakeLocks.get(id);
132            if (wl != null) {
133                wl.release();
134                sActiveWakeLocks.remove(id);
135                return true;
136            }
137            // We return true whether or not we actually found the wake lock
138            // the return code is defined to indicate whether the Intent contained
139            // an identifier for a wake lock that it was supposed to match.
140            // We just log a warning here if there is no wake lock found, which could
141            // happen for example if this function is called twice on the same
142            // intent or the process is killed and restarted before processing the intent.
143            Log.w("WakefulBroadcastReceiv.", "No active wake lock id #" + id);
144            return true;
145        }
146    }
147}
148