13d6e487dSEvan Baconimport path from 'path';
23d6e487dSEvan Bacon
38a424bebSJames Ideimport { resolveInstallApkNameAsync } from './resolveInstallApkName';
48a424bebSJames Ideimport { Options, ResolvedOptions, resolveOptionsAsync } from './resolveOptions';
53d6e487dSEvan Baconimport { Log } from '../../log';
63d6e487dSEvan Baconimport { assembleAsync, installAsync } from '../../start/platforms/android/gradle';
72dd43328SEvan Baconimport { setNodeEnv } from '../../utils/nodeEnv';
8*75a300d7SEvan Baconimport { ensurePortAvailabilityAsync } from '../../utils/port';
93d6e487dSEvan Baconimport { getSchemesForAndroidAsync } from '../../utils/scheme';
103d6e487dSEvan Baconimport { ensureNativeProjectAsync } from '../ensureNativeProject';
113d6e487dSEvan Baconimport { logProjectLogsLocation } from '../hints';
123d6e487dSEvan Baconimport { startBundlerAsync } from '../startBundler';
133d6e487dSEvan Bacon
143d6e487dSEvan Baconconst debug = require('debug')('expo:run:android');
153d6e487dSEvan Bacon
163d6e487dSEvan Baconexport async function runAndroidAsync(projectRoot: string, { install, ...options }: Options) {
176a750d06SEvan Bacon  // NOTE: This is a guess, the developer can overwrite with `NODE_ENV`.
186a750d06SEvan Bacon  setNodeEnv(options.variant === 'release' ? 'production' : 'development');
196a750d06SEvan Bacon  require('@expo/env').load(projectRoot);
202dd43328SEvan Bacon
213d6e487dSEvan Bacon  await ensureNativeProjectAsync(projectRoot, { platform: 'android', install });
223d6e487dSEvan Bacon
233d6e487dSEvan Bacon  const props = await resolveOptionsAsync(projectRoot, options);
243d6e487dSEvan Bacon
253d6e487dSEvan Bacon  debug('Package name: ' + props.packageName);
263d6e487dSEvan Bacon  Log.log('› Building app...');
273d6e487dSEvan Bacon
283d6e487dSEvan Bacon  const androidProjectRoot = path.join(projectRoot, 'android');
293d6e487dSEvan Bacon
303d6e487dSEvan Bacon  await assembleAsync(androidProjectRoot, {
313d6e487dSEvan Bacon    variant: props.variant,
323d6e487dSEvan Bacon    port: props.port,
333d6e487dSEvan Bacon    appName: props.appName,
343d6e487dSEvan Bacon    buildCache: props.buildCache,
353d6e487dSEvan Bacon  });
363d6e487dSEvan Bacon
37*75a300d7SEvan Bacon  // Ensure the port hasn't become busy during the build.
38*75a300d7SEvan Bacon  if (props.shouldStartBundler && !(await ensurePortAvailabilityAsync(projectRoot, props))) {
39*75a300d7SEvan Bacon    props.shouldStartBundler = false;
40*75a300d7SEvan Bacon  }
41*75a300d7SEvan Bacon
423d6e487dSEvan Bacon  const manager = await startBundlerAsync(projectRoot, {
433d6e487dSEvan Bacon    port: props.port,
44c4ef02aeSEvan Bacon    // If a scheme is specified then use that instead of the package name.
45c4ef02aeSEvan Bacon    scheme: (await getSchemesForAndroidAsync(projectRoot))?.[0],
463d6e487dSEvan Bacon    headless: !props.shouldStartBundler,
473d6e487dSEvan Bacon  });
483d6e487dSEvan Bacon
493d6e487dSEvan Bacon  await installAppAsync(androidProjectRoot, props);
503d6e487dSEvan Bacon
513d6e487dSEvan Bacon  await manager.getDefaultDevServer().openCustomRuntimeAsync(
523d6e487dSEvan Bacon    'emulator',
533d6e487dSEvan Bacon    {
543d6e487dSEvan Bacon      applicationId: props.packageName,
553d6e487dSEvan Bacon    },
563d6e487dSEvan Bacon    { device: props.device.device }
573d6e487dSEvan Bacon  );
583d6e487dSEvan Bacon
593d6e487dSEvan Bacon  if (props.shouldStartBundler) {
603d6e487dSEvan Bacon    logProjectLogsLocation();
613d6e487dSEvan Bacon  }
623d6e487dSEvan Bacon}
633d6e487dSEvan Bacon
643d6e487dSEvan Baconasync function installAppAsync(androidProjectRoot: string, props: ResolvedOptions) {
653d6e487dSEvan Bacon  // Find the APK file path
663d6e487dSEvan Bacon  const apkFile = await resolveInstallApkNameAsync(props.device.device, props);
673d6e487dSEvan Bacon
683d6e487dSEvan Bacon  if (apkFile) {
693d6e487dSEvan Bacon    // Attempt to install the APK from the file path
703d6e487dSEvan Bacon    const binaryPath = path.join(props.apkVariantDirectory, apkFile);
713d6e487dSEvan Bacon    debug('Installing:', binaryPath);
723d6e487dSEvan Bacon    await props.device.installAppAsync(binaryPath);
733d6e487dSEvan Bacon  } else {
743d6e487dSEvan Bacon    // If we cannot resolve the APK file path then we can attempt to install using Gradle.
753d6e487dSEvan Bacon    // This offers more advanced resolution that we may not have first class support for.
763d6e487dSEvan Bacon    Log.log('› Failed to locate binary file, installing with Gradle...');
773d6e487dSEvan Bacon    await installAsync(androidProjectRoot, {
783d6e487dSEvan Bacon      variant: props.variant ?? 'debug',
793d6e487dSEvan Bacon      appName: props.appName ?? 'app',
803d6e487dSEvan Bacon      port: props.port,
813d6e487dSEvan Bacon    });
823d6e487dSEvan Bacon  }
833d6e487dSEvan Bacon}
84