microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
efbe1ba61ca71887b3fa9a2d1ae3afb9a78cb87e

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/androidPlatform.ts

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