microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
261063a17b169f05912ceccfb6abf10afc6e2c2b

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/androidPlatform.ts

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