"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.isPlatformSupported = isPlatformSupported;
exports.getAdbOutputAsync = getAdbOutputAsync;
exports.downloadApkAsync = downloadApkAsync;
exports.installExpoAsync = installExpoAsync;
exports.uninstallExpoAsync = uninstallExpoAsync;
exports.upgradeExpoAsync = upgradeExpoAsync;
exports.assertDeviceReadyAsync = assertDeviceReadyAsync;
exports.openProjectAsync = openProjectAsync;
exports.openWebProjectAsync = openWebProjectAsync;
exports.startAdbReverseAsync = startAdbReverseAsync;
exports.stopAdbReverseAsync = stopAdbReverseAsync;
exports.checkSplashScreenImages = checkSplashScreenImages;

function _lodash() {
  const data = _interopRequireDefault(require("lodash"));

  _lodash = function () {
    return data;
  };

  return data;
}

function _fsExtra() {
  const data = _interopRequireDefault(require("fs-extra"));

  _fsExtra = function () {
    return data;
  };

  return data;
}

function _spawnAsync() {
  const data = _interopRequireDefault(require("@expo/spawn-async"));

  _spawnAsync = function () {
    return data;
  };

  return data;
}

function _path() {
  const data = _interopRequireDefault(require("path"));

  _path = function () {
    return data;
  };

  return data;
}

function _semver() {
  const data = _interopRequireDefault(require("semver"));

  _semver = function () {
    return data;
  };

  return data;
}

function _chalk() {
  const data = _interopRequireDefault(require("chalk"));

  _chalk = function () {
    return data;
  };

  return data;
}

function ConfigUtils() {
  const data = _interopRequireWildcard(require("@expo/config"));

  ConfigUtils = function () {
    return data;
  };

  return data;
}

function Analytics() {
  const data = _interopRequireWildcard(require("./Analytics"));

  Analytics = function () {
    return data;
  };

  return data;
}

function Binaries() {
  const data = _interopRequireWildcard(require("./Binaries"));

  Binaries = function () {
    return data;
  };

  return data;
}

function _Api() {
  const data = _interopRequireDefault(require("./Api"));

  _Api = function () {
    return data;
  };

  return data;
}

function _Logger() {
  const data = _interopRequireDefault(require("./Logger"));

  _Logger = function () {
    return data;
  };

  return data;
}

function _NotificationCode() {
  const data = _interopRequireDefault(require("./NotificationCode"));

  _NotificationCode = function () {
    return data;
  };

  return data;
}

function ProjectUtils() {
  const data = _interopRequireWildcard(require("./project/ProjectUtils"));

  ProjectUtils = function () {
    return data;
  };

  return data;
}

function ProjectSettings() {
  const data = _interopRequireWildcard(require("./ProjectSettings"));

  ProjectSettings = function () {
    return data;
  };

  return data;
}

function _UserSettings() {
  const data = _interopRequireDefault(require("./UserSettings"));

  _UserSettings = function () {
    return data;
  };

  return data;
}

function UrlUtils() {
  const data = _interopRequireWildcard(require("./UrlUtils"));

  UrlUtils = function () {
    return data;
  };

  return data;
}

function Versions() {
  const data = _interopRequireWildcard(require("./Versions"));

  Versions = function () {
    return data;
  };

  return data;
}

function _ImageUtils() {
  const data = require("./tools/ImageUtils");

  _ImageUtils = function () {
    return data;
  };

  return data;
}

function _Webpack() {
  const data = require("./Webpack");

  _Webpack = function () {
    return data;
  };

  return data;
}

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

// @ts-ignore
let _lastUrl = null;
const BEGINNING_OF_ADB_ERROR_MESSAGE = 'error: ';
const CANT_START_ACTIVITY_ERROR = 'Activity not started, unable to resolve Intent';

function isPlatformSupported() {
  return process.platform === 'darwin' || process.platform === 'win32' || process.platform === 'linux';
}

async function getAdbOutputAsync(args) {
  await Binaries().addToPathAsync('adb');

  try {
    let result = await (0, _spawnAsync().default)('adb', args);
    return result.stdout;
  } catch (e) {
    let errorMessage = _lodash().default.trim(e.stderr);

    if (errorMessage.startsWith(BEGINNING_OF_ADB_ERROR_MESSAGE)) {
      errorMessage = errorMessage.substring(BEGINNING_OF_ADB_ERROR_MESSAGE.length);
    }

    throw new Error(errorMessage);
  }
} // Device attached


