WakeLocks.kt

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
@file:JvmName("WakeLocks")

package androidx.work.impl.utils

import android.content.Context
import android.os.PowerManager
import androidx.work.Logger
import java.util.WeakHashMap

private val TAG = Logger.tagWithPrefix("WakeLocks")

/**
 * Creates and returns a new WakeLock.
 *
 * @param context The context from which to get the PowerManager
 * @param tag A descriptive tag for the WakeLock; this method will prefix "WorkManager: " to it
 * @return A new [android.os.PowerManager.WakeLock]
 */
internal fun newWakeLock(context: Context, tag: String): PowerManager.WakeLock {
    val powerManager = context.applicationContext
        .getSystemService(Context.POWER_SERVICE) as PowerManager
    val tagWithPrefix = "WorkManager: $tag"
    val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tagWithPrefix)
    // Wakelocks are created on the command processor thread, but we check if they are still
    // being held on the main thread.
    synchronized(WakeLocksHolder) {
        WakeLocksHolder.wakeLocks.put(wakeLock, tagWithPrefix)
    }
    return wakeLock
}

/**
 * Checks to see if there are any [PowerManager.WakeLock]s that
 * [androidx.work.impl.background.systemalarm.SystemAlarmService] holds when all the
 * pending commands have been drained in the command queue.
 */
fun checkWakeLocks() {
    // There is a small chance that while we are checking if all the commands in the queue are
    // drained and wake locks are no longer being held, a new command comes along and we end up
    // with a ConcurrentModificationException. The addition of commands happens on the command
    // processor thread and this check is done on the main thread.
    val wakeLocksCopy = mutableMapOf<PowerManager.WakeLock?, String>()
    synchronized(WakeLocksHolder) {
        // Copy the WakeLocks - otherwise we can get a ConcurrentModificationException if the
        // garbage collector kicks in and ends up removing something from the master copy while
        // we are iterating over it.
        wakeLocksCopy.putAll(WakeLocksHolder.wakeLocks)
    }

    wakeLocksCopy.forEach { (wakeLock, tag) ->
        if (wakeLock?.isHeld == true) Logger.get().warning(TAG, "WakeLock held for $tag")
    }
}

private object WakeLocksHolder {
    val wakeLocks = WeakHashMap<PowerManager.WakeLock, String>()
}