microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
ca288f662978f0f1e343f4a0f098df8cbbfc6889

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/android/androidPlatform.ts

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