microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.11.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/androidPlatform.ts

258lines · modecode

1// 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";
5import * as semver from "semver";
6
7import {GeneralMobilePlatform, MobilePlatformDeps } from "../generalMobilePlatform";
8import {IAndroidRunOptions} from "../launchArgs";
9import {AdbHelper, AndroidAPILevel, IDevice} from "./adb";
10import {Package} from "../../common/node/package";
11import {PromiseUtil} from "../../common/node/promise";
12import {PackageNameResolver} from "./packageNameResolver";
13import {OutputVerifier, PatternToFailure} from "../../common/outputVerifier";
14import {TelemetryHelper} from "../../common/telemetryHelper";
15import {CommandExecutor} from "../../common/commandExecutor";
16import {LogCatMonitor} from "./logCatMonitor";
17import {ReactNativeProjectHelper} from "../../common/reactNativeProjectHelper";
18import * as nls from "vscode-nls";
19import { InternalErrorCode } from "../../common/error/internalErrorCode";
20import { ErrorHelper } from "../../common/error/errorHelper";
21import { isNullOrUndefined } from "util";
22const localize = nls.loadMessageBundle();
23
24/**
25 * Android specific platform implementation for debugging RN applications.
26 */
27export class AndroidPlatform extends GeneralMobilePlatform {
28
29 // We should add the common Android build/run errors we find to this list
30 private static RUN_ANDROID_FAILURE_PATTERNS: PatternToFailure[] = [{
31 pattern: "Failed to install on any devices",
32 errorCode: InternalErrorCode.AndroidCouldNotInstallTheAppOnAnyAvailibleDevice,
33 }, {
34 pattern: "com.android.ddmlib.ShellCommandUnresponsiveException",
35 errorCode: InternalErrorCode.AndroidShellCommandTimedOut,
36 }, {
37 pattern: "Android project not found",
38 errorCode: InternalErrorCode.AndroidProjectNotFound,
39
40 }, {
41 pattern: "error: more than one device/emulator",
42 errorCode: InternalErrorCode.AndroidMoreThanOneDeviceOrEmulator,
43 }, {
44 pattern: /^Error: Activity class \{.*\} does not exist\.$/m,
45 errorCode: InternalErrorCode.AndroidFailedToLaunchTheSpecifiedActivity,
46 }];
47
48 private static RUN_ANDROID_SUCCESS_PATTERNS: string[] = ["BUILD SUCCESSFUL", "Starting the app", "Starting: Intent"];
49
50 private debugTarget: IDevice;
51 private devices: IDevice[];
52 private packageName: string;
53 private logCatMonitor: LogCatMonitor | null = null;
54 private adbHelper: AdbHelper;
55
56 private needsToLaunchApps: boolean = false;
57
58 public showDevMenu(deviceId?: string): Q.Promise<void> {
59 return this.adbHelper.showDevMenu(deviceId);
60 }
61
62 public reloadApp(deviceId?: string): Q.Promise<void> {
63 return this.adbHelper.reloadApp(deviceId);
64 }
65
66 // 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.
67 constructor(protected runOptions: IAndroidRunOptions, platformDeps: MobilePlatformDeps = {}) {
68 super(runOptions, platformDeps);
69 this.adbHelper = new AdbHelper(this.runOptions.projectRoot, this.logger);
70 }
71
72 // TODO: remove this method when sinon will be updated to upper version. Now it is used for tests only.
73 public setAdbHelper(adbHelper: AdbHelper) {
74 this.adbHelper = adbHelper;
75 }
76
77 public runApp(shouldLaunchInAllDevices: boolean = false): Q.Promise<void> {
78 const extProps = {
79 platform: {
80 value: "android",
81 isPii: false,
82 },
83 };
84
85 return TelemetryHelper.generate("AndroidPlatform.runApp", extProps, () => {
86 const env = this.getEnvArgument();
87
88 return ReactNativeProjectHelper.getReactNativeVersion(this.runOptions.projectRoot)
89 .then(version => {
90 if (!semver.valid(version) /*Custom RN implementations should support this flag*/ || semver.gte(version, AndroidPlatform.NO_PACKAGER_VERSION)) {
91 this.runArguments.push("--no-packager");
92 }
93
94 let mainActivity = GeneralMobilePlatform.getOptFromRunArgs(this.runArguments, "--main-activity");
95
96 if (mainActivity) {
97 this.adbHelper.setLaunchActivity(mainActivity);
98 } else if (!isNullOrUndefined(this.runOptions.debugLaunchActivity)) {
99 this.runArguments.push("--main-activity", this.runOptions.debugLaunchActivity);
100 this.adbHelper.setLaunchActivity(this.runOptions.debugLaunchActivity);
101 }
102
103 const runAndroidSpawn = new CommandExecutor(this.projectPath, this.logger).spawnReactCommand("run-android", this.runArguments, {env});
104 const output = new OutputVerifier(
105 () =>
106 Q(AndroidPlatform.RUN_ANDROID_SUCCESS_PATTERNS),
107 () =>
108 Q(AndroidPlatform.RUN_ANDROID_FAILURE_PATTERNS),
109 "android").process(runAndroidSpawn);
110
111 return output
112 .finally(() => {
113 return this.initializeTargetDevicesAndPackageName();
114 }).then(() => [this.debugTarget], reason => {
115 if (reason.message === ErrorHelper.getInternalError(InternalErrorCode.AndroidMoreThanOneDeviceOrEmulator).message && this.devices.length > 1 && this.debugTarget) {
116 /* If it failed due to multiple devices, we'll apply this workaround to make it work anyways */
117 this.needsToLaunchApps = true;
118 return shouldLaunchInAllDevices
119 ? this.adbHelper.getOnlineDevices()
120 : Q([this.debugTarget]);
121 } else {
122 return Q.reject<IDevice[]>(reason);
123 }
124 }).then(devices => {
125 return new PromiseUtil().forEach(devices, device => {
126 return this.launchAppWithADBReverseAndLogCat(device);
127 });
128 });
129 });
130 });
131 }
132
133 public enableJSDebuggingMode(): Q.Promise<void> {
134 return this.adbHelper.switchDebugMode(this.runOptions.projectRoot, this.packageName, true, this.debugTarget.id);
135 }
136
137 public disableJSDebuggingMode(): Q.Promise<void> {
138 return this.adbHelper.switchDebugMode(this.runOptions.projectRoot, this.packageName, false, this.debugTarget.id);
139 }
140
141 public prewarmBundleCache(): Q.Promise<void> {
142 return this.packager.prewarmBundleCache("android");
143 }
144
145 public getRunArguments(): string[] {
146 let runArguments: string[] = [];
147
148 if (this.runOptions.runArguments && this.runOptions.runArguments.length > 0) {
149 runArguments = this.runOptions.runArguments;
150 } else {
151 if (this.runOptions.variant) {
152 runArguments.push("--variant", this.runOptions.variant);
153 }
154 if (this.runOptions.target) {
155 if (this.runOptions.target === AndroidPlatform.simulatorString ||
156 this.runOptions.target === AndroidPlatform.deviceString) {
157
158 const message = localize("TargetIsNotSupportedForAndroid",
159 "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.",
160 this.runOptions.target);
161 this.logger.warning(message);
162 } else {
163 runArguments.push("--deviceId", this.runOptions.target);
164 }
165 }
166 }
167
168 return runArguments;
169 }
170
171 private initializeTargetDevicesAndPackageName(): Q.Promise<void> {
172 return this.adbHelper.getConnectedDevices().then(devices => {
173 this.devices = devices;
174 this.debugTarget = this.getTargetEmulator(devices);
175 return this.getPackageName().then(packageName => {
176 this.packageName = packageName;
177 });
178 });
179 }
180
181 private launchAppWithADBReverseAndLogCat(device: IDevice): Q.Promise<void> {
182 return Q({})
183 .then(() => {
184 return this.configureADBReverseWhenApplicable(device);
185 }).then(() => {
186 return this.needsToLaunchApps
187 ? this.adbHelper.launchApp(this.runOptions.projectRoot, this.packageName, device.id)
188 : Q<void>(void 0);
189 }).then(() => {
190 return this.startMonitoringLogCat(device, this.runOptions.logCatArguments);
191 });
192 }
193
194 private configureADBReverseWhenApplicable(device: IDevice): Q.Promise<void> {
195 return Q({}) // For other emulators and devices we try to enable adb reverse
196 .then(() => this.adbHelper.apiVersion(device.id))
197 .then(apiVersion => {
198 if (apiVersion >= AndroidAPILevel.LOLLIPOP) { // If we support adb reverse
199 return this.adbHelper.reverseAdb(device.id, Number(this.runOptions.packagerPort));
200 } else {
201 const message = localize("DeviceSupportsOnlyAPILevel",
202 "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",
203 device.id, apiVersion, AndroidAPILevel.LOLLIPOP);
204 this.logger.warning(message);
205 return void 0;
206 }
207 });
208 }
209
210 private getPackageName(): Q.Promise<string> {
211 return new Package(this.runOptions.projectRoot).name().then(appName =>
212 new PackageNameResolver(appName).resolvePackageName(this.runOptions.projectRoot));
213 }
214
215 /**
216 * Returns the target emulator, using the following logic:
217 * * If an emulator is specified and it is connected, use that one.
218 * * Otherwise, use the first one in the list.
219 */
220 private getTargetEmulator(devices: IDevice[]): IDevice {
221 let activeFilterFunction = (device: IDevice) => {
222 return device.isOnline;
223 };
224
225 let targetFilterFunction = (device: IDevice) => {
226 return device.id === this.runOptions.target && activeFilterFunction(device);
227 };
228
229 if (this.runOptions && this.runOptions.target && devices) {
230 /* check if the specified target is active */
231 const targetDevice = devices.find(targetFilterFunction);
232 if (targetDevice) {
233 return targetDevice;
234 }
235 }
236
237 /* return the first active device in the list */
238 let activeDevices = devices && devices.filter(activeFilterFunction);
239 return activeDevices && activeDevices[0];
240 }
241
242 private startMonitoringLogCat(device: IDevice, logCatArguments: string): void {
243 this.stopMonitoringLogCat(); // Stop previous logcat monitor if it's running
244
245 // this.logCatMonitor can be mutated, so we store it locally too
246 this.logCatMonitor = new LogCatMonitor(device.id, logCatArguments, this.adbHelper);
247 this.logCatMonitor.start() // The LogCat will continue running forever, so we don't wait for it
248 .catch(error => this.logger.warning(localize("ErrorWhileMonitoringLogCat", "Error while monitoring LogCat"), error)) // The LogCatMonitor failing won't stop the debugging experience
249 .done();
250 }
251
252 private stopMonitoringLogCat(): void {
253 if (this.logCatMonitor) {
254 this.logCatMonitor.dispose();
255 this.logCatMonitor = null;
256 }
257 }
258}
259