All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.triplet.gradle.play.tasks.InstallInternalSharingArtifact.kt Maven / Gradle / Ivy

package com.github.triplet.gradle.play.tasks

import com.android.build.gradle.AppExtension
import com.android.build.gradle.internal.LoggerWrapper
import com.android.build.gradle.internal.testing.ConnectedDeviceProvider
import com.android.builder.testing.api.DeviceProvider
import com.android.ddmlib.MultiLineReceiver
import com.android.utils.ILogger
import com.google.api.client.json.jackson2.JacksonFactory
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Property
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.submit
import org.gradle.kotlin.dsl.support.serviceOf
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import java.io.File
import java.util.concurrent.TimeUnit
import javax.inject.Inject

internal abstract class InstallInternalSharingArtifact @Inject constructor(
        private val extension: AppExtension
) : DefaultTask() {
    @get:PathSensitive(PathSensitivity.RELATIVE)
    @get:InputDirectory
    abstract val uploadedArtifacts: DirectoryProperty

    init {
        // Always out-of-date since we don't know anything about the target device
        outputs.upToDateWhen { false }
    }

    @TaskAction
    fun install() {
        val uploads = uploadedArtifacts
        project.serviceOf().noIsolation().submit(Installer::class) {
            uploadedArtifacts.set(uploads)
            adbExecutable.set(extension.adbExecutable)
            timeOutInMs.set(extension.adbOptions.timeOutInMs)
        }
    }

    abstract class Installer : WorkAction {
        override fun execute() {
            val uploads = parameters.uploadedArtifacts.get().asFileTree
            val latestUpload = checkNotNull(
                    uploads.maxBy { it.nameWithoutExtension.toLong() }
            ) { "Failed to find uploaded artifacts in ${uploads.joinToString()}" }
            val launchUrl = latestUpload.inputStream().use {
                JacksonFactory.getDefaultInstance().createJsonParser(it).parse(Map::class.java)
            }["downloadUrl"] as String

            val shell = AdbShell(
                    parameters.adbExecutable.get().asFile, parameters.timeOutInMs.get())
            val result = shell.executeShellCommand(
                    "am start -a \"android.intent.action.VIEW\" -d $launchUrl")
            check(result) {
                "Failed to install on any devices."
            }
        }

        interface Params : WorkParameters {
            val uploadedArtifacts: DirectoryProperty
            val adbExecutable: RegularFileProperty
            val timeOutInMs: Property
        }
    }

    interface AdbShell {
        fun executeShellCommand(command: String): Boolean

        interface Factory {
            fun create(adbExecutable: File, timeOutInMs: Int): AdbShell
        }

        companion object {
            private var factory: Factory = DefaultAdbShell

            internal fun setFactory(factory: Factory) {
                Companion.factory = factory
            }

            operator fun invoke(
                    adbExecutable: File,
                    timeOutInMs: Int
            ): AdbShell = factory.create(adbExecutable, timeOutInMs)
        }
    }

    private class DefaultAdbShell(
            private val deviceProvider: DeviceProvider,
            private val timeOutInMs: Long
    ) : AdbShell {
        override fun executeShellCommand(command: String): Boolean {
            return deviceProvider.use {
                launchIntents(deviceProvider, command)
            }
        }

        private fun launchIntents(deviceProvider: DeviceProvider, command: String): Boolean {
            var successfulLaunches = 0
            for (device in deviceProvider.devices) {
                val receiver = object : MultiLineReceiver() {
                    private var _hasErrored = false
                    val hasErrored get() = _hasErrored

                    override fun processNewLines(lines: Array) {
                        if (lines.any { it.contains("error", true) }) {
                            _hasErrored = true
                        }
                    }

                    override fun isCancelled() = false
                }

                device.executeShellCommand(
                        command,
                        receiver,
                        timeOutInMs,
                        TimeUnit.MILLISECONDS
                )

                if (!receiver.hasErrored) successfulLaunches++
            }

            return successfulLaunches > 0
        }

        companion object : AdbShell.Factory {
            override fun create(adbExecutable: File, timeOutInMs: Int): AdbShell {
                val deviceProvider = try {
                    ConnectedDeviceProvider(
                            adbExecutable,
                            timeOutInMs,
                            LoggerWrapper(Logging.getLogger(
                                    InstallInternalSharingArtifact::class.java))
                    )
                } catch (e: NoClassDefFoundError) {
                    // TODO(#756): remove when AGP 4.0 is the minimum
                    Class.forName("com.android.builder.testing.ConnectedDeviceProvider")
                            .getConstructor(File::class.java, Int::class.java, ILogger::class.java)
                            .newInstance(
                                    adbExecutable,
                                    timeOutInMs,
                                    LoggerWrapper(Logging.getLogger(
                                            InstallInternalSharingArtifact::class.java))
                            ) as DeviceProvider
                }
                return DefaultAdbShell(deviceProvider, timeOutInMs.toLong())
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy