You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1270 lines
44 KiB

"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