async function _isDeviceAttachedAsync() {
  let devices = await getAdbOutputAsync(['devices']);

  let lines = _lodash().default.trim(devices).split(/\r?\n/); // First line is "List of devices".


  return lines.length > 1;
}

async function _isDeviceAuthorizedAsync() {
  let devices = await getAdbOutputAsync(['devices']);

  let lines = _lodash().default.trim(devices).split(/\r?\n/);

  lines.shift();
  let listOfDevicesWithoutFirstLine = lines.join('\n'); // result looks like "072c4cf200e333c7  device" when authorized
  // and "072c4cf200e333c7  unauthorized" when not.

  return listOfDevicesWithoutFirstLine.includes('device');
} // Expo installed


async function _isExpoInstalledAsync() {
  let packages = await getAdbOutputAsync(['shell', 'pm', 'list', 'packages', '-f']);
  let lines = packages.split(/\r?\n/);

  for (let i = 0; i < lines.length; i++) {
    let line = lines[i];

    if (line.includes('host.exp.exponent.test')) {
      continue;
    }

    if (line.includes('host.exp.exponent')) {
      return true;
    }
  }

  return false;
}

async function _expoVersionAsync() {
  let info = await getAdbOutputAsync(['shell', 'dumpsys', 'package', 'host.exp.exponent']);
  let regex = /versionName=([0-9.]+)/;
  let regexMatch = regex.exec(info);

  if (!regexMatch || regexMatch.length < 2) {
    return null;
  }

  return regexMatch[1];
}

async function _checkExpoUpToDateAsync() {
  let versions = await Versions().versionsAsync();
  let installedVersion = await _expoVersionAsync();

  if (!installedVersion || _semver().default.lt(installedVersion, versions.androidVersion)) {
    _Logger().default.notifications.warn({
      code: _NotificationCode().default.OLD_ANDROID_APP_VERSION
    }, 'This version of the Expo app is out of date. Uninstall the app and run again to upgrade.');
  }
}

function _apkCacheDirectory() {
  let dotExpoHomeDirectory = _UserSettings().default.dotExpoHomeDirectory();

  let dir = _path().default.join(dotExpoHomeDirectory, 'android-apk-cache');

  _fsExtra().default.mkdirpSync(dir);

  return dir;
}

async function downloadApkAsync(url) {
  let versions = await Versions().versionsAsync();

  let apkPath = _path().default.join(_apkCacheDirectory(), `Exponent-${versions.androidVersion}.apk`);

  if (await _fsExtra().default.pathExists(apkPath)) {
    return apkPath;
  }

  await _Api().default.downloadAsync(url || versions.androidUrl, _path().default.join(_apkCacheDirectory(), `Exponent-${versions.androidVersion}.apk`));
  return apkPath;
}

async function installExpoAsync(url) {
  _Logger().default.global.info(`Downloading latest version of Expo`);

  _Logger().default.notifications.info({
    code: _NotificationCode().default.START_LOADING
  });

  let path = await downloadApkAsync(url);

  _Logger().default.notifications.info({
    code: _NotificationCode().default.STOP_LOADING
  });

  _Logger().default.global.info(`Installing Expo on device`);

  _Logger().default.notifications.info({
    code: _NotificationCode().default.START_LOADING
  });

  let result = await getAdbOutputAsync(['install', path]);

  _Logger().default.notifications.info({
    code: _NotificationCode().default.STOP_LOADING
  });

  return result;
}

async function uninstallExpoAsync() {
  _Logger().default.global.info('Uninstalling Expo from Android device.');

  return await getAdbOutputAsync(['uninstall', 'host.exp.exponent']);
}

async function upgradeExpoAsync() {
  try {
    await assertDeviceReadyAsync();
    await uninstallExpoAsync();
    await installExpoAsync();

    if (_lastUrl) {
      _Logger().default.global.info(`Opening ${_lastUrl} in Expo.`);

      await getAdbOutputAsync(['shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-d', _lastUrl]);
      _lastUrl = null;
    }

    return true;
  } catch (e) {
    _Logger().default.global.error(e.message);

    return false;
  }
} // Open Url


