microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1ca47c7c2d73a604d8f110cdbad1aad4bbdddce6

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/android/androidPlatform.ts

215lines · 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";
5
6import {GeneralMobilePlatform, MobilePlatformDeps } from "../generalMobilePlatform";
7import {Packager} from "../packager";
8import {IAndroidRunOptions} from "../launchArgs";
9import {Log} from "../log/log";
10import {IAdb, Adb, AndroidAPILevel, IDevice, DeviceType} from "./adb";
11import {Package} from "../node/package";
12import {PromiseUtil} from "../node/promise";
13import {PackageNameResolver} from "./packageNameResolver";
14import {OutputVerifier, PatternToFailure} from "../outputVerifier";
15import {TelemetryHelper} from "../telemetryHelper";
16import {CommandExecutor} from "../../common/commandExecutor";
17
18export interface AndroidPlatformDeps extends MobilePlatformDeps {
19 adb?: IAdb;
20}
21/**
22 * Android specific platform implementation for debugging RN applications.
23 */
24export class AndroidPlatform extends GeneralMobilePlatform {
25 private static MULTIPLE_DEVICES_ERROR = "error: more than one device/emulator";
26
27 // We should add the common Android build/run erros we find to this list
28 private static RUN_ANDROID_FAILURE_PATTERNS: PatternToFailure[] = [{
29 pattern: "Failed to install on any devices",
30 message: "Could not install the app on any available device. Make sure you have a correctly"
31 + " configured device or emulator running. See https://facebook.github.io/react-native/docs/android-setup.html",
32 }, {
33 pattern: "com.android.ddmlib.ShellCommandUnresponsiveException",
34 message: "An Android shell command timed-out. Please retry the operation.",
35 }, {
36 pattern: "Android project not found",
37 message: "Android project not found.",
38
39 }, {
40 pattern: "error: more than one device/emulator",
41 message: AndroidPlatform.MULTIPLE_DEVICES_ERROR,
42 }, {
43 pattern: /^Error: Activity class \{.*\} does not exist\.$/m,
44 message: "Failed to launch the specified activity. Try running application manually and "
45 + "start debugging using 'Attach to packager' launch configuration.",
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 adb: IAdb;
54
55 private needsToLaunchApps: boolean = false;
56
57 // 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.
58 constructor(protected runOptions: IAndroidRunOptions, {
59 remoteExtension,
60 adb = <IAdb>new Adb(),
61 }: AndroidPlatformDeps = {}) {
62 super(runOptions, { remoteExtension: remoteExtension });
63 this.adb = adb;
64
65 if (this.runOptions.target === AndroidPlatform.simulatorString ||
66 this.runOptions.target === AndroidPlatform.deviceString) {
67
68 const message = `Target ${this.runOptions.target} is not supported for Android ` +
69 "platform. If you want to use particular device or simulator for launching " +
70 "Android app, please specify device id (as in 'adb devices' output) instead.";
71
72 Log.logMessage(message);
73 delete this.runOptions.target;
74 }
75 }
76
77 public runApp(shouldLaunchInAllDevices: boolean = false): Q.Promise<void> {
78 return TelemetryHelper.generate("AndroidPlatform.runApp", () => {
79 const runArguments = this.getRunArgument();
80 const runAndroidSpawn = new CommandExecutor(this.projectPath).spawnReactCommand("run-android", runArguments);
81
82 const output = new OutputVerifier(
83 () =>
84 Q(AndroidPlatform.RUN_ANDROID_SUCCESS_PATTERNS),
85 () =>
86 Q(AndroidPlatform.RUN_ANDROID_FAILURE_PATTERNS)).process(runAndroidSpawn);
87
88 return output
89 .finally(() => {
90 return this.initializeTargetDevicesAndPackageName();
91 }).then(() => [this.debugTarget], reason => {
92 if (reason.message === AndroidPlatform.MULTIPLE_DEVICES_ERROR && this.devices.length > 1 && this.debugTarget) {
93 /* If it failed due to multiple devices, we'll apply this workaround to make it work anyways */
94 this.needsToLaunchApps = true;
95 return shouldLaunchInAllDevices
96 ? this.adb.getOnlineDevices()
97 : Q([this.debugTarget]);
98 } else {
99 return Q.reject<IDevice[]>(reason);
100 }
101 }).then(devices => {
102 return new PromiseUtil().forEach(devices, device => {
103 return this.launchAppWithADBReverseAndLogCat(device);
104 });
105 });
106 });
107 }
108
109 public enableJSDebuggingMode(): Q.Promise<void> {
110 return this.adb.reloadAppInDebugMode(this.runOptions.projectRoot, this.packageName, this.debugTarget.id);
111 }
112
113 public prewarmBundleCache(): Q.Promise<void> {
114 return this.remoteExtension.prewarmBundleCache(this.platformName);
115 }
116
117 public getRunArgument(): string[] {
118 let runArguments: string[] = [];
119
120 if (this.runOptions.runArguments && this.runOptions.runArguments.length > 0) {
121 runArguments = this.runOptions.runArguments;
122 } else {
123 if (this.runOptions.variant) {
124 runArguments.push("--variant", this.runOptions.variant);
125 }
126 if (this.runOptions.target) {
127 runArguments.push("--deviceId", this.runOptions.target);
128 }
129 }
130
131 return runArguments;
132 }
133
134 private initializeTargetDevicesAndPackageName(): Q.Promise<void> {
135 return this.adb.getConnectedDevices().then(devices => {
136 this.devices = devices;
137 this.debugTarget = this.getTargetEmulator(devices);
138 return this.getPackageName().then(packageName => {
139 this.packageName = packageName;
140 });
141 });
142 }
143
144 private launchAppWithADBReverseAndLogCat(device: IDevice): Q.Promise<void> {
145 return Q({})
146 .then(() => {
147 return this.configureADBReverseWhenApplicable(device);
148 }).then(() => {
149 return this.needsToLaunchApps
150 ? this.adb.launchApp(this.runOptions.projectRoot, this.packageName, device.id)
151 : Q<void>(void 0);
152 }).then(() => {
153 return this.startMonitoringLogCat(device, this.runOptions.logCatArguments).catch(error => // The LogCatMonitor failing won't stop the debugging experience
154 Log.logWarning("Couldn't start LogCat monitor", error));
155 });
156 }
157
158 private configureADBReverseWhenApplicable(device: IDevice): Q.Promise<void> {
159 if (device.type !== DeviceType.AndroidSdkEmulator) {
160 return Q({}) // For other emulators and devices we try to enable adb reverse
161 .then(() => this.adb.apiVersion(device.id))
162 .then(apiVersion => {
163 if (apiVersion >= AndroidAPILevel.LOLLIPOP) { // If we support adb reverse
164 return this.adb.reverseAdd(device.id, Packager.DEFAULT_PORT.toString(), this.runOptions.packagerPort);
165 } else {
166 Log.logWarning(`Device ${device.id} supports only API Level ${apiVersion}. `
167 + `Level ${AndroidAPILevel.LOLLIPOP} is needed to support port forwarding via adb reverse. `
168 + "For debugging to work you'll need <Shake or press menu button> for the dev menu, "
169 + "go into <Dev Settings> and configure <Debug Server host & port for Device> to be "
170 + "an IP address of your computer that the Device can reach. More info at: "
171 + "https://facebook.github.io/react-native/docs/debugging.html#debugging-react-native-apps");
172 return void 0;
173 }
174 });
175 } else {
176 return Q<void>(void 0); // Android SDK emulators can connect directly to 10.0.0.2, so they don't need port forwarding
177 }
178 }
179
180 private getPackageName(): Q.Promise<string> {
181 return new Package(this.runOptions.projectRoot).name().then(appName =>
182 new PackageNameResolver(appName).resolvePackageName(this.runOptions.projectRoot));
183 }
184
185 /**
186 * Returns the target emulator, using the following logic:
187 * * If an emulator is specified and it is connected, use that one.
188 * * Otherwise, use the first one in the list.
189 */
190 private getTargetEmulator(devices: IDevice[]): IDevice {
191 let activeFilterFunction = (device: IDevice) => {
192 return device.isOnline;
193 };
194
195 let targetFilterFunction = (device: IDevice) => {
196 return device.id === this.runOptions.target && activeFilterFunction(device);
197 };
198
199 if (this.runOptions && this.runOptions.target && devices) {
200 /* check if the specified target is active */
201 const targetDevice = devices.find(targetFilterFunction);
202 if (targetDevice) {
203 return targetDevice;
204 }
205 }
206
207 /* return the first active device in the list */
208 let activeDevices = devices && devices.filter(activeFilterFunction);
209 return activeDevices && activeDevices[0];
210 }
211
212 private startMonitoringLogCat(device: IDevice, logCatArguments: string): Q.Promise<void> {
213 return this.remoteExtension.startMonitoringLogcat(device.id, logCatArguments);
214 }
215}
216