microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
66b52ec91bf71c509ff18c0c35c78a126d7f8dc0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/android/androidPlatform.ts

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