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