ProcessUtils.kt

/*
 * Copyright 2020 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("ProcessUtils")

package androidx.work.impl.utils

import android.annotation.SuppressLint
import android.app.ActivityManager
import android.app.Application
import android.content.Context
import android.os.Build
import android.os.Process
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.work.Configuration
import androidx.work.Logger
import androidx.work.WorkManager

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

/**
 * @return `true` when `WorkManager` is running in the configured app process.
 */
fun isDefaultProcess(context: Context, configuration: Configuration): Boolean {
    val processName = getProcessName(context)
    return if (!configuration.defaultProcessName.isNullOrEmpty()) {
        processName == configuration.defaultProcessName
    } else {
        processName == context.applicationInfo.processName
    }
}

/**
 * @return The name of the active process.
 */
@SuppressLint("PrivateApi", "DiscouragedPrivateApi")
private fun getProcessName(context: Context): String? {
    if (Build.VERSION.SDK_INT >= 28) return Api28Impl.processName

    // Try using ActivityThread to determine the current process name.
    try {
        val activityThread = Class.forName(
            "android.app.ActivityThread",
            false,
            WorkManager::class.java.classLoader
        )
        val packageName = if (Build.VERSION.SDK_INT >= 18) {
            val currentProcessName = activityThread.getDeclaredMethod("currentProcessName")
            currentProcessName.isAccessible = true
            currentProcessName.invoke(null)!!
        } else {
            val getActivityThread = activityThread.getDeclaredMethod("currentActivityThread")
            getActivityThread.isAccessible = true
            val getProcessName = activityThread.getDeclaredMethod("getProcessName")
            getProcessName.isAccessible = true
            getProcessName.invoke(getActivityThread.invoke(null))!!
        }
        if (packageName is String) return packageName
    } catch (exception: Throwable) {
        Logger.get().debug(TAG, "Unable to check ActivityThread for processName", exception)
    }

    // Fallback to the most expensive way
    val pid = Process.myPid()
    val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return am.runningAppProcesses?.find { process -> process.pid == pid }?.processName
}

@RequiresApi(28)
private object Api28Impl {
    @get:DoNotInline
    val processName: String
        get() = Application.getProcessName()
}