com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner.java Source code

Java tutorial

Introduction

Here is the source code for com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner.java

Source

/*
 * Copyright 2016 The Bazel Authors. All rights reserved.
 *
 * 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.
 */

package com.google.idea.blaze.android.run.runner;

import static org.jetbrains.android.actions.RunAndroidAvdManagerAction.getName;

import com.android.ddmlib.IDevice;
import com.android.tools.idea.run.AndroidProcessHandler;
import com.android.tools.idea.run.AndroidSessionInfo;
import com.android.tools.idea.run.ApkProvisionException;
import com.android.tools.idea.run.ApplicationIdProvider;
import com.android.tools.idea.run.DeviceFutures;
import com.android.tools.idea.run.LaunchInfo;
import com.android.tools.idea.run.LaunchOptions;
import com.android.tools.idea.run.LaunchTaskRunner;
import com.android.tools.idea.run.editor.DeployTarget;
import com.android.tools.idea.run.editor.DeployTargetState;
import com.android.tools.idea.run.tasks.LaunchTasksProvider;
import com.android.tools.idea.run.util.LaunchUtils;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.idea.blaze.base.experiments.ExperimentScope;
import com.google.idea.blaze.base.metrics.Action;
import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner;
import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils;
import com.google.idea.blaze.base.scope.Scope;
import com.google.idea.blaze.base.scope.output.IssueOutput;
import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope;
import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
import com.google.idea.blaze.base.scope.scopes.IssuesScope;
import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
import com.google.idea.blaze.base.settings.BlazeUserSettings;
import com.intellij.execution.DefaultExecutionResult;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.ExecutionResult;
import com.intellij.execution.Executor;
import com.intellij.execution.configurations.RunProfileState;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.ProgramRunner;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.sdk.AndroidSdkUtils;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * Supports the execution. Used by both android_binary and android_test.
 *
 * <p>Builds the APK and installs it, launches and debug tasks, etc.
 *
 * <p>Any indirection between android_binary/android_test, mobile-install, InstantRun etc. should
 * come via the strategy class.
 */
public final class BlazeAndroidRunConfigurationRunner implements BlazeCommandRunConfigurationRunner {

    private static final Logger LOG = Logger.getInstance(BlazeAndroidRunConfigurationRunner.class);

    private static final Key<BlazeAndroidRunContext> RUN_CONTEXT_KEY = Key.create("blaze.run.context");
    public static final Key<BlazeAndroidDeviceSelector.DeviceSession> DEVICE_SESSION_KEY = Key
            .create("blaze.device.session");

    private final Module module;
    private final BlazeAndroidRunContext runContext;
    private final BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager;
    private final BlazeAndroidRunConfigurationDebuggerManager debuggerManager;
    private final int runConfigId;

    public BlazeAndroidRunConfigurationRunner(Module module, BlazeAndroidRunContext runContext,
            BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager,
            BlazeAndroidRunConfigurationDebuggerManager debuggerManager, int runConfigId) {
        this.module = module;
        this.runContext = runContext;
        this.deployTargetManager = deployTargetManager;
        this.debuggerManager = debuggerManager;
        this.runConfigId = runConfigId;
    }

    @Override
    @Nullable
    public final RunProfileState getRunProfileState(final Executor executor, ExecutionEnvironment env)
            throws ExecutionException {

        final AndroidFacet facet = AndroidFacet.getInstance(module);
        assert facet != null : "Enforced by fatal validation check in createRunner.";
        final Project project = env.getProject();

        runContext.augmentEnvironment(env);

        boolean isDebug = executor instanceof DefaultDebugExecutor;
        if (isDebug && !AndroidSdkUtils.activateDdmsIfNecessary(facet.getModule().getProject())) {
            throw new ExecutionException("Unable to obtain debug bridge. "
                    + "Please check if there is a different tool using adb that is active.");
        }

        AndroidSessionInfo info = AndroidSessionInfo.findOldSession(project, null, runConfigId);

        BlazeAndroidDeviceSelector deviceSelector = runContext.getDeviceSelector();
        BlazeAndroidDeviceSelector.DeviceSession deviceSession = deviceSelector.getDevice(project, facet,
                deployTargetManager, executor, env, info, isDebug, runConfigId);
        if (deviceSession == null) {
            return null;
        }

        DeployTarget deployTarget = deviceSession.deployTarget;
        if (deployTarget != null && deployTarget.hasCustomRunProfileState(executor)) {
            DeployTargetState deployTargetState = deployTargetManager.getCurrentDeployTargetState();
            return deployTarget.getRunProfileState(executor, env, deployTargetState);
        }

        DeviceFutures deviceFutures = deviceSession.deviceFutures;
        if (deviceFutures == null) {
            // The user deliberately canceled, or some error was encountered and exposed by the chooser.
            // Quietly exit.
            return null;
        }

        if (deviceFutures.get().isEmpty()) {
            throw new ExecutionException(AndroidBundle.message("deployment.target.not.found"));
        }

        if (isDebug) {
            String error = canDebug(deviceFutures, facet, module.getName());
            if (error != null) {
                throw new ExecutionException(error);
            }
        }

        LaunchOptions.Builder launchOptionsBuilder = getDefaultLaunchOptions().setDebug(isDebug);
        runContext.augmentLaunchOptions(launchOptionsBuilder);

        // Store the run context on the execution environment so before-run tasks can access it.
        env.putCopyableUserData(RUN_CONTEXT_KEY, runContext);
        env.putCopyableUserData(DEVICE_SESSION_KEY, deviceSession);

        return new BlazeAndroidRunState(module, env, getName(), launchOptionsBuilder, isDebug, deviceSession,
                runContext);
    }