async function assertDeviceReadyAsync() {
  const genymotionMessage = `https://developer.android.com/studio/run/device.html#developer-device-options. If you are using Genymotion go to Settings -> ADB, select "Use custom Android SDK tools", and point it at your Android SDK directory.`;

  if (!(await _isDeviceAttachedAsync())) {
    throw new Error(`No Android device found. Please connect a device and follow the instructions here to enable USB debugging:\n${genymotionMessage}`);
  }

  if (!(await _isDeviceAuthorizedAsync())) {
    throw new Error(`This computer is not authorized to debug the device. Please follow the instructions here to enable USB debugging:\n${genymotionMessage}`);
  }
}

async function _openUrlAsync(url) {
  let output = await getAdbOutputAsync(['shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-d', url]);

  if (output.includes(CANT_START_ACTIVITY_ERROR)) {
    throw new Error(output.substring(output.indexOf('Error: ')));
  }

  return output;
}

async function openUrlAsync(url, isDetached = false) {
  try {
    await assertDeviceReadyAsync();
    let installedExpo = false;

    if (!isDetached && !(await _isExpoInstalledAsync())) {
      await installExpoAsync();
      installedExpo = true;
    }

    if (!isDetached) {
      _lastUrl = url;

      _checkExpoUpToDateAsync(); // let this run in background

    }

    _Logger().default.global.info(`Opening on Android device`);

    try {
      await _openUrlAsync(url);
    } catch (e) {
      if (isDetached) {
        e.message = `Error running app. Have you installed the app already using Android Studio? Since you are detached you must build manually. ${e.message}`;
      } else {
        e.message = `Error running app. ${e.message}`;
      }

      throw e;
    }

    Analytics().logEvent('Open Url on Device', {
      platform: 'android',
      installedExpo
    });
  } catch (e) {
    e.message = `Error running adb: ${e.message}`;
    throw e;
  }
}

async function openProjectAsync(projectRoot) {
  try {
    await startAdbReverseAsync(projectRoot);
    let projectUrl = await UrlUtils().constructManifestUrlAsync(projectRoot);
    let {
      exp
    } = await ConfigUtils().readConfigJsonAsync(projectRoot);
    await openUrlAsync(projectUrl, !!exp.isDetached);
    return {
      success: true,
      url: projectUrl
    };
  } catch (e) {
    _Logger().default.global.error(`Couldn't start project on Android: ${e.message}`);

    return {
      success: false,
      error: e
    };
  }
}

async function openWebProjectAsync(projectRoot) {
  try {
    await startAdbReverseAsync(projectRoot);
    const projectUrl = await (0, _Webpack().getUrlAsync)(projectRoot);

    if (projectUrl === null) {
      return {
        success: false,
        error: `The web project has not been started yet`
      };
    }

    await openUrlAsync(projectUrl, true);
    return {
      success: true,
      url: projectUrl
    };
  } catch (e) {
    _Logger().default.global.error(`Couldn't open the web project on Android: ${e.message}`);

    return {
      success: false,
      error: e
    };
  }
} // Adb reverse


async function startAdbReverseAsync(projectRoot) {
  const packagerInfo = await ProjectSettings().readPackagerInfoAsync(projectRoot);
  const expRc = await ProjectUtils().readExpRcAsync(projectRoot);
  const userDefinedAdbReversePorts = expRc.extraAdbReversePorts || [];
  let adbReversePorts = [packagerInfo.packagerPort, packagerInfo.expoServerPort, ...userDefinedAdbReversePorts];

  for (let port of adbReversePorts) {
    if (!(await adbReverse(port))) {
      return false;
    }
  }

  return true;
}

async function stopAdbReverseAsync(projectRoot) {
  const packagerInfo = await ProjectSettings().readPackagerInfoAsync(projectRoot);
  const expRc = await ProjectUtils().readExpRcAsync(projectRoot);
  const userDefinedAdbReversePorts = expRc.extraAdbReversePorts || [];
  let adbReversePorts = [packagerInfo.packagerPort, packagerInfo.expoServerPort, ...userDefinedAdbReversePorts];

  for (let port of adbReversePorts) {
    await adbReverseRemove(port);
  }
}

async function adbReverse(port) {
  if (!(await _isDeviceAuthorizedAsync())) {
    return false;
  }

  try {
    await getAdbOutputAsync(['reverse', `tcp:${port}`, `tcp:${port}`]);
    return true;
  } catch (e) {
    _Logger().default.global.warn(`Couldn't adb reverse: ${e.message}`);

    return false;
  }
}

