"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: all[name]
    });
}
_export(exports, {
    DeviceABI: function() {
        return DeviceABI;
    },
    adbArgs: function() {
        return adbArgs;
    },
    getAdbNameForDeviceIdAsync: function() {
        return getAdbNameForDeviceIdAsync;
    },
    getAttachedDevicesAsync: function() {
        return getAttachedDevicesAsync;
    },
    getDeviceABIsAsync: function() {
        return getDeviceABIsAsync;
    },
    getPackageInfoAsync: function() {
        return getPackageInfoAsync;
    },
    getPropertyDataForDeviceAsync: function() {
        return getPropertyDataForDeviceAsync;
    },
    getServer: function() {
        return getServer;
    },
    installAsync: function() {
        return installAsync;
    },
    isBootAnimationCompleteAsync: function() {
        return isBootAnimationCompleteAsync;
    },
    isDeviceBootedAsync: function() {
        return isDeviceBootedAsync;
    },
    isPackageInstalledAsync: function() {
        return isPackageInstalledAsync;
    },
    launchActivityAsync: function() {
        return launchActivityAsync;
    },
    logUnauthorized: function() {
        return logUnauthorized;
    },
    openUrlAsync: function() {
        return openUrlAsync;
    },
    sanitizeAdbDeviceName: function() {
        return sanitizeAdbDeviceName;
    },
    uninstallAsync: function() {
        return uninstallAsync;
    }
});
function _chalk() {
    const data = /*#__PURE__*/ _interop_require_default(require("chalk"));
    _chalk = function() {
        return data;
    };
    return data;
}
function _os() {
    const data = /*#__PURE__*/ _interop_require_default(require("os"));
    _os = function() {
        return data;
    };
    return data;
}
const _ADBServer = require("./ADBServer");
const _log = /*#__PURE__*/ _interop_require_wildcard(require("../../../log"));
const _env = require("../../../utils/env");
const _errors = require("../../../utils/errors");
const _link = require("../../../utils/link");
function _interop_require_default(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 _interop_require_wildcard(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 = {
        __proto__: null
    };
    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;
}
const debug = require('debug')('expo:start:platforms:android:adb');
var DeviceABI = /*#__PURE__*/ function(DeviceABI) {
    // The arch specific android target platforms are soft-deprecated.
    // Instead of using TargetPlatform as a combination arch + platform
    // the code will be updated to carry arch information in [DarwinArch]
    // and [AndroidArch].
    DeviceABI["arm"] = "arm";
    DeviceABI["arm64"] = "arm64";
    DeviceABI["x64"] = "x64";
    DeviceABI["x86"] = "x86";
    DeviceABI["x8664"] = "x86_64";
    DeviceABI["arm64v8a"] = "arm64-v8a";
    DeviceABI["armeabiV7a"] = "armeabi-v7a";
    DeviceABI["armeabi"] = "armeabi";
    DeviceABI["universal"] = "universal";
    return DeviceABI;
}({});
const CANT_START_ACTIVITY_ERROR = 'Activity not started, unable to resolve Intent';
// http://developer.android.com/ndk/guides/abis.html
const PROP_CPU_NAME = 'ro.product.cpu.abi';
const PROP_CPU_ABI_LIST_NAME = 'ro.product.cpu.abilist';
// Can sometimes be null
// http://developer.android.com/ndk/guides/abis.html
const PROP_BOOT_ANIMATION_STATE = 'init.svc.bootanim';
let _server;
function getServer() {
    _server ??= new _ADBServer.ADBServer();
    return _server;
}
function logUnauthorized(device) {
    _log.warn(`\nThis computer is not authorized for developing on ${_chalk().default.bold(device.name)}. ${_chalk().default.dim((0, _link.learnMore)('https://expo.fyi/authorize-android-device'))}`);
}
async function isPackageInstalledAsync(device, androidPackage) {
    const packages = await getServer().runAsync(adbArgs(device.pid, 'shell', 'pm', 'list', 'packages', '--user', _env.env.EXPO_ADB_USER, 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;
}
async function launchActivityAsync(device, { launchActivity, url }) {
    const args = [
        'shell',
        'am',
        'start',
        // 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
    ];
    if (url) {
        args.push('-d', url);
    }
    return openAsync(adbArgs(device.pid, ...args));
}
async function openUrlAsync(device, { url }) {
    return openAsync(adbArgs(device.pid, 'shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-d', // ADB requires ampersands to be escaped.
    url.replace(/&/g, String.raw`\&`)));
}
/** Runs a generic command watches for common errors in order to throw with an expected code. */ async function openAsync(args) {
    const results = await getServer().runAsync(args);
    if (results.includes(CANT_START_ACTIVITY_ERROR) || results.match(/Error: Activity class .* does not exist\./g)) {
        throw new _errors.CommandError('APP_NOT_INSTALLED', results.substring(results.indexOf('Error: ')));
    }
    return results;
}
async function uninstallAsync(device, { appId }) {
    return await getServer().runAsync(adbArgs(device.pid, 'uninstall', '--user', _env.env.EXPO_ADB_USER, appId));
}
async function getPackageInfoAsync(device, { appId }) {
    return await getServer().runAsync(adbArgs(device.pid, 'shell', 'dumpsys', 'package', appId));
}
async function installAsync(device, { filePath }) {
    // TODO: Handle the `INSTALL_FAILED_INSUFFICIENT_STORAGE` error.
    return await getServer().runAsync(adbArgs(device.pid, 'install', '-r', '-d', '--user', _env.env.EXPO_ADB_USER, filePath));
}
function adbArgs(pid, ...options) {
    const args = [];
    if (pid) {
        args.push('-s', pid);
    }
    return args.concat(options);
}
async function getAttachedDevicesAsync() {
    const output = await getServer().runAsync([
        'devices',
        '-l'
    ]);
    const splitItems = output.trim().replace(/\n$/, '').split(_os().default.EOL)// Filter ADB trace logs from the output, e.g.
    // adb D 03-06 15:25:53 63677 4018815 adb_client.cpp:393] adb_query: host:devices-l
    // 03-04 12:29:44.557 16415 16415 D adb     : commandline.cpp:1646 Using server socket: tcp:172.27.192.1:5037
    // 03-04 12:29:44.557 16415 16415 D adb     : adb_client.cpp:160 _adb_connect: host:version
    .filter((line)=>!line.match(/\.cpp:[0-9]+/));
    // 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 type = line.includes('emulator') ? 'emulator' : 'device';
        let connectionType;
        if (type === 'device' && line.includes('usb:')) {
            connectionType = 'USB';
        } else if (type === 'device' && line.includes('_adb-tls-connect.')) {
            connectionType = 'Network';
        }
        const isBooted = type === 'emulator' || props[1] !== 'offline';
        const isAuthorized = connectionType === 'Network' ? line.includes('model:') // Network connected devices show `model:<name>` when authorized
         : props[1] !== 'unauthorized';
        return {
            props,
            type,
            isAuthorized,
            isBooted,
            connectionType
        };
    }).filter(({ props: [pid] })=>!!pid);
    const devicePromises = attachedDevices.map(async (props)=>{
        const { type, props: [pid, ...deviceInfo], isAuthorized, isBooted } = 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 {
            // Given an emulator pid, get the emulator name which can be used to start the emulator later.
            name = await getAdbNameForDeviceIdAsync({
                pid
            }) ?? '';
        }
        return props.connectionType ? {
            pid,
            name,
            type,
            isAuthorized,
            isBooted,
            connectionType: props.connectionType
        } : {
            pid,
            name,
            type,
            isAuthorized,
            isBooted
        };
    });
    return Promise.all(devicePromises);
}
async function getAdbNameForDeviceIdAsync(device) {
    const results = await getServer().runAsync(adbArgs(device.pid, 'emu', 'avd', 'name'));
    if (results.match(/could not connect to TCP port .*: Connection refused/)) {
        // Can also occur when the emulator does not exist.
        throw new _errors.CommandError('EMULATOR_NOT_FOUND', results);
    }
    return sanitizeAdbDeviceName(results) ?? null;
}
async function isDeviceBootedAsync({ name } = {}) {
    const devices = await getAttachedDevicesAsync();
    if (!name) {
        return devices[0] ?? null;
    }
    return devices.find((device)=>device.name === name) ?? null;
}
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 getDeviceABIsAsync(device) {
    const cpuAbiList = (await getPropertyDataForDeviceAsync(device, PROP_CPU_ABI_LIST_NAME))[PROP_CPU_ABI_LIST_NAME];
    if (cpuAbiList) {
        return cpuAbiList.trim().split(',');
    }
    const abi = (await getPropertyDataForDeviceAsync(device, PROP_CPU_NAME))[PROP_CPU_NAME];
    return [
        abi
    ];
}
async function getPropertyDataForDeviceAsync(device, prop) {
    // @ts-ignore
    const propCommand = adbArgs(...[
        device.pid,
        'shell',
        'getprop',
        prop
    ].filter(Boolean));
    try {
        // Prevent reading as UTF8.
        const results = await getServer().getFileOutputAsync(propCommand);
        // Like:
        // [wifi.direct.interface]: [p2p-dev-wlan0]
        // [wifi.interface]: [wlan0]
        if (prop) {
            debug(`Property data: (device pid: ${device.pid}, prop: ${prop}, data: ${results})`);
            return {
                [prop]: results
            };
        }
        const props = parseAdbDeviceProperties(results);
        debug(`Parsed data:`, props);
        return props;
    } catch (error) {
        // TODO: Ensure error has message and not stderr
        throw new _errors.CommandError(`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;
}
function sanitizeAdbDeviceName(deviceName) {
    return deviceName.trim().split(/[\r\n]+/).shift();
}

//# sourceMappingURL=adb.js.map