"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DeviceABI = void 0; exports.activateEmulatorWindowAsync = activateEmulatorWindowAsync; exports.attemptToStartEmulatorOrAssertAsync = attemptToStartEmulatorOrAssertAsync; exports.checkSplashScreenImages = checkSplashScreenImages; exports.getAdbFileOutputAsync = getAdbFileOutputAsync; exports.getAdbOutputAsync = getAdbOutputAsync; exports.getAllAvailableDevicesAsync = getAllAvailableDevicesAsync; exports.getAttachedDevicesAsync = getAttachedDevicesAsync; exports.getDeviceABIsAsync = getDeviceABIsAsync; exports.getDeviceAPIVersionAsync = getDeviceAPIVersionAsync; exports.getDeviceSDKVersionAsync = getDeviceSDKVersionAsync; exports.getPropertyForDeviceAsync = getPropertyForDeviceAsync; exports.installExpoAsync = installExpoAsync; exports.installOnDeviceAsync = installOnDeviceAsync; exports.isDeviceBootedAsync = isDeviceBootedAsync; exports.isPlatformSupported = isPlatformSupported; exports.maybeStopAdbDaemonAsync = maybeStopAdbDaemonAsync; exports.openAppAsync = openAppAsync; exports.openProjectAsync = openProjectAsync; exports.openWebProjectAsync = openWebProjectAsync; exports.parseAdbDeviceProperties = parseAdbDeviceProperties; exports.promptForDeviceAsync = promptForDeviceAsync; exports.resolveApplicationIdAsync = resolveApplicationIdAsync; exports.startAdbReverseAsync = startAdbReverseAsync; exports.stopAdbReverseAsync = stopAdbReverseAsync; exports.uninstallExpoAsync = uninstallExpoAsync; exports.upgradeExpoAsync = upgradeExpoAsync; function _config() { const data = require("@expo/config"); _config = function () { return data; }; return data; } function _configPlugins() { const data = require("@expo/config-plugins"); _configPlugins = function () { return data; }; return data; } function osascript() { const data = _interopRequireWildcard(require("@expo/osascript")); osascript = function () { return data; }; return data; } function _spawnAsync() { const data = _interopRequireDefault(require("@expo/spawn-async")); _spawnAsync = function () { return data; }; return data; } function _chalk() { const data = _interopRequireDefault(require("chalk")); _chalk = function () { return data; }; return data; } function _child_process() { const data = _interopRequireWildcard(require("child_process")); _child_process = function () { return data; }; return data; } function _trim() { const data = _interopRequireDefault(require("lodash/trim")); _trim = function () { return data; }; return data; } function _os() { const data = _interopRequireDefault(require("os")); _os = function () { return data; }; return data; } function _prompts() { const data = _interopRequireDefault(require("prompts")); _prompts = function () { return data; }; return data; } function _semver() { const data = _interopRequireDefault(require("semver")); _semver = function () { return data; }; return data; } function _internal() { const data = require("./internal"); _internal = function () { return data; }; return data; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } let _lastUrl = null; let _isAdbOwner = null; const BEGINNING_OF_ADB_ERROR_MESSAGE = 'error: '; const CANT_START_ACTIVITY_ERROR = 'Activity not started, unable to resolve Intent'; const INSTALL_WARNING_TIMEOUT = 60 * 1000; const EMULATOR_MAX_WAIT_TIMEOUT = 60 * 1000 * 3; function whichEmulator() { if (process.env.ANDROID_HOME) { return `${process.env.ANDROID_HOME}/emulator/emulator`; } return 'emulator'; } function whichADB() { if (process.env.ANDROID_HOME) { return `${process.env.ANDROID_HOME}/platform-tools/adb`; } return 'adb'; } /** * Returns a list of emulator names. */ async function getEmulatorsAsync() { try { const { stdout } = await (0, _spawnAsync().default)(whichEmulator(), ['-list-avds']); return stdout.split(_os().default.EOL).filter(Boolean).map(name => ({ name, type: 'emulator', // unsure from this isBooted: false, isAuthorized: true })); } catch { return []; } } /** * Return the Emulator name for an emulator ID, this can be used to determine if an emulator is booted. * * @param emulatorId a value like `emulator-5554` from `abd devices` */ async function getAbdNameForEmulatorIdAsync(emulatorId) { var _trim$split$shift; return (_trim$split$shift = (0, _trim().default)(await getAdbOutputAsync(['-s', emulatorId, 'emu', 'avd', 'name'])).split(/\r?\n/).shift()) !== null && _trim$split$shift !== void 0 ? _trim$split$shift : null; } async function getAllAvailableDevicesAsync() { const bootedDevices = await getAttachedDevicesAsync(); const data = await getEmulatorsAsync(); const connectedNames = bootedDevices.map(({ name }) => name); const offlineEmulators = data.filter(({ name }) => !connectedNames.includes(name)).map(({ name, type }) => { return { name, type, isBooted: false, // TODO: Are emulators always authorized? isAuthorized: true }; }); const allDevices = bootedDevices.concat(offlineEmulators); if (!allDevices.length) { 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.`; throw new Error(`No Android connected device found, and no emulators could be started automatically.\nPlease connect a device or create an emulator (https://docs.expo.dev/workflow/android-studio-emulator).\nThen follow the instructions here to enable USB debugging:\n${genymotionMessage}`); } return allDevices; } /** * Returns true when a device's splash screen animation has stopped. * This can be used to detect when a device is fully booted and ready to use. * * @param pid */ async function isBootAnimationCompleteAsync(pid) { try { const props = await getPropertyDataForDeviceAsync({ pid }, PROP_BOOT_ANIMATION_STATE); return !!props[PROP_BOOT_ANIMATION_STATE].match(/stopped/); } catch { return false; } } async function startEmulatorAsync(device) { _internal().Logger.global.info(`\u203A Opening emulator ${_chalk().default.bold(device.name)}`); // Start a process to open an emulator const emulatorProcess = _child_process().default.spawn(whichEmulator(), [`@${device.name}` // disable animation for faster boot -- this might make it harder to detect if it mounted properly tho //'-no-boot-anim' // '-google-maps-key' -- TODO: Use from config ], { stdio: 'ignore', detached: true }); emulatorProcess.unref(); return new Promise((resolve, reject) => { const waitTimer = setInterval(async () => { const bootedDevices = await getAttachedDevicesAsync(); const connected = bootedDevices.find(({ name }) => name === device.name); if (connected) { const isBooted = await isBootAnimationCompleteAsync(connected.pid); if (isBooted) { stopWaiting(); resolve(connected); } } }, 1000); // Reject command after timeout const maxTimer = setTimeout(() => { const manualCommand = `${whichEmulator()} @${device.name}`; stopWaitingAndReject(`It took too long to start the Android emulator: ${device.name}. You can try starting the emulator manually from the terminal with: ${manualCommand}`); }, EMULATOR_MAX_WAIT_TIMEOUT); const stopWaiting = () => { clearTimeout(maxTimer); clearInterval(waitTimer); }; const stopWaitingAndReject = message => { stopWaiting(); reject(new Error(message)); clearInterval(waitTimer); }; emulatorProcess.on('error', ({ message }) => stopWaitingAndReject(message)); emulatorProcess.on('exit', () => { const manualCommand = `${whichEmulator()} @${device.name}`; stopWaitingAndReject(`The emulator (${device.name}) quit before it finished opening. You can try starting the emulator manually from the terminal with: ${manualCommand}`); }); }); } // TODO: This is very expensive for some operations. async function getAttachedDevicesAsync() { const output = await getAdbOutputAsync(['devices', '-l']); const splitItems = output.trim().replace(/\n$/, '').split(_os().default.EOL); // First line is `"List of devices attached"`, remove it // @ts-ignore: todo const attachedDevices = splitItems.slice(1, splitItems.length).map(line => { // unauthorized: ['FA8251A00719', 'unauthorized', 'usb:338690048X', 'transport_id:5'] // authorized: ['FA8251A00719', 'device', 'usb:336592896X', 'product:walleye', 'model:Pixel_2', 'device:walleye', 'transport_id:4'] // emulator: ['emulator-5554', 'offline', 'transport_id:1'] const props = line.split(' ').filter(Boolean); const isAuthorized = props[1] !== 'unauthorized'; const type = line.includes('emulator') ? 'emulator' : 'device'; return { props, type, isAuthorized }; }).filter(({ props: [pid] }) => !!pid); const devicePromises = attachedDevices.map(async props => { const { type, props: [pid, ...deviceInfo], isAuthorized } = props; let name = null; if (type === 'device') { if (isAuthorized) { // Possibly formatted like `model:Pixel_2` // Transform to `Pixel_2` const modelItem = deviceInfo.find(info => info.includes('model:')); if (modelItem) { name = modelItem.replace('model:', ''); } } // unauthorized devices don't have a name available to read if (!name) { // Device FA8251A00719 name = `Device ${pid}`; } } else { var _await$getAbdNameForE; // Given an emulator pid, get the emulator name which can be used to start the emulator later. name = (_await$getAbdNameForE = await getAbdNameForEmulatorIdAsync(pid)) !== null && _await$getAbdNameForE !== void 0 ? _await$getAbdNameForE : ''; } return { pid, name, type, isAuthorized, isBooted: true }; }); return Promise.all(devicePromises); } function isPlatformSupported() { return process.platform === 'darwin' || process.platform === 'win32' || process.platform === 'linux'; } async function adbAlreadyRunning(adb) { try { const result = await (0, _spawnAsync().default)(adb, ['start-server']); const lines = (0, _trim().default)(result.stderr).split(/\r?\n/); return lines.includes('* daemon started successfully') === false; } catch (e) { let errorMessage = (0, _trim().default)(e.stderr || e.stdout); if (errorMessage.startsWith(BEGINNING_OF_ADB_ERROR_MESSAGE)) { errorMessage = errorMessage.substring(BEGINNING_OF_ADB_ERROR_MESSAGE.length); } e.message = errorMessage; throw e; } } async function getAdbOutputAsync(args) { await _internal().Binaries.addToPathAsync('adb'); const adb = whichADB(); if (_isAdbOwner === null) { const alreadyRunning = await adbAlreadyRunning(adb); _isAdbOwner = alreadyRunning === false; } if (_internal().Env.isDebug()) { _internal().Logger.global.info([adb, ...args].join(' ')); } try { const result = await (0, _spawnAsync().default)(adb, args); return result.output.join('\n'); } catch (e) { // User pressed ctrl+c to cancel the process... if (e.signal === 'SIGINT') { e.isAbortError = true; } // TODO: Support heap corruption for adb 29 (process exits with code -1073740940) (windows and linux) let errorMessage = (e.stderr || e.stdout || e.message).trim(); if (errorMessage.startsWith(BEGINNING_OF_ADB_ERROR_MESSAGE)) { errorMessage = errorMessage.substring(BEGINNING_OF_ADB_ERROR_MESSAGE.length); } e.message = errorMessage; throw e; } } async function getAdbFileOutputAsync(args, encoding) { await _internal().Binaries.addToPathAsync('adb'); const adb = whichADB(); if (_isAdbOwner === null) { const alreadyRunning = await adbAlreadyRunning(adb); _isAdbOwner = alreadyRunning === false; } try { return await (0, _child_process().execFileSync)(adb, args, { encoding, stdio: 'pipe' }); } catch (e) { let errorMessage = (e.stderr || e.stdout || e.message).trim(); if (errorMessage.startsWith(BEGINNING_OF_ADB_ERROR_MESSAGE)) { errorMessage = errorMessage.substring(BEGINNING_OF_ADB_ERROR_MESSAGE.length); } e.message = errorMessage; throw e; } } async function _isDeviceAuthorizedAsync(device) { // TODO: Get the latest version of the device in case isAuthorized changes. return device.isAuthorized; } async function isInstalledAsync(device, androidPackage) { const packages = await getAdbOutputAsync(adbPidArgs(device.pid, 'shell', 'pm', 'list', 'packages', androidPackage)); const lines = packages.split(/\r?\n/); for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line === `package:${androidPackage}`) { return true; } } return false; } // Expo installed async function _isExpoInstalledAsync(device) { return await isInstalledAsync(device, 'host.exp.exponent'); } async function ensureDevClientInstalledAsync(device, applicationId) { if (!(await isInstalledAsync(device, applicationId))) { throw new Error(`The development client (${applicationId}) for this project is not installed. ` + `Please build and install the client on the device first.\n${(0, _internal().learnMore)('https://docs.expo.dev/clients/distribution-for-android/')}`); } } async function isDevClientInstalledAsync(device, applicationId) { return await isInstalledAsync(device, applicationId); } async function getExpoVersionAsync(device) { const info = await getAdbOutputAsync(adbPidArgs(device.pid, 'shell', 'dumpsys', 'package', 'host.exp.exponent')); const regex = /versionName=([0-9.]+)/; const regexMatch = regex.exec(info); if (!regexMatch || regexMatch.length < 2) { return null; } return regexMatch[1]; } async function isClientOutdatedAsync(device, sdkVersion) { var _clientForSdk$version; const versions = await _internal().Versions.versionsAsync(); const clientForSdk = await getClientForSDK(sdkVersion); const latestVersionForSdk = (_clientForSdk$version = clientForSdk === null || clientForSdk === void 0 ? void 0 : clientForSdk.version) !== null && _clientForSdk$version !== void 0 ? _clientForSdk$version : versions.androidVersion; const installedVersion = await getExpoVersionAsync(device); return !installedVersion || _semver().default.lt(installedVersion, latestVersionForSdk); } async function installExpoAsync({ device, url, version }) { let warningTimer; const setWarningTimer = () => { if (warningTimer) { clearTimeout(warningTimer); } return setTimeout(() => { _internal().Logger.global.info(''); _internal().Logger.global.info('This download is taking longer than expected. You can also try downloading the clients from the website at https://expo.dev/tools'); }, INSTALL_WARNING_TIMEOUT); }; _internal().Logger.notifications.info({ code: _internal().LoadingEvent.START_PROGRESS_BAR }, 'Downloading the Expo Go app [:bar] :percent :etas'); warningTimer = setWarningTimer(); const path = await (0, _internal().downloadApkAsync)(url, progress => { _internal().Logger.notifications.info({ code: _internal().LoadingEvent.TICK_PROGRESS_BAR }, progress); }); _internal().Logger.notifications.info({ code: _internal().LoadingEvent.STOP_PROGRESS_BAR }); const message = version ? `Installing Expo Go ${version} on ${device.name}` : `Installing Expo Go on ${device.name}`; _internal().Logger.notifications.info({ code: _internal().LoadingEvent.START_LOADING }, message); warningTimer = setWarningTimer(); const result = await installOnDeviceAsync(device, { binaryPath: path }); _internal().Logger.notifications.info({ code: _internal().LoadingEvent.STOP_LOADING }); clearTimeout(warningTimer); return result; } async function installOnDeviceAsync(device, { binaryPath }) { return await getAdbOutputAsync(adbPidArgs(device.pid, 'install', '-r', '-d', binaryPath)); } async function isDeviceBootedAsync({ name } = {}) { var _devices$find; const devices = await getAttachedDevicesAsync(); if (!name) { var _devices$; return (_devices$ = devices[0]) !== null && _devices$ !== void 0 ? _devices$ : null; } return (_devices$find = devices.find(device => device.name === name)) !== null && _devices$find !== void 0 ? _devices$find : null; } async function uninstallExpoAsync(device) { _internal().Logger.global.info('Uninstalling Expo Go from Android device.'); // we need to check if its installed, else we might bump into "Failure [DELETE_FAILED_INTERNAL_ERROR]" const isInstalled = await _isExpoInstalledAsync(device); if (!isInstalled) { return; } try { return await getAdbOutputAsync(adbPidArgs(device.pid, 'uninstall', 'host.exp.exponent')); } catch (e) { _internal().Logger.global.error('Could not uninstall Expo Go from your device, please uninstall Expo Go manually and try again.'); throw e; } } async function upgradeExpoAsync({ url, version, device } = {}) { try { if (!device) { device = (await getAttachedDevicesAsync())[0]; if (!device) { throw new Error('no devices connected'); } } device = await attemptToStartEmulatorOrAssertAsync(device); if (!device) { return false; } await uninstallExpoAsync(device); await installExpoAsync({ device, url, version }); if (_lastUrl) { _internal().Logger.global.info(`\u203A Opening ${_lastUrl} in Expo.`); await getAdbOutputAsync(['shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-d', _lastUrl]); _lastUrl = null; } return true; } catch (e) { _internal().Logger.global.error(e.message); return false; } } async function _openUrlAsync({ pid, url, applicationId }) { // NOTE(brentvatne): temporary workaround! launch Expo Go first, then // launch the project! // https://github.com/expo/expo/issues/7772 // adb shell monkey -p host.exp.exponent -c android.intent.category.LAUNCHER 1 // Note: this is not needed in Expo Development Client, it only applies to Expo Go if (applicationId === 'host.exp.exponent') { const openClient = await getAdbOutputAsync(adbPidArgs(pid, 'shell', 'monkey', '-p', applicationId, '-c', 'android.intent.category.LAUNCHER', '1')); if (openClient.includes(CANT_START_ACTIVITY_ERROR)) { throw new Error(openClient.substring(openClient.indexOf('Error: '))); } } const openProject = await getAdbOutputAsync(adbPidArgs(pid, 'shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-d', url)); if (openProject.includes(CANT_START_ACTIVITY_ERROR)) { throw new Error(openProject.substring(openProject.indexOf('Error: '))); } return openProject; } function getUnixPID(port) { return (0, _child_process().execFileSync)('lsof', [`-i:${port}`, '-P', '-t', '-sTCP:LISTEN'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).split('\n')[0].trim(); } async function activateEmulatorWindowAsync(device) { var _match; if ( // only mac is supported for now. process.platform !== 'darwin' || // can only focus emulators device.type !== 'emulator') { return; } // Google Emulator ID: `emulator-5554` -> `5554` const androidPid = (_match = device.pid.match(/-(\d+)/)) === null || _match === void 0 ? void 0 : _match[1]; if (!androidPid) { return; } // Unix PID const pid = getUnixPID(androidPid); try { await osascript().execAsync(` tell application "System Events" set frontmost of the first process whose unix id is ${pid} to true end tell`); } catch { // noop -- this feature is very specific and subject to failure. } } /** * @param device Android device to open on * @param props.launchActivity Activity to launch `[application identifier]/.[main activity name]`, ex: `com.bacon.app/.MainActivity` */ async function openAppAsync(device, { launchActivity }) { const openProject = await getAdbOutputAsync(adbPidArgs(device.pid, 'shell', 'am', 'start', '-a', 'android.intent.action.RUN', // FLAG_ACTIVITY_SINGLE_TOP -- If set, the activity will not be launched if it is already running at the top of the history stack. '-f', '0x20000000', // Activity to open first: com.bacon.app/.MainActivity '-n', launchActivity)); // App is not installed or main activity cannot be found if (openProject.match(/Error: Activity class .* does not exist./g)) { throw new (_internal().XDLError)('APP_NOT_INSTALLED', openProject.substring(openProject.indexOf('Error: '))); } await activateEmulatorWindowAsync(device); return openProject; } async function attemptToStartEmulatorOrAssertAsync(device) { // TODO: Add a light-weight method for checking since a device could disconnect. if (!(await isDeviceBootedAsync(device))) { device = await startEmulatorAsync(device); } if (!(await _isDeviceAuthorizedAsync(device))) { logUnauthorized(device); return null; } return device; } function logUnauthorized(device) { _internal().Logger.global.warn(`\nThis computer is not authorized for developing on ${_chalk().default.bold(device.name)}. ${_chalk().default.dim((0, _internal().learnMore)('https://expo.fyi/authorize-android-device'))}`); } // Keep a list of simulator UDIDs so we can prevent asking multiple times if a user wants to upgrade. // This can prevent annoying interactions when they don't want to upgrade for whatever reason. const hasPromptedToUpgrade = {}; async function isManagedProjectAsync(projectRoot) { try { await _configPlugins().AndroidConfig.Paths.getProjectPathOrThrowAsync(projectRoot); return false; } catch { return true; } } async function openUrlAsync({ url, device, isDetached = false, sdkVersion, devClient = false, exp, projectRoot }) { const bootedDevice = await attemptToStartEmulatorOrAssertAsync(device); if (!bootedDevice) { return; } _internal().Logger.global.info(`\u203A Opening ${_chalk().default.underline(url)} on ${_chalk().default.bold(bootedDevice.name)}`); await activateEmulatorWindowAsync(bootedDevice); device = bootedDevice; let installedExpo = false; let clientApplicationId = 'host.exp.exponent'; const installExpoIfNeeded = async device => { var _device$pid; let shouldInstall = !(await _isExpoInstalledAsync(device)); const promptKey = (_device$pid = device.pid) !== null && _device$pid !== void 0 ? _device$pid : 'unknown'; if (!shouldInstall && !hasPromptedToUpgrade[promptKey] && (await isClientOutdatedAsync(device, sdkVersion))) { // Only prompt once per device, per run. hasPromptedToUpgrade[promptKey] = true; const confirm = await _internal().Prompts.confirmAsync({ initial: true, message: `Expo Go on ${device.name} (${device.type}) is outdated, would you like to upgrade?` }); if (confirm) { await uninstallExpoAsync(device); shouldInstall = true; } } if (shouldInstall) { const androidClient = await getClientForSDK(sdkVersion); await installExpoAsync({ device, ...androidClient }); installedExpo = true; } }; const getClientApplicationId = async () => { let applicationId; const isManaged = await isManagedProjectAsync(projectRoot); if (isManaged) { var _exp$android; applicationId = exp === null || exp === void 0 ? void 0 : (_exp$android = exp.android) === null || _exp$android === void 0 ? void 0 : _exp$android.package; if (!applicationId) { throw new Error(`Could not find property android.package in app.config.js/app.json. This setting is required to launch the app.`); } } else { applicationId = await resolveApplicationIdAsync(projectRoot); if (!applicationId) { throw new Error(`Could not find applicationId in ${_configPlugins().AndroidConfig.Paths.getAppBuildGradleFilePath(projectRoot)}`); } } return applicationId; }; try { if (devClient) { clientApplicationId = await getClientApplicationId(); await ensureDevClientInstalledAsync(device, clientApplicationId); } else if (_internal().Env.isInterstitiaLPageEnabled() && !devClient && (0, _internal().isDevClientPackageInstalled)(projectRoot)) { await installExpoIfNeeded(device); let applicationId; try { applicationId = await getClientApplicationId(); } catch (e) { _internal().Logger.global.warn(e); } const isDevClientInstalled = applicationId ? await isDevClientInstalledAsync(device, applicationId) : false; if (isDevClientInstalled) { // Everything is installed, we can present the interstitial page. clientApplicationId = ''; // it will open browser } else { // The development build isn't available. So let's fall back to Expo Go. _internal().Logger.global.warn(`\u203A The 'expo-dev-client' package is installed, but a development build isn't available.\nYour app will open in Expo Go instead. If you want to use the development build, please install it on the simulator first.\n${(0, _internal().learnMore)('https://docs.expo.dev/development/build/')}`); const newProjectUrl = await constructDeepLinkAsync(projectRoot, undefined, false, false); if (!newProjectUrl) { // This shouldn't happen. throw Error('Could not generate a deep link for your project.'); } url = newProjectUrl; _internal().Logger.global.debug(`iOS project url: ${url}`); _lastUrl = url; } } else if (!isDetached) { await installExpoIfNeeded(device); _lastUrl = url; // _checkExpoUpToDateAsync(); // let this run in background } try { await _openUrlAsync({ pid: device.pid, url, applicationId: clientApplicationId }); } 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; } if (device.type === 'emulator') { // TODO: Bring the emulator window to the front. } _internal().Analytics.logEvent('Open Url on Device', { platform: 'android', installedExpo }); } catch (e) { e.message = `Error running adb: ${e.message}`; throw e; } } async function getClientForSDK(sdkVersionString) { if (!sdkVersionString) { return null; } const sdkVersion = (await _internal().Versions.sdkVersionsAsync())[sdkVersionString]; if (!sdkVersion) { return null; } return { url: sdkVersion.androidClientUrl, version: sdkVersion.androidClientVersion }; } async function resolveApplicationIdAsync(projectRoot) { var _exp$android$package, _exp$android2; try { const applicationIdFromGradle = await _configPlugins().AndroidConfig.Package.getApplicationIdAsync(projectRoot); if (applicationIdFromGradle) { return applicationIdFromGradle; } } catch {} try { var _androidManifest$mani, _androidManifest$mani2; const filePath = await _configPlugins().AndroidConfig.Paths.getAndroidManifestAsync(projectRoot); const androidManifest = await _configPlugins().AndroidConfig.Manifest.readAndroidManifestAsync(filePath); // Assert MainActivity defined. await _configPlugins().AndroidConfig.Manifest.getMainActivityOrThrow(androidManifest); if ((_androidManifest$mani = androidManifest.manifest) !== null && _androidManifest$mani !== void 0 && (_androidManifest$mani2 = _androidManifest$mani.$) !== null && _androidManifest$mani2 !== void 0 && _androidManifest$mani2.package) { return androidManifest.manifest.$.package; } } catch {} const { exp } = (0, _config().getConfig)(projectRoot, { skipSDKVersionRequirement: true }); return (_exp$android$package = (_exp$android2 = exp.android) === null || _exp$android2 === void 0 ? void 0 : _exp$android2.package) !== null && _exp$android$package !== void 0 ? _exp$android$package : null; } async function constructDeepLinkAsync(projectRoot, scheme, devClient, shouldGenerateInterstitialPage = true) { if (_internal().Env.isInterstitiaLPageEnabled() && !devClient && (0, _internal().isDevClientPackageInstalled)(projectRoot) && shouldGenerateInterstitialPage) { return _internal().UrlUtils.constructLoadingUrlAsync(projectRoot, 'android'); } else { return await _internal().UrlUtils.constructDeepLinkAsync(projectRoot, { scheme }).catch(e => { if (devClient) { return null; } throw e; }); } } async function openProjectAsync({ projectRoot, shouldPrompt, devClient = false, device, scheme, applicationId, launchActivity }) { await startAdbReverseAsync(projectRoot); const projectUrl = await constructDeepLinkAsync(projectRoot, scheme, devClient); const { exp } = (0, _config().getConfig)(projectRoot, { skipSDKVersionRequirement: true }); // Resolve device if (device) { const booted = await attemptToStartEmulatorOrAssertAsync(device); if (!booted) { return { success: false, error: 'escaped' }; } device = booted; } else { const devices = await getAllAvailableDevicesAsync(); let booted = devices[0]; if (shouldPrompt) { booted = await promptForDeviceAsync(devices); } if (!booted) { return { success: false, error: 'escaped' }; } device = booted; } // No URL, and is devClient if (!projectUrl) { if (!launchActivity) { var _applicationId; applicationId = (_applicationId = applicationId) !== null && _applicationId !== void 0 ? _applicationId : await resolveApplicationIdAsync(projectRoot); if (!applicationId) { return { success: false, error: 'Cannot resolve application identifier or URI scheme to open the native Android app.\nBuild the native app with `expo run:android` or `eas build -p android`' }; } launchActivity = `${applicationId}/.MainActivity`; } try { await openAppAsync(device, { launchActivity }); } catch (error) { let errorMessage = `Couldn't open Android app with activity "${launchActivity}" on device "${device.name}".`; if (error instanceof _internal().XDLError && error.code === 'APP_NOT_INSTALLED') { errorMessage += `\nThe app might not be installed, try installing it with: ${_chalk().default.bold(`expo run:android -d ${device.name}`)}`; } errorMessage += _chalk().default.gray(`\n${error.message}`); error.message = errorMessage; return { success: false, error }; } return { success: true, // TODO: Remove this hack url: '' }; } try { await openUrlAsync({ url: projectUrl, device, isDetached: !!exp.isDetached, sdkVersion: exp.sdkVersion, devClient, exp, projectRoot }); return { success: true, url: projectUrl }; } catch (e) { if (e.isAbortError) { // Don't log anything when the user cancelled the process return { success: false, error: 'escaped' }; } else { e.message = `Couldn't start project on Android: ${e.message}`; } return { success: false, error: e }; } } async function openWebProjectAsync({ projectRoot, shouldPrompt }) { try { await startAdbReverseAsync(projectRoot); const projectUrl = await _internal().Webpack.getUrlAsync(projectRoot); if (projectUrl === null) { return { success: false, error: `The web project has not been started yet` }; } const devices = await getAllAvailableDevicesAsync(); let device = devices[0]; if (shouldPrompt) { device = await promptForDeviceAsync(devices); } if (!device) { return { success: false, error: 'escaped' }; } await openUrlAsync({ url: projectUrl, device, isDetached: true, projectRoot }); return { success: true, url: projectUrl }; } catch (e) { return { success: false, error: `Couldn't open the web project on Android: ${e.message}` }; } } // Adb reverse async function startAdbReverseAsync(projectRoot) { const packagerInfo = await _internal().ProjectSettings.readPackagerInfoAsync(projectRoot); const expRc = await (0, _config().readExpRcAsync)(projectRoot); const userDefinedAdbReversePorts = expRc.extraAdbReversePorts || []; const adbReversePorts = [packagerInfo.packagerPort, packagerInfo.expoServerPort, ...userDefinedAdbReversePorts].filter(Boolean); const devices = await getAttachedDevicesAsync(); for (const device of devices) { for (const port of adbReversePorts) { if (!(await adbReverse({ device, port }))) { return false; } } } return true; } async function stopAdbReverseAsync(projectRoot) { const packagerInfo = await _internal().ProjectSettings.readPackagerInfoAsync(projectRoot); const expRc = await (0, _config().readExpRcAsync)(projectRoot); const userDefinedAdbReversePorts = expRc.extraAdbReversePorts || []; const adbReversePorts = [packagerInfo.packagerPort, packagerInfo.expoServerPort, ...userDefinedAdbReversePorts].filter(Boolean); const devices = await getAttachedDevicesAsync(); for (const device of devices) { for (const port of adbReversePorts) { await adbReverseRemove({ device, port }); } } } async function adbReverse({ device, port }) { if (!(await _isDeviceAuthorizedAsync(device))) { return false; } try { await getAdbOutputAsync(adbPidArgs(device.pid, 'reverse', `tcp:${port}`, `tcp:${port}`)); return true; } catch (e) { _internal().Logger.global.warn(`Couldn't adb reverse: ${e.message}`); return false; } } async function adbReverseRemove({ device, port }) { if (!(await _isDeviceAuthorizedAsync(device))) { return false; } try { await getAdbOutputAsync(adbPidArgs(device.pid, 'reverse', '--remove', `tcp:${port}`)); return true; } catch (e) { // Don't send this to warn because we call this preemptively sometimes _internal().Logger.global.debug(`Couldn't adb reverse remove: ${e.message}`); return false; } } function adbPidArgs(pid, ...options) { const args = []; if (pid) { args.push('-s', pid); } return args.concat(options); } 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 projectRoot - directory of the expo project * @since SDK33 */ async function checkSplashScreenImages(projectRoot) { var _ref, _exp$android$splash$r, _exp$android3, _exp$android3$splash, _exp$splash, _exp$splash2, _exp$android4; const { exp } = (0, _config().getConfig)(projectRoot); // return before SDK33 if (!_internal().Versions.gteSdkVersion(exp, '33.0.0')) { return; } const splashScreenMode = (_ref = (_exp$android$splash$r = (_exp$android3 = exp.android) === null || _exp$android3 === void 0 ? void 0 : (_exp$android3$splash = _exp$android3.splash) === null || _exp$android3$splash === void 0 ? void 0 : _exp$android3$splash.resizeMode) !== null && _exp$android$splash$r !== void 0 ? _exp$android$splash$r : (_exp$splash = exp.splash) === null || _exp$splash === void 0 ? void 0 : _exp$splash.resizeMode) !== null && _ref !== void 0 ? _ref : 'contain'; // only mode `native` is handled by this check if (splashScreenMode === 'contain' || splashScreenMode === 'cover') { return; } const generalSplashImagePath = (_exp$splash2 = exp.splash) === null || _exp$splash2 === void 0 ? void 0 : _exp$splash2.image; if (!generalSplashImagePath) { _internal().Logger.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 _internal().ImageUtils.getImageDimensionsAsync(projectRoot, generalSplashImagePath); if (!generalSplashImage) { _internal().Logger.global.warn(`Couldn't read dimensions of provided splash image '${_chalk().default.italic(generalSplashImagePath)}'. Does the file exist?`); return; } const androidSplash = (_exp$android4 = exp.android) === null || _exp$android4 === void 0 ? void 0 : _exp$android4.splash; const androidSplashImages = []; for (const { dpi, sizeMultiplier } of splashScreenDPIConstraints) { const imageRelativePath = androidSplash === null || androidSplash === void 0 ? void 0 : androidSplash[dpi]; if (imageRelativePath) { const splashImage = await _internal().ImageUtils.getImageDimensionsAsync(projectRoot, imageRelativePath); if (!splashImage) { _internal().Logger.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) { _internal().Logger.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.dev/guides/splash-screens/#splash-screen-api-limitations-on-android for more information`); return; } if (androidSplashImages.some(({ sizeMatches }) => !sizeMatches)) { _internal().Logger.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.dev/guides/splash-screens/#splash-screen-api-limitations-on-android for more information`); } } async function maybeStopAdbDaemonAsync() { if (_isAdbOwner !== true) { return false; } try { await getAdbOutputAsync(['kill-server']); return true; } catch { return false; } } function nameStyleForDevice(device) { const isActive = device.isBooted; if (!isActive) { // Use no style changes for a disconnected device that is available to be opened. return text => text; } // A device that is connected and ready to be used should be bolded to match iOS. if (device.isAuthorized) { return _chalk().default.bold; } // Devices that are unauthorized and connected cannot be used, but they are connected so gray them out. return text => _chalk().default.bold(_chalk().default.gray(text)); } async function promptForDeviceAsync(devices) { // TODO: provide an option to add or download more simulators // Pause interactions on the TerminalUI _internal().Prompts.pauseInteractions(); const { value } = await (0, _prompts().default)({ type: 'autocomplete', name: 'value', limit: 11, message: 'Select a device/emulator', choices: devices.map(item => { const format = nameStyleForDevice(item); const type = item.isAuthorized ? item.type : 'unauthorized'; return { title: `${format(item.name)} ${_chalk().default.dim(`(${type})`)}`, value: item.name }; }), suggest: (input, choices) => { const regex = new RegExp(input, 'i'); return choices.filter(choice => regex.test(choice.title)); } }); // Resume interactions on the TerminalUI _internal().Prompts.resumeInteractions(); const device = value ? devices.find(({ name }) => name === value) : null; if ((device === null || device === void 0 ? void 0 : device.isAuthorized) === false) { logUnauthorized(device); return null; } return device; } let DeviceABI; exports.DeviceABI = DeviceABI; (function (DeviceABI) { DeviceABI["arm"] = "arm"; DeviceABI["arm64"] = "arm64"; DeviceABI["x64"] = "x64"; DeviceABI["x86"] = "x86"; DeviceABI["armeabiV7a"] = "armeabi-v7a"; DeviceABI["armeabi"] = "armeabi"; DeviceABI["universal"] = "universal"; })(DeviceABI || (exports.DeviceABI = DeviceABI = {})); const deviceProperties = {}; const PROP_SDK_VERSION = 'ro.build.version.release'; // Can sometimes be null const PROP_API_VERSION = 'ro.build.version.sdk'; // http://developer.android.com/ndk/guides/abis.html const PROP_CPU_NAME = 'ro.product.cpu.abi'; const PROP_CPU_ABILIST_NAME = 'ro.product.cpu.abilist'; const PROP_BOOT_ANIMATION_STATE = 'init.svc.bootanim'; const LOWEST_SUPPORTED_EXPO_API_VERSION = 21; /** * @returns string like '11' (i.e. Android 11) */ async function getDeviceSDKVersionAsync(device) { return await getPropertyForDeviceAsync(device, PROP_SDK_VERSION); } /** * @returns number like `30` (i.e. API 30) */ async function getDeviceAPIVersionAsync(device) { var _await$getPropertyFor; const sdkVersion = (_await$getPropertyFor = await getPropertyForDeviceAsync(device, PROP_API_VERSION)) !== null && _await$getPropertyFor !== void 0 ? _await$getPropertyFor : LOWEST_SUPPORTED_EXPO_API_VERSION; return parseInt(sdkVersion, 10); } async function getDeviceABIsAsync(device) { const cpuAbilist = await getPropertyForDeviceAsync(device, PROP_CPU_ABILIST_NAME); if (cpuAbilist) { return cpuAbilist.trim().split(','); } const abi = await getPropertyForDeviceAsync(device, PROP_CPU_NAME); return [abi]; } async function getPropertyForDeviceAsync(device, name, shouldRefresh) { if (shouldRefresh) { delete deviceProperties[device.name]; } if (deviceProperties[device.name] == null) { try { deviceProperties[device.name] = await getPropertyDataForDeviceAsync(device); } catch (error) { // TODO: Ensure error has message and not stderr _internal().Logger.global.error(`Failed to get properties for device "${device.name}" (${device.pid}): ${error.message}`); } } return deviceProperties[device.name][name]; } async function getPropertyDataForDeviceAsync(device, prop) { // @ts-ignore const propCommand = adbPidArgs(...[device.pid, 'shell', 'getprop', prop].filter(Boolean)); try { // Prevent reading as UTF8. const results = (await getAdbFileOutputAsync(propCommand, 'latin1')).toString('latin1'); // Like: // [wifi.direct.interface]: [p2p-dev-wlan0] // [wifi.interface]: [wlan0] if (prop) { return { [prop]: results }; } return parseAdbDeviceProperties(results); } catch (error) { // TODO: Ensure error has message and not stderr throw new Error(`Failed to get properties for device (${device.pid}): ${error.message}`); } } function parseAdbDeviceProperties(devicePropertiesString) { const properties = {}; const propertyExp = /\[(.*?)\]: \[(.*?)\]/gm; for (const match of devicePropertiesString.matchAll(propertyExp)) { properties[match[1]] = match[2]; } return properties; } //# sourceMappingURL=Android.js.map