"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