Skip to content

Commit

Permalink
chore: update cert installation process
Browse files Browse the repository at this point in the history
  • Loading branch information
maliroteh-sf committed Sep 17, 2024
1 parent 2b97e57 commit 782df1c
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 26 deletions.
20 changes: 20 additions & 0 deletions messages/common.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ terminating app %s

error encountered

# playStoreNotWritableError

Play Store devices cannot be booted with writable system.

# playStoreNotWritableWarning

Play Store devices cannot be booted with writable system. Booting normally instead.

# bootingWritable

booting with system writable option

# notWritableSystemShutDownStatus

emulator currently launched without -writable-system. Shutting down.
Expand Down Expand Up @@ -82,6 +94,14 @@ rebooting for changes to take effect

remounting system partition to be writable

# certificateInstall

installing certificate on device

# adbRoot

running ADB as root

# openBrowserWithUrlStatus

Opening browser with url %s
Expand Down
3 changes: 2 additions & 1 deletion src/common/AndroidLauncher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { AndroidAppPreviewConfig } from './PreviewConfigFile.js';
import { CommonUtils } from './CommonUtils.js';
import { PreviewUtils } from './PreviewUtils.js';
import { LaunchArgument } from './device/BaseDevice.js';
import { BootMode } from './device/AndroidDevice.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/lwc-dev-mobile-core', 'common');
Expand Down Expand Up @@ -66,7 +67,7 @@ export class AndroidLauncher {
})
.then(() => {
CommonUtils.updateCliAction(messages.getMessage('startDeviceStatus', [emuName]));
return AndroidUtils.startEmulator(emuName, false, true, logger);
return AndroidUtils.startEmulator(emuName, BootMode.normal, undefined, undefined, logger);
})
.then((emulatorPort) => {
const useServer = PreviewUtils.useLwcServerForPreviewing(targetApp, appConfig);
Expand Down
36 changes: 20 additions & 16 deletions src/common/AndroidUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Version } from './Common.js';
import { CommonUtils } from './CommonUtils.js';
import { PlatformConfig } from './PlatformConfig.js';
import { LaunchArgument } from './device/BaseDevice.js';
import { BootMode } from './device/AndroidDevice.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/lwc-dev-mobile-core', 'common');
Expand Down Expand Up @@ -472,13 +473,15 @@ export class AndroidUtils {
* Attempts to launch an emulator and returns the ADB port that the emulator was launched on.
*
* @param emulatorName Name of the emulator to be launched (e.g Pixel XL, Nexus_6_API_30).
* @param writable Optional boolean indicating whether the emulator should launch with the '-writable-system' flag. Defaults to false.
* @param bootMode Optional enum indicating the boot mode. Defaults to Normal.
* @param coldBoot Optional boolean indicating whether we should perform a cold boot. Defaults to false.
* @param waitForBoot Optional boolean indicating whether it should wait for the device to finish booting up. Defaults to true.
* @returns The ADB port that the emulator was launched on.
*/
public static async startEmulator(
emulatorName: string,
writable = false,
bootMode = BootMode.normal,
coldBoot = false,
waitForBoot = true,
logger?: Logger
): Promise<number> {
Expand All @@ -497,21 +500,22 @@ export class AndroidUtils {
const resolvedPortNumber = port ? port : await AndroidUtils.getNextAvailableAdbPort(logger);

if (resolvedPortNumber === port) {
// already is running on a port
const isWritable = await AndroidUtils.isEmulatorSystemWritable(resolvedPortNumber, logger);
// Already booted and running on a port, so determine whether need to relaunch with system writable or not.
const isAlreadyWritable = await AndroidUtils.isEmulatorSystemWritable(resolvedPortNumber, logger);

if (writable === false || isWritable === true) {
// If we're not asked for a writable, or if it already
// is writable then we're done so just return its port.
// If it is already writable or it is not mandatory to have a writable system then we're done so just return its port.
if (isAlreadyWritable || bootMode !== BootMode.systemWritableMandatory) {
return Promise.resolve(resolvedPortNumber);
} else {
// It is mandatory to have writable system but the emulator is already booted without it.
// Shut it down and relaunch it in the right mode.
CommonUtils.updateCliAction(messages.getMessage('notWritableSystemShutDownStatus'));
await AndroidUtils.stopEmulator(resolvedPortNumber, true, logger);
}

// mismatch... shut it down and relaunch it in the right mode
CommonUtils.updateCliAction(messages.getMessage('notWritableSystemShutDownStatus'));
await AndroidUtils.stopEmulator(resolvedPortNumber, true, logger);
}

let msgKey = '';
const writable = bootMode !== BootMode.normal;
if (resolvedPortNumber === port) {
msgKey = writable ? 'emulatorRelaunchWritableStatus' : 'emulatorRelaunchNotWritableStatus';
} else {
Expand All @@ -524,10 +528,10 @@ export class AndroidUtils {
// spit out a bunch of output to stderr where they are not really errors. This
// is specially true on Windows platform. So instead we spawn the process to launch
// the emulator and later attempt at polling the emulator to see if it failed to boot.
const writableFlag = writable ? '-writable-system' : '';
const coldFlag = coldBoot ? '-no-snapshot-load' : '';
const child = childProcess.spawn(
`${AndroidUtils.getEmulatorCommand()} @${resolvedEmulatorName} -port ${resolvedPortNumber}${
writable ? ' -writable-system' : ''
}`,
`${AndroidUtils.getEmulatorCommand()} @${resolvedEmulatorName} -port ${resolvedPortNumber} ${writableFlag} ${coldFlag}`,
{ detached: true, shell: true, stdio: 'ignore' }
);
child.unref();
Expand All @@ -548,7 +552,7 @@ export class AndroidUtils {
* @param waitForPowerOff Optional boolean indicating whether it should wait for the device to shut down. Defaults to true.
*/
public static async stopEmulator(portNumber: number, waitForPowerOff = true, logger?: Logger): Promise<void> {
return AndroidUtils.executeAdbCommand('shell reboot -p', portNumber, logger).then(() => {
return AndroidUtils.executeAdbCommand('emu kill', portNumber, logger).then(() => {
if (waitForPowerOff) {
return AndroidUtils.waitUntilDeviceIsPoweredOff(portNumber, logger);
} else {
Expand Down Expand Up @@ -629,7 +633,7 @@ export class AndroidUtils {
// to see if it is also running with writable system already or not. If so then nothing will happen and startEmulator()
// will just return. Otherwise startEmulator() will power off the emulator first, then relaunch it with writable system,
// and finally wait for it to finish booting.
return AndroidUtils.startEmulator(emulatorName, true, true, logger)
return AndroidUtils.startEmulator(emulatorName, BootMode.systemWritableMandatory, true, true, logger)
.then((port) => {
portNumber = port;
// Now that emulator is launched with writable system, run root command
Expand Down
43 changes: 35 additions & 8 deletions src/common/device/AndroidDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { Logger, SfError } from '@salesforce/core';
import { Logger, Messages, SfError } from '@salesforce/core';
import { AndroidUtils } from '../AndroidUtils.js';
import { Version } from '../Common.js';
import { CryptoUtils, SSLCertificateData } from '../CryptoUtils.js';
import { CommonUtils } from '../CommonUtils.js';
import { BaseDevice, DeviceType, LaunchArgument } from './BaseDevice.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/lwc-dev-mobile-core', 'common');

export enum AndroidOSType {
googleAPIs = 'google_apis',
googlePlayStore = 'google_apis_playstore',
Expand All @@ -23,6 +27,12 @@ export enum AndroidOSType {
androidAutomotive = 'android-automotive'
}

export enum BootMode {
normal = 'normal',
systemWritablePreferred = 'systemWritablePreferred',
systemWritableMandatory = 'systemWritableMandatory'
}

export class AndroidDevice implements BaseDevice {
public logger?: Logger;
public readonly id: string;
Expand Down Expand Up @@ -67,14 +77,19 @@ export class AndroidDevice implements BaseDevice {
* Attempts to boot up the device.
*
* @param waitForBoot Optional boolean indicating whether to wait for the device to boot up. Defaults to true.
* @param systemWritable Optional boolean indicating whether the emulator should launch with the '-writable-system' flag. Defaults to false.
* @param bootMode Optional enum indicating the boot mode. Defaults to Normal.
* @param coldBoot Optional boolean indicating whether we should perform a cold boot. Defaults to false.
*/
public async boot(waitForBoot = true, systemWritable = false): Promise<void> {
if (systemWritable && this.isPlayStore) {
throw new SfError('Play Store devices cannot be booted with writable system.');
public async boot(waitForBoot = true, bootMode = BootMode.normal, coldBoot = false): Promise<void> {
if (this.isPlayStore) {
if (bootMode === BootMode.systemWritableMandatory) {
throw new SfError(messages.getMessage('playStoreNotWritableError'));
} else if (bootMode === BootMode.systemWritablePreferred) {
this.logger?.warn(messages.getMessage('playStoreNotWritableWarning'));
}
}

this.port = await AndroidUtils.startEmulator(this.id, systemWritable, waitForBoot, this.logger);
this.port = await AndroidUtils.startEmulator(this.id, bootMode, coldBoot, waitForBoot, this.logger);
}

/**
Expand All @@ -87,7 +102,7 @@ export class AndroidDevice implements BaseDevice {
// Has not been booted yet so instead of rebooting just start it up.
await this.boot(waitForBoot);
} else {
await AndroidUtils.rebootEmulator(this.port, waitForBoot);
await AndroidUtils.rebootEmulator(this.port, waitForBoot, this.logger);
}
}

Expand Down Expand Up @@ -188,7 +203,16 @@ export class AndroidDevice implements BaseDevice {
fs.writeFileSync(certFilePath, pemContent);

// We then need to push the file to the emulator (needs to be root-mountable).
await AndroidUtils.mountAsRootWritableSystem(this.id, this.logger); // boot with writable system access
CommonUtils.updateCliAction(messages.getMessage('bootingWritable'));
await this.boot(true, BootMode.systemWritableMandatory, false);

CommonUtils.updateCliAction(messages.getMessage('adbRoot'));
await AndroidUtils.executeAdbCommandWithRetry('root', this.port, undefined, undefined, this.logger);

CommonUtils.updateCliAction(messages.getMessage('remountSystemWritableStatus'));
await AndroidUtils.executeAdbCommandWithRetry('remount', this.port, undefined, undefined, this.logger);

CommonUtils.updateCliAction(messages.getMessage('certificateInstall'));
await AndroidUtils.executeAdbCommand(
`push ${certFilePath} /data/misc/user/0/cacerts-added/${fileName}`,
this.port,
Expand All @@ -199,5 +223,8 @@ export class AndroidDevice implements BaseDevice {
this.port,
this.logger
);

CommonUtils.updateCliAction(messages.getMessage('rebootChangesStatus'));
await this.reboot();
}
}
2 changes: 1 addition & 1 deletion test/unit/common/AndroidUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ describe('Android utils', () => {
stubMethod($$.SANDBOX, CommonUtils, 'executeCommandAsync').callsFake(myCommandRouterBlock);
stubMethod($$.SANDBOX, fs, 'readFileSync').returns('');

const port = await AndroidUtils.startEmulator(testAvdName, true);
const port = await AndroidUtils.startEmulator(testAvdName, undefined, undefined, true);
expect(port).to.be.equal(5572);
});

Expand Down

0 comments on commit 782df1c

Please sign in to comment.