diff --git a/lib/helpers.js b/lib/helpers.js index 42f34435..15e9dfdf 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -335,7 +335,7 @@ export function isScreenOnFully (dumpsys) { * Builds command line representation for the given * application startup options * - * @param {Record} startAppOptions - Application options mapping + * @param {StartCmdOptions} startAppOptions - Application options mapping * @param {number} apiLevel - The actual OS API level * @returns {string[]} The actual command line array */ @@ -349,16 +349,17 @@ export function buildStartCmd (startAppOptions, apiLevel) { category, stopApp, flags, + optionalIntentArguments, } = startAppOptions; const cmd = ['am', (apiLevel < 26) ? 'start' : 'start-activity']; if (util.hasValue(user)) { - cmd.push('--user', user); + cmd.push('--user', `${user}`); } if (waitForLaunch) { cmd.push('-W'); } if (activity && pkg) { - cmd.push('-n', `${pkg}/${activity}`); + cmd.push('-n', activity.startsWith(`${pkg}/`) ? activity : `${pkg}/${activity}`); } if (stopApp && apiLevel >= 15) { cmd.push('-S'); @@ -372,56 +373,8 @@ export function buildStartCmd (startAppOptions, apiLevel) { if (flags) { cmd.push('-f', flags); } - if (startAppOptions.optionalIntentArguments) { - // expect optionalIntentArguments to be a single string of the form: - // "-flag key" - // "-flag key value" - // or a combination of these (e.g., "-flag1 key1 -flag2 key2 value2") - - // take a string and parse out the part before any spaces, and anything after - // the first space - let parseKeyValue = function (str) { - str = str.trim(); - let space = str.indexOf(' '); - if (space === -1) { - return str.length ? [str] : []; - } else { - return [str.substring(0, space).trim(), str.substring(space + 1).trim()]; - } - }; - - // cycle through the optionalIntentArguments and pull out the arguments - // add a space initially so flags can be distinguished from arguments that - // have internal hyphens - let optionalIntentArguments = ` ${startAppOptions.optionalIntentArguments}`; - let re = / (-[^\s]+) (.+)/; - while (true) { - let args = re.exec(optionalIntentArguments); - if (!args) { - if (optionalIntentArguments.length) { - // no more flags, so the remainder can be treated as 'key' or 'key value' - cmd.push.apply(cmd, parseKeyValue(optionalIntentArguments)); - } - // we are done - break; - } - - // take the flag and see if it is at the beginning of the string - // if it is not, then it means we have been through already, and - // what is before the flag is the argument for the previous flag - let flag = args[1]; - let flagPos = optionalIntentArguments.indexOf(flag); - if (flagPos !== 0) { - let prevArgs = optionalIntentArguments.substring(0, flagPos); - cmd.push.apply(cmd, parseKeyValue(prevArgs)); - } - - // add the flag, as there are no more earlier arguments - cmd.push(flag); - - // make optionalIntentArguments hold the remainder - optionalIntentArguments = args[2]; - } + if (optionalIntentArguments) { + cmd.push(...parseOptionalIntentArguments(optionalIntentArguments)); } return cmd; } @@ -1106,6 +1059,64 @@ export async function readPackageManifest(apkPath) { return result; } +/** + * + * @param {string} value expect optionalIntentArguments to be a single string of the form: + * "-flag key" + * "-flag key value" + * or a combination of these (e.g., "-flag1 key1 -flag2 key2 value2") + * @returns {string[]} + */ +function parseOptionalIntentArguments(value) { + // take a string and parse out the part before any spaces, and anything after + // the first space + /** @type {(str: string) => string[]} */ + const parseKeyValue = (str) => { + str = str.trim(); + const spacePos = str.indexOf(' '); + if (spacePos < 0) { + return str.length ? [str] : []; + } else { + return [str.substring(0, spacePos).trim(), str.substring(spacePos + 1).trim()]; + } + }; + + // cycle through the optionalIntentArguments and pull out the arguments + // add a space initially so flags can be distinguished from arguments that + // have internal hyphens + let optionalIntentArguments = ` ${value}`; + const re = / (-[^\s]+) (.+)/; + /** @type {string[]} */ + const result = []; + while (true) { + const args = re.exec(optionalIntentArguments); + if (!args) { + if (optionalIntentArguments.length) { + // no more flags, so the remainder can be treated as 'key' or 'key value' + result.push(...parseKeyValue(optionalIntentArguments)); + } + // we are done + return result; + } + + // take the flag and see if it is at the beginning of the string + // if it is not, then it means we have been through already, and + // what is before the flag is the argument for the previous flag + const flag = args[1]; + const flagPos = optionalIntentArguments.indexOf(flag); + if (flagPos !== 0) { + const prevArgs = optionalIntentArguments.substring(0, flagPos); + result.push(...parseKeyValue(prevArgs)); + } + + // add the flag, as there are no more earlier arguments + result.push(flag); + + // make optionalIntentArguments hold the remainder + optionalIntentArguments = args[2]; + } +} + /** * @typedef {Object} InstallOptions * @property {boolean} [allowTestPackages=false] - Set to true in order to allow test @@ -1122,3 +1133,16 @@ export async function readPackageManifest(apkPath) { * @property {boolean} [partialInstall=false] - Install apks partially. It is used for 'install-multiple'. * https://android.stackexchange.com/questions/111064/what-is-a-partial-application-install-via-adb */ + +/** + * @typedef {Object} StartCmdOptions + * @property {number|string} [user] + * @property {boolean} [waitForLaunch] + * @property {string} [pkg] + * @property {string} [activity] + * @property {string} [action] + * @property {string} [category] + * @property {boolean} [stopApp] + * @property {string} [flags] + * @property {string} [optionalIntentArguments] + */ diff --git a/test/unit/helper-specs.js b/test/unit/helper-specs.js index af16f054..eb300735 100644 --- a/test/unit/helper-specs.js +++ b/test/unit/helper-specs.js @@ -11,6 +11,16 @@ import _ from 'lodash'; describe('helpers', withMocks({fs}, function (mocks) { + let chai; + + before(async function () { + chai = await import('chai'); + const chaiAsPromised = await import('chai-as-promised'); + + chai.should(); + chai.use(chaiAsPromised.default); + }); + afterEach(function () { mocks.verify(); }); @@ -107,7 +117,7 @@ describe('helpers', withMocks({fs}, function (mocks) { }); describe('buildStartCmd', function () { - let startOptions = { + const startOptions = { pkg: 'com.something', activity: '.SomeActivity' }; @@ -120,6 +130,17 @@ describe('helpers', withMocks({fs}, function (mocks) { let cmd = buildStartCmd(startOptions, 26); cmd[1].should.eql('start-activity'); }); + it('should not repeat package name', function () { + let cmd = buildStartCmd({ + pkg: 'com.package', + activity: 'com.package/.activity', + }, 20); + cmd.includes('com.package/.activity').should.be.true; + }); + it('should inlcude package name', function () { + let cmd = buildStartCmd(startOptions, 20); + cmd.includes(`${startOptions.pkg}/${startOptions.activity}`).should.be.true; + }); it('should parse optionalIntentArguments with single key', function () { let cmd = buildStartCmd(_.defaults({optionalIntentArguments: '-d key'}, startOptions), 20); cmd[cmd.length - 2].should.eql('-d');