    private static String canDebug(DeviceFutures deviceFutures, AndroidFacet facet, String moduleName) {
        // If we are debugging on a device, then the app needs to be debuggable
        for (ListenableFuture<IDevice> future : deviceFutures.get()) {
            if (!future.isDone()) {
                // this is an emulator, and we assume that all emulators are debuggable
                continue;
            }
            IDevice device = Futures.getUnchecked(future);
            if (!LaunchUtils.canDebugAppOnDevice(facet, device)) {
                return AndroidBundle.message("android.cannot.debug.noDebugPermissions", moduleName,
                        device.getName());
            }
        }
        return null;
    }

    private static LaunchOptions.Builder getDefaultLaunchOptions() {
        return LaunchOptions.builder().setClearLogcatBeforeStart(false).setSkipNoopApkInstallations(true)
                .setForceStopRunningApp(true);
    }

    @Override
    public boolean executeBeforeRunTask(ExecutionEnvironment env) {
        final Project project = env.getProject();
        boolean suppressConsole = BlazeUserSettings.getInstance().getSuppressConsoleForRunAction();
        return Scope.root(context -> {
            context.push(new IssuesScope(project)).push(new ExperimentScope())
                    .push(new BlazeConsoleScope.Builder(project).setSuppressConsole(suppressConsole).build())
                    .push(new IdeaLogScope()).push(new LoggedTimingScope(project, Action.APK_BUILD_AND_INSTALL));

            BlazeAndroidRunContext runContext = env.getCopyableUserData(RUN_CONTEXT_KEY);
            if (runContext == null) {
                IssueOutput.error("Could not find run context. Please try again").submit(context);
                return false;
            }
            BlazeAndroidDeviceSelector.DeviceSession deviceSession = env.getCopyableUserData(DEVICE_SESSION_KEY);

            BlazeApkBuildStep buildStep = runContext.getBuildStep();
            try {
                return buildStep.build(context, deviceSession);
            } catch (Exception e) {
                LOG.error(e);
                return false;
            }
        });
    }

    private final class BlazeAndroidRunState implements RunProfileState {

        private final Module module;
        private final ExecutionEnvironment env;
        private final String launchConfigName;
        private final BlazeAndroidDeviceSelector.DeviceSession deviceSession;
        private final BlazeAndroidRunContext runContext;
        private final LaunchOptions.Builder launchOptionsBuilder;
        private final boolean isDebug;

        private BlazeAndroidRunState(Module module, ExecutionEnvironment env, String launchConfigName,
                LaunchOptions.Builder launchOptionsBuilder, boolean isDebug,
                BlazeAndroidDeviceSelector.DeviceSession deviceSession, BlazeAndroidRunContext runContext) {
            this.module = module;
            this.env = env;
            this.launchConfigName = launchConfigName;
            this.deviceSession = deviceSession;
            this.runContext = runContext;
            this.launchOptionsBuilder = launchOptionsBuilder;
            this.isDebug = isDebug;
        }

        @Nullable
        @Override
        public ExecutionResult execute(Executor executor, ProgramRunner runner) throws ExecutionException {
            DefaultExecutionResult result = executeInner(executor, runner);
            if (result == null) {
                return null;
            }
            return SmRunnerUtils.attachRerunFailedTestsAction(result);
        }

        @Nullable
        private DefaultExecutionResult executeInner(Executor executor, @NotNull ProgramRunner<?> runner)
                throws ExecutionException {
            ProcessHandler processHandler;
            ConsoleView console;

            ApplicationIdProvider applicationIdProvider = runContext.getApplicationIdProvider();

            String applicationId;
            try {
                applicationId = applicationIdProvider.getPackageName();
            } catch (ApkProvisionException e) {
                throw new ExecutionException("Unable to obtain application id", e);
            }

            LaunchTasksProvider launchTasksProvider = runContext.getLaunchTasksProvider(launchOptionsBuilder,
                    isDebug, debuggerManager);

            DeviceFutures deviceFutures = deviceSession.deviceFutures;
            assert deviceFutures != null;
            ProcessHandler previousSessionProcessHandler = deviceSession.sessionInfo != null
                    ? deviceSession.sessionInfo.getProcessHandler()
                    : null;

            if (launchTasksProvider.createsNewProcess()) {
                // In the case of cold swap, there is an existing process that is connected,
                // but we are going to launch a new one.
                // Detach the previous process handler so that we don't end up with
                // 2 run tabs for the same launch (the existing one and the new one).
                if (previousSessionProcessHandler != null) {
                    previousSessionProcessHandler.detachProcess();
                }

                processHandler = new AndroidProcessHandler(applicationId,
                        launchTasksProvider.monitorRemoteProcess());
                console = runContext.getConsoleProvider().createAndAttach(module.getProject(), processHandler,
                        executor);
            } else {
                assert previousSessionProcessHandler != null : "No process handler from previous session, yet current tasks don't create one";
                processHandler = previousSessionProcessHandler;
                console = null;
            }

            LaunchInfo launchInfo = new LaunchInfo(executor, runner, env, runContext.getConsoleProvider());

            LaunchTaskRunner task = new LaunchTaskRunner(module.getProject(), launchConfigName, launchInfo,
                    processHandler, deviceSession.deviceFutures, launchTasksProvider);
            ProgressManager.getInstance().run(task);

            return console == null ? null : new DefaultExecutionResult(console, processHandler);
        }
    }
}