async function adbReverseRemove(port) {
  if (!(await _isDeviceAuthorizedAsync())) {
    return false;
  }

  try {
    await getAdbOutputAsync(['reverse', '--remove', `tcp:${port}`]);
    return true;
  } catch (e) {
    // Don't send this to warn because we call this preemptively sometimes
    _Logger().default.global.debug(`Couldn't adb reverse remove: ${e.message}`);

    return false;
  }
}

const splashScreenDPIConstraints = [{
  dpi: 'mdpi',
  sizeMultiplier: 1
}, {
  dpi: 'hdpi',
  sizeMultiplier: 1.5
}, {
  dpi: 'xhdpi',
  sizeMultiplier: 2
}, {
  dpi: 'xxhdpi',
  sizeMultiplier: 3
}, {
  dpi: 'xxxhdpi',
  sizeMultiplier: 4
}];
/**
 * Checks whether `resizeMode` is set to `native` and if `true` analyzes provided images for splashscreen
 * providing `Logger` feedback upon problems.
 * @param projectDir - directory of the expo project
 * @since SDK33
 */

async function checkSplashScreenImages(projectDir) {
  const {
    exp
  } = await ConfigUtils().readConfigJsonAsync(projectDir); // return before SDK33

  if (!Versions().gteSdkVersion(exp, '33.0.0')) {
    return;
  }

  const splashScreenMode = _lodash().default.get(exp, 'android.splash.resizeMode') || _lodash().default.get(exp, 'splash.resizeMode', 'contain'); // only mode `native` is handled by this check


  if (splashScreenMode === 'contain' || splashScreenMode === 'cover') {
    return;
  }

  const generalSplashImagePath = _lodash().default.get(exp, 'splash.image');

  if (!generalSplashImagePath) {
    _Logger().default.global.warn(`Couldn't read '${_chalk().default.italic('splash.image')}' from ${_chalk().default.italic('app.json')}. Provide asset that would serve as baseline splash image.`);

    return;
  }

  const generalSplashImage = await (0, _ImageUtils().getImageDimensionsAsync)(projectDir, generalSplashImagePath);

  if (!generalSplashImage) {
    _Logger().default.global.warn(`Couldn't read dimensions of provided splash image '${_chalk().default.italic(generalSplashImagePath)}'. Does the file exist?`);

    return;
  }

  const androidSplash = _lodash().default.get(exp, 'android.splash');

  const androidSplashImages = [];

  for (const {
    dpi,
    sizeMultiplier
  } of splashScreenDPIConstraints) {
    const imageRelativePath = _lodash().default.get(androidSplash, dpi);

    if (imageRelativePath) {
      const splashImage = await (0, _ImageUtils().getImageDimensionsAsync)(projectDir, imageRelativePath);

      if (!splashImage) {
        _Logger().default.global.warn(`Couldn't read dimensions of provided splash image '${_chalk().default.italic(imageRelativePath)}'. Does the file exist?`);

        continue;
      }

      const {
        width,
        height
      } = splashImage;
      const expectedWidth = sizeMultiplier * generalSplashImage.width;
      const expectedHeight = sizeMultiplier * generalSplashImage.height;
      androidSplashImages.push({
        dpi,
        width,
        height,
        expectedWidth,
        expectedHeight,
        sizeMatches: width === expectedWidth && height === expectedHeight
      });
    }
  }

  if (androidSplashImages.length === 0) {
    _Logger().default.global.warn(`Splash resizeMode is set to 'native', but you haven't provided any images for different DPIs.
Be aware that your splash image will be used as xxxhdpi asset and its ${_chalk().default.bold('actual size will be different')} depending on device's DPI.
See https://docs.expo.io/versions/latest/guides/splash-screens/#differences-between-environments---android for more information`);

    return;
  }

  if (_lodash().default.some(androidSplashImages, ({
    sizeMatches
  }) => !sizeMatches)) {
    _Logger().default.global.warn(`Splash resizeMode is set to 'native' and you've provided different images for different DPIs,
but their sizes mismatch expected ones: [dpi: provided (expected)] ${androidSplashImages.map(({
      dpi,
      width,
      height,
      expectedWidth,
      expectedHeight
    }) => `${dpi}: ${width}x${height} (${expectedWidth}x${expectedHeight})`).join(', ')}
See https://docs.expo.io/versions/latest/guides/splash-screens/#differences-between-environments---android for more information`);
  }
}
//# sourceMappingURL=__sourcemaps__/Android.js.map