1 /*
2  * Copyright (C) 2017 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 com.android.systemui.util.wakelock;
18 
19 import android.content.Context;
20 import android.os.PowerManager;
21 import android.util.Log;
22 
23 import androidx.annotation.VisibleForTesting;
24 
25 import java.util.HashMap;
26 
27 import javax.inject.Inject;
28 
29 /** WakeLock wrapper for testability */
30 public interface WakeLock {
31 
32     String TAG = "WakeLock";
33     String REASON_WRAP = "wrap";
34 
35     /**
36      * Default wake-lock timeout in milliseconds, to avoid battery regressions.
37      */
38     long DEFAULT_MAX_TIMEOUT = 20000;
39 
40     /**
41      * Default wake-lock levels and flags.
42      */
43     int DEFAULT_LEVELS_AND_FLAGS = PowerManager.PARTIAL_WAKE_LOCK;
44 
45     /**
46      * @param why A tag that will be saved for sysui dumps.
47      * @see android.os.PowerManager.WakeLock#acquire()
48      **/
acquire(String why)49     void acquire(String why);
50 
51     /**
52      * @param why Same tag used in {@link #acquire(String)}
53      * @see android.os.PowerManager.WakeLock#release()
54      **/
release(String why)55     void release(String why);
56 
57     /** @see android.os.PowerManager.WakeLock#wrap(Runnable) */
wrap(Runnable r)58     Runnable wrap(Runnable r);
59 
60     /**
61      * Creates a {@link WakeLock} that has a default release timeout and flags.
62      * @see android.os.PowerManager.WakeLock#acquire(long)
63      */
createPartial(Context context, WakeLockLogger logger, String tag)64     static WakeLock createPartial(Context context, WakeLockLogger logger, String tag) {
65         return createPartial(context, logger, tag, DEFAULT_MAX_TIMEOUT);
66     }
67 
68     /**
69      * Creates a {@link WakeLock} that has default flags.
70      * @see android.os.PowerManager.WakeLock#acquire(long)
71      */
createPartial( Context context, WakeLockLogger logger, String tag, long maxTimeout)72     static WakeLock createPartial(
73             Context context, WakeLockLogger logger, String tag, long maxTimeout) {
74         return wrap(
75                 createWakeLockInner(context, tag, DEFAULT_LEVELS_AND_FLAGS), logger, maxTimeout);
76     }
77 
78     /**
79      * Creates a {@link WakeLock}.
80      * @see android.os.PowerManager.WakeLock#acquire(long)
81      */
createWakeLock( Context context, WakeLockLogger logger, String tag, int flags, long maxTimeout)82     static WakeLock createWakeLock(
83             Context context, WakeLockLogger logger, String tag, int flags, long maxTimeout) {
84         return wrap(
85                 createWakeLockInner(context, tag, flags), logger, maxTimeout);
86     }
87 
88     @VisibleForTesting
createWakeLockInner( Context context, String tag, int levelsAndFlags)89     static PowerManager.WakeLock createWakeLockInner(
90             Context context, String tag, int levelsAndFlags) {
91         return context.getSystemService(PowerManager.class)
92                     .newWakeLock(levelsAndFlags, tag);
93     }
94 
wrapImpl(WakeLock w, Runnable r)95     static Runnable wrapImpl(WakeLock w, Runnable r) {
96         w.acquire(REASON_WRAP);
97         return () -> {
98             try {
99                 r.run();
100             } finally {
101                 w.release(REASON_WRAP);
102             }
103         };
104     }
105 
106     /**
107      * Create a {@link WakeLock} containing a {@link PowerManager.WakeLock}.
108      * @param inner To be wrapped.
109      * @param maxTimeout When to expire.
110      * @return The new wake lock.
111      */
112     @VisibleForTesting
113     static WakeLock wrap(
114             final PowerManager.WakeLock inner, WakeLockLogger logger, long maxTimeout) {
115         return new WakeLock() {
116             private final HashMap<String, Integer> mActiveClients = new HashMap<>();
117 
118             /** @see PowerManager.WakeLock#acquire() */
119             public void acquire(String why) {
120                 mActiveClients.putIfAbsent(why, 0);
121                 int count = mActiveClients.get(why) + 1;
122                 mActiveClients.put(why, count);
123                 if (logger != null) {
124                     logger.logAcquire(inner, why, count);
125                 }
126                 inner.acquire(maxTimeout);
127             }
128 
129             /** @see PowerManager.WakeLock#release() */
130             public void release(String why) {
131                 Integer count = mActiveClients.get(why);
132                 if (count == null) {
133                     Log.wtf(TAG, "Releasing WakeLock with invalid reason: " + why,
134                             new Throwable());
135                     return;
136                 }
137                 count--;
138                 if (count == 0) {
139                     mActiveClients.remove(why);
140                 } else {
141                     mActiveClients.put(why, count);
142                 }
143                 if (logger != null) {
144                     logger.logRelease(inner, why, count);
145                 }
146                 inner.release();
147             }
148 
149             /** @see PowerManager.WakeLock#wrap(Runnable) */
150             public Runnable wrap(Runnable runnable) {
151                 return wrapImpl(this, runnable);
152             }
153 
154             @Override
155             public String toString() {
156                 return "active clients= " + mActiveClients;
157             }
158         };
159     }
160 
161     /**
162      * An injectable Builder that wraps {@link #createPartial(Context, String, long)}.
163      */
164     class Builder {
165         private final Context mContext;
166         private final WakeLockLogger mLogger;
167         private String mTag;
168         private int mLevelsAndFlags = DEFAULT_LEVELS_AND_FLAGS;
169         private long mMaxTimeout = DEFAULT_MAX_TIMEOUT;
170 
171         @Inject
172         public Builder(Context context, WakeLockLogger logger) {
173             mContext = context;
174             mLogger = logger;
175         }
176 
177         public Builder setTag(String tag) {
178             this.mTag = tag;
179             return this;
180         }
181 
182         public Builder setLevelsAndFlags(int levelsAndFlags) {
183             this.mLevelsAndFlags = levelsAndFlags;
184             return this;
185         }
186 
187         public Builder setMaxTimeout(long maxTimeout) {
188             this.mMaxTimeout = maxTimeout;
189             return this;
190         }
191 
192         public WakeLock build() {
193             return WakeLock.createWakeLock(mContext, mLogger, mTag, mLevelsAndFlags, mMaxTimeout);
194         }
195     }
196 }
197