microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.17.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/android/androidPlatform.ts

266lines · modeblame

52f3873ddigeff10 years ago1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as Q from "q";
df8c800dArtem Egorov8 years ago5import * as semver from "semver";
52f3873ddigeff10 years ago6
5c8365a6Artem Egorov8 years ago7import {GeneralMobilePlatform, MobilePlatformDeps } from "../generalMobilePlatform";
0db0be15Artem Egorov8 years ago8import {IAndroidRunOptions} from "../launchArgs";
7daed3fcArtem Egorov8 years ago9import {AdbHelper, AndroidAPILevel, IDevice} from "./adb";
0a68f8dbArtem Egorov8 years ago10import {Package} from "../../common/node/package";
11import {PromiseUtil} from "../../common/node/promise";
5c8365a6Artem Egorov8 years ago12import {PackageNameResolver} from "./packageNameResolver";
0a68f8dbArtem Egorov8 years ago13import {OutputVerifier, PatternToFailure} from "../../common/outputVerifier";
14import {TelemetryHelper} from "../../common/telemetryHelper";
8022afdfVladimir Kotikov8 years ago15import {CommandExecutor} from "../../common/commandExecutor";
0a68f8dbArtem Egorov8 years ago16import {LogCatMonitor} from "./logCatMonitor";
d7d405aeYuri Skorokhodov7 years ago17import * as nls from "vscode-nls";
18import { InternalErrorCode } from "../../common/error/internalErrorCode";
19import { ErrorHelper } from "../../common/error/errorHelper";
78c2b4deRedMickey6 years ago20import { isNullOrUndefined } from "util";
d7d405aeYuri Skorokhodov7 years ago21const localize = nls.loadMessageBundle();
5c8365a6Artem Egorov8 years ago22
52f3873ddigeff10 years ago23/**
24* Android specific platform implementation for debugging RN applications.
25*/
299b0557Patricio Beltran10 years ago26export class AndroidPlatform extends GeneralMobilePlatform {
52f3873ddigeff10 years ago27
0a68f8dbArtem Egorov8 years ago28// We should add the common Android build/run errors we find to this list
ef902673Vladimir Kotikov9 years ago29private static RUN_ANDROID_FAILURE_PATTERNS: PatternToFailure[] = [{
30pattern: "Failed to install on any devices",
d7d405aeYuri Skorokhodov7 years ago31errorCode: InternalErrorCode.AndroidCouldNotInstallTheAppOnAnyAvailibleDevice,
ef902673Vladimir Kotikov9 years ago32}, {
33pattern: "com.android.ddmlib.ShellCommandUnresponsiveException",
d7d405aeYuri Skorokhodov7 years ago34errorCode: InternalErrorCode.AndroidShellCommandTimedOut,
ef902673Vladimir Kotikov9 years ago35}, {
36pattern: "Android project not found",
d7d405aeYuri Skorokhodov7 years ago37errorCode: InternalErrorCode.AndroidProjectNotFound,
ef902673Vladimir Kotikov9 years ago38
39}, {
40pattern: "error: more than one device/emulator",
d7d405aeYuri Skorokhodov7 years ago41errorCode: InternalErrorCode.AndroidMoreThanOneDeviceOrEmulator,
ef902673Vladimir Kotikov9 years ago42}, {
43pattern: /^Error: Activity class \{.*\} does not exist\.$/m,
d7d405aeYuri Skorokhodov7 years ago44errorCode: InternalErrorCode.AndroidFailedToLaunchTheSpecifiedActivity,
ef902673Vladimir Kotikov9 years ago45}];
52f3873ddigeff10 years ago46
47private static RUN_ANDROID_SUCCESS_PATTERNS: string[] = ["BUILD SUCCESSFUL", "Starting the app", "Starting: Intent"];
48
49private debugTarget: IDevice;
50private devices: IDevice[];
51private packageName: string;
0a68f8dbArtem Egorov8 years ago52private logCatMonitor: LogCatMonitor | null = null;
db6fd42aRuslan Bikkinin7 years ago53private adbHelper: AdbHelper;
52f3873ddigeff10 years ago54
55private needsToLaunchApps: boolean = false;
db6fd42aRuslan Bikkinin7 years ago56
57public showDevMenu(deviceId?: string): Q.Promise<void> {
58return this.adbHelper.showDevMenu(deviceId);
7daed3fcArtem Egorov8 years ago59}
db6fd42aRuslan Bikkinin7 years ago60
61public reloadApp(deviceId?: string): Q.Promise<void> {
62return this.adbHelper.reloadApp(deviceId);
7daed3fcArtem Egorov8 years ago63}
52f3873ddigeff10 years ago64
299b0557Patricio Beltran10 years ago65// We set remoteExtension = null so that if there is an instance of androidPlatform that wants to have it's custom remoteExtension it can. This is specifically useful for tests.
7daed3fcArtem Egorov8 years ago66constructor(protected runOptions: IAndroidRunOptions, platformDeps: MobilePlatformDeps = {}) {
0a68f8dbArtem Egorov8 years ago67super(runOptions, platformDeps);
db6fd42aRuslan Bikkinin7 years ago68this.adbHelper = new AdbHelper(this.runOptions.projectRoot, this.logger);
69}
43e1a996Ruslan Bikkinin7 years ago70
db6fd42aRuslan Bikkinin7 years ago71// TODO: remove this method when sinon will be updated to upper version. Now it is used for tests only.
72public setAdbHelper(adbHelper: AdbHelper) {
73this.adbHelper = adbHelper;
52f3873ddigeff10 years ago74}
75
76public runApp(shouldLaunchInAllDevices: boolean = false): Q.Promise<void> {
549baae2RedMickey6 years ago77let extProps: any = {
031832ffArtem Egorov8 years ago78platform: {
79value: "android",
80isPii: false,
81},
82};
83
549baae2RedMickey6 years ago84if (this.runOptions.isDirect) {
85extProps.isDirect = {
86value: true,
87isPii: false,
88};
89}
90
7fa90b3bRedMickey6 years ago91extProps = TelemetryHelper.addPropertyToTelemetryProperties(this.runOptions.reactNativeVersions.reactNativeVersion, "reactNativeVersion", extProps);
ba953e9fRedMickey6 years ago92
031832ffArtem Egorov8 years ago93return TelemetryHelper.generate("AndroidPlatform.runApp", extProps, () => {
5f0a4a46JiglioNero6 years ago94const env = GeneralMobilePlatform.getEnvArgument(process.env, this.runOptions.env, this.runOptions.envFile);
78c2b4deRedMickey6 years ago95
e3706a1cRedMickey6 years ago96if (
97!semver.valid(this.runOptions.reactNativeVersions.reactNativeVersion) /*Custom RN implementations should support this flag*/ ||
98semver.gte(this.runOptions.reactNativeVersions.reactNativeVersion, AndroidPlatform.NO_PACKAGER_VERSION)
99) {
100this.runArguments.push("--no-packager");
101}
102
103let mainActivity = GeneralMobilePlatform.getOptFromRunArgs(this.runArguments, "--main-activity");
104
105if (mainActivity) {
106this.adbHelper.setLaunchActivity(mainActivity);
107} else if (!isNullOrUndefined(this.runOptions.debugLaunchActivity)) {
108this.runArguments.push("--main-activity", this.runOptions.debugLaunchActivity);
109this.adbHelper.setLaunchActivity(this.runOptions.debugLaunchActivity);
110}
111
112const runAndroidSpawn = new CommandExecutor(this.projectPath, this.logger).spawnReactCommand("run-android", this.runArguments, {env});
113const output = new OutputVerifier(
114() =>
115Q(AndroidPlatform.RUN_ANDROID_SUCCESS_PATTERNS),
116() =>
117Q(AndroidPlatform.RUN_ANDROID_FAILURE_PATTERNS),
118"android").process(runAndroidSpawn);
119
120return output
121.finally(() => {
122return this.initializeTargetDevicesAndPackageName();
123}).then(() => [this.debugTarget], reason => {
124if (reason.message === ErrorHelper.getInternalError(InternalErrorCode.AndroidMoreThanOneDeviceOrEmulator).message && this.devices.length > 1 && this.debugTarget) {
125/* If it failed due to multiple devices, we'll apply this workaround to make it work anyways */
126this.needsToLaunchApps = true;
127return shouldLaunchInAllDevices
128? this.adbHelper.getOnlineDevices()
129: Q([this.debugTarget]);
130} else {
131return Q.reject<IDevice[]>(reason);
78c2b4deRedMickey6 years ago132}
e3706a1cRedMickey6 years ago133}).then(devices => {
134return new PromiseUtil().forEach(devices, device => {
135return this.launchAppWithADBReverseAndLogCat(device);
136});
52f3873ddigeff10 years ago137});
138});
139}
140
141public enableJSDebuggingMode(): Q.Promise<void> {
db6fd42aRuslan Bikkinin7 years ago142return this.adbHelper.switchDebugMode(this.runOptions.projectRoot, this.packageName, true, this.debugTarget.id);
b57ea017Artem Egorov8 years ago143}
144
145public disableJSDebuggingMode(): Q.Promise<void> {
db6fd42aRuslan Bikkinin7 years ago146return this.adbHelper.switchDebugMode(this.runOptions.projectRoot, this.packageName, false, this.debugTarget.id);
52f3873ddigeff10 years ago147}
148
299b0557Patricio Beltran10 years ago149public prewarmBundleCache(): Q.Promise<void> {
0a68f8dbArtem Egorov8 years ago150return this.packager.prewarmBundleCache("android");
299b0557Patricio Beltran10 years ago151}
152
cbc7ac5bArtem Egorov7 years ago153public getRunArguments(): string[] {
8022afdfVladimir Kotikov8 years ago154let runArguments: string[] = [];
155
156if (this.runOptions.runArguments && this.runOptions.runArguments.length > 0) {
157runArguments = this.runOptions.runArguments;
158} else {
159if (this.runOptions.variant) {
160runArguments.push("--variant", this.runOptions.variant);
161}
162if (this.runOptions.target) {
cbc7ac5bArtem Egorov7 years ago163if (this.runOptions.target === AndroidPlatform.simulatorString ||
164this.runOptions.target === AndroidPlatform.deviceString) {
165
d7d405aeYuri Skorokhodov7 years ago166const message = localize("TargetIsNotSupportedForAndroid",
58726f4dYuri Skorokhodov6 years ago167"Target {0} is not supported for Android platform. \n If you want to use particular device or simulator for launching Android app,\n please specify device id (as in 'adb devices' output) instead.",
d7d405aeYuri Skorokhodov7 years ago168this.runOptions.target);
cbc7ac5bArtem Egorov7 years ago169this.logger.warning(message);
170} else {
171runArguments.push("--deviceId", this.runOptions.target);
172}
8022afdfVladimir Kotikov8 years ago173}
174}
175
176return runArguments;
177}
178
52f3873ddigeff10 years ago179private initializeTargetDevicesAndPackageName(): Q.Promise<void> {
db6fd42aRuslan Bikkinin7 years ago180return this.adbHelper.getConnectedDevices().then(devices => {
52f3873ddigeff10 years ago181this.devices = devices;
182this.debugTarget = this.getTargetEmulator(devices);
183return this.getPackageName().then(packageName => {
184this.packageName = packageName;
185});
186});
187}
188
189private launchAppWithADBReverseAndLogCat(device: IDevice): Q.Promise<void> {
190return Q({})
191.then(() => {
192return this.configureADBReverseWhenApplicable(device);
193}).then(() => {
194return this.needsToLaunchApps
db6fd42aRuslan Bikkinin7 years ago195? this.adbHelper.launchApp(this.runOptions.projectRoot, this.packageName, device.id)
52f3873ddigeff10 years ago196: Q<void>(void 0);
197}).then(() => {
0a68f8dbArtem Egorov8 years ago198return this.startMonitoringLogCat(device, this.runOptions.logCatArguments);
52f3873ddigeff10 years ago199});
200}
201
202private configureADBReverseWhenApplicable(device: IDevice): Q.Promise<void> {
b57ea017Artem Egorov8 years ago203return Q({}) // For other emulators and devices we try to enable adb reverse
db6fd42aRuslan Bikkinin7 years ago204.then(() => this.adbHelper.apiVersion(device.id))
b57ea017Artem Egorov8 years ago205.then(apiVersion => {
206if (apiVersion >= AndroidAPILevel.LOLLIPOP) { // If we support adb reverse
db6fd42aRuslan Bikkinin7 years ago207return this.adbHelper.reverseAdb(device.id, Number(this.runOptions.packagerPort));
b57ea017Artem Egorov8 years ago208} else {
d7d405aeYuri Skorokhodov7 years ago209const message = localize("DeviceSupportsOnlyAPILevel",
210"Device {0} supports only API Level {1}. \n Level {2} is needed to support port forwarding via adb reverse. \n For debugging to work you'll need <Shake or press menu button> for the dev menu, \n go into <Dev Settings> and configure <Debug Server host & port for Device> to be \n an IP address of your computer that the Device can reach. More info at: \n https://facebook.github.io/react-native/docs/debugging.html#debugging-react-native-apps",
211device.id, apiVersion, AndroidAPILevel.LOLLIPOP);
212this.logger.warning(message);
b57ea017Artem Egorov8 years ago213return void 0;
214}
215});
52f3873ddigeff10 years ago216}
217
218private getPackageName(): Q.Promise<string> {
8022afdfVladimir Kotikov8 years ago219return new Package(this.runOptions.projectRoot).name().then(appName =>
52f3873ddigeff10 years ago220new PackageNameResolver(appName).resolvePackageName(this.runOptions.projectRoot));
221}
222
223/**
224* Returns the target emulator, using the following logic:
225* * If an emulator is specified and it is connected, use that one.
226* * Otherwise, use the first one in the list.
227*/
228private getTargetEmulator(devices: IDevice[]): IDevice {
229let activeFilterFunction = (device: IDevice) => {
230return device.isOnline;
231};
232
233let targetFilterFunction = (device: IDevice) => {
234return device.id === this.runOptions.target && activeFilterFunction(device);
235};
236
237if (this.runOptions && this.runOptions.target && devices) {
238/* check if the specified target is active */
239const targetDevice = devices.find(targetFilterFunction);
240if (targetDevice) {
241return targetDevice;
242}
243}
244
245/* return the first active device in the list */
246let activeDevices = devices && devices.filter(activeFilterFunction);
247return activeDevices && activeDevices[0];
248}
249
0a68f8dbArtem Egorov8 years ago250private startMonitoringLogCat(device: IDevice, logCatArguments: string): void {
251this.stopMonitoringLogCat(); // Stop previous logcat monitor if it's running
252
253// this.logCatMonitor can be mutated, so we store it locally too
db6fd42aRuslan Bikkinin7 years ago254this.logCatMonitor = new LogCatMonitor(device.id, logCatArguments, this.adbHelper);
0a68f8dbArtem Egorov8 years ago255this.logCatMonitor.start() // The LogCat will continue running forever, so we don't wait for it
d7d405aeYuri Skorokhodov7 years ago256.catch(error => this.logger.warning(localize("ErrorWhileMonitoringLogCat", "Error while monitoring LogCat"), error)) // The LogCatMonitor failing won't stop the debugging experience
0a68f8dbArtem Egorov8 years ago257.done();
258}
259
260private stopMonitoringLogCat(): void {
261if (this.logCatMonitor) {
262this.logCatMonitor.dispose();
263this.logCatMonitor = null;
264}
52f3873ddigeff10 years ago265}
ef902673Vladimir Kotikov9 years ago266}