microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.13.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/androidPlatform.ts

266lines · 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 let extProps: any = {
79 platform: {
80 value: "android",
81 isPii: false,
82 },
83 };
84
85 if (this.runOptions.isDirect) {
86 extProps.isDirect = {
87 value: true,
88 isPii: false,
89 };
90 }
91
92 extProps = TelemetryHelper.addReactNativeVersionToEventProperties(this.runOptions.reactNativeVersion, extProps);
93
94 return TelemetryHelper.generate("AndroidPlatform.runApp", extProps, () => {
95 const env = this.getEnvArgument();
96 return ReactNativeProjectHelper.getReactNativeVersion(this.runOptions.projectRoot)
97 .then(version => {
98 if (!semver.valid(version) /*Custom RN implementations should support this flag*/ || semver.gte(version, AndroidPlatform.NO_PACKAGER_VERSION)) {
99 this.runArguments.push("--no-packager");
100 }
101
102 let mainActivity = GeneralMobilePlatform.getOptFromRunArgs(this.runArguments, "--main-activity");
103
104 if (mainActivity) {
105 this.adbHelper.setLaunchActivity(mainActivity);
106 } else if (!isNullOrUndefined(this.runOptions.debugLaunchActivity)) {
107 this.runArguments.push("--main-activity", this.runOptions.debugLaunchActivity);
108 this.adbHelper.setLaunchActivity(this.runOptions.debugLaunchActivity);
109 }
110
111 const runAndroidSpawn = new CommandExecutor(this.projectPath, this.logger).spawnReactCommand("run-android", this.runArguments, {env});
112 const output = new OutputVerifier(
113 () =>
114 Q(AndroidPlatform.RUN_ANDROID_SUCCESS_PATTERNS),
115 () =>
116 Q(AndroidPlatform.RUN_ANDROID_FAILURE_PATTERNS),
117 "android").process(runAndroidSpawn);
118
119 return output
120 .finally(() => {
121 return this.initializeTargetDevicesAndPackageName();
122 }).then(() => [this.debugTarget], reason => {
123 if (reason.message === ErrorHelper.getInternalError(InternalErrorCode.AndroidMoreThanOneDeviceOrEmulator).message && this.devices.length > 1 && this.debugTarget) {
124 /* If it failed due to multiple devices, we'll apply this workaround to make it work anyways */
125 this.needsToLaunchApps = true;
126 return shouldLaunchInAllDevices
127 ? this.adbHelper.getOnlineDevices()
128 : Q([this.debugTarget]);
129 } else {
130 return Q.reject<IDevice[]>(reason);
131 }
132 }).then(devices => {
133 return new PromiseUtil().forEach(devices, device => {
134 return this.launchAppWithADBReverseAndLogCat(device);
135 });
136 });
137 });
138 });
139 }
140
141 public enableJSDebuggingMode(): Q.Promise<void> {
142 return this.adbHelper.switchDebugMode(this.runOptions.projectRoot, this.packageName, true, this.debugTarget.id);
143 }
144
145 public disableJSDebuggingMode(): Q.Promise<void> {
146 return this.adbHelper.switchDebugMode(this.runOptions.projectRoot, this.packageName, false, this.debugTarget.id);
147 }
148
149 public prewarmBundleCache(): Q.Promise<void> {
150 return this.packager.prewarmBundleCache("android");
151 }
152
153 public getRunArguments(): string[] {
154 let runArguments: string[] = [];
155
156 if (this.runOptions.runArguments && this.runOptions.runArguments.length > 0) {
157 runArguments = this.runOptions.runArguments;
158 } else {
159 if (this.runOptions.variant) {
160 runArguments.push("--variant", this.runOptions.variant);
161 }
162 if (this.runOptions.target) {
163 if (this.runOptions.target === AndroidPlatform.simulatorString ||
164 this.runOptions.target === AndroidPlatform.deviceString) {
165
166 const message = localize("TargetIsNotSupportedForAndroid",
167 "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.",
168 this.runOptions.target);
169 this.logger.warning(message);
170 } else {
171 runArguments.push("--deviceId", this.runOptions.target);
172 }
173 }
174 }
175
176 return runArguments;
177 }
178
179 private initializeTargetDevicesAndPackageName(): Q.Promise<void> {
180 return this.adbHelper.getConnectedDevices().then(devices => {
181 this.devices = devices;
182 this.debugTarget = this.getTargetEmulator(devices);
183 return this.getPackageName().then(packageName => {
184 this.packageName = packageName;
185 });
186 });
187 }
188
189 private launchAppWithADBReverseAndLogCat(device: IDevice): Q.Promise<void> {
190 return Q({})
191 .then(() => {
192 return this.configureADBReverseWhenApplicable(device);
193 }).then(() => {
194 return this.needsToLaunchApps
195 ? this.adbHelper.launchApp(this.runOptions.projectRoot, this.packageName, device.id)
196 : Q<void>(void 0);
197 }).then(() => {
198 return this.startMonitoringLogCat(device, this.runOptions.logCatArguments);
199 });
200 }
201
202 private configureADBReverseWhenApplicable(device: IDevice): Q.Promise<void> {
203 return Q({}) // For other emulators and devices we try to enable adb reverse
204 .then(() => this.adbHelper.apiVersion(device.id))
205 .then(apiVersion => {
206 if (apiVersion >= AndroidAPILevel.LOLLIPOP) { // If we support adb reverse
207 return this.adbHelper.reverseAdb(device.id, Number(this.runOptions.packagerPort));
208 } else {
209 const 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",
211 device.id, apiVersion, AndroidAPILevel.LOLLIPOP);
212 this.logger.warning(message);
213 return void 0;
214 }
215 });
216 }
217
218 private getPackageName(): Q.Promise<string> {
219 return new Package(this.runOptions.projectRoot).name().then(appName =>
220 new 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 */
228 private getTargetEmulator(devices: IDevice[]): IDevice {
229 let activeFilterFunction = (device: IDevice) => {
230 return device.isOnline;
231 };
232
233 let targetFilterFunction = (device: IDevice) => {
234 return device.id === this.runOptions.target && activeFilterFunction(device);
235 };
236
237 if (this.runOptions && this.runOptions.target && devices) {
238 /* check if the specified target is active */
239 const targetDevice = devices.find(targetFilterFunction);
240 if (targetDevice) {
241 return targetDevice;
242 }
243 }
244
245 /* return the first active device in the list */
246 let activeDevices = devices && devices.filter(activeFilterFunction);
247 return activeDevices && activeDevices[0];
248 }
249
250 private startMonitoringLogCat(device: IDevice, logCatArguments: string): void {
251 this.stopMonitoringLogCat(); // Stop previous logcat monitor if it's running
252
253 // this.logCatMonitor can be mutated, so we store it locally too
254 this.logCatMonitor = new LogCatMonitor(device.id, logCatArguments, this.adbHelper);
255 this.logCatMonitor.start() // The LogCat will continue running forever, so we don't wait for it
256 .catch(error => this.logger.warning(localize("ErrorWhileMonitoringLogCat", "Error while monitoring LogCat"), error)) // The LogCatMonitor failing won't stop the debugging experience
257 .done();
258 }
259
260 private stopMonitoringLogCat(): void {
261 if (this.logCatMonitor) {
262 this.logCatMonitor.dispose();
263 this.logCatMonitor = null;
264 }
265 }
266}
267