microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
514df4f4ef8788dfab32c26fd9d9c889f50a55ed

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/android/androidPlatform.ts

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