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 
17 package androidx.legacy.content;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.PowerManager;
24 import android.util.Log;
25 import 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
73 public 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      */
startWakefulService(Context context, Intent intent)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      */
completeWakefulIntent(Intent intent)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