microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.5.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

314lines · 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 { ChildProcess, ISpawnResult } from "../../common/node/childProcess";
5import { CommandExecutor } from "../../common/commandExecutor";
6import { IDevice } from "../../common/device";
7import * as path from "path";
8import * as fs from "fs";
9import { ILogger } from "../log/LogHelper";
10import * as os from "os";
11import * as nls from "vscode-nls";
12nls.config({
13 messageFormat: nls.MessageFormat.bundle,
14 bundleFormat: nls.BundleFormat.standalone,
15})();
16const localize = nls.loadMessageBundle();
17
18// See android versions usage at: http://developer.android.com/about/dashboards/index.html
19export enum AndroidAPILevel {
20 Marshmallow = 23,
21 LOLLIPOP_MR1 = 22,
22 LOLLIPOP = 21 /* Supports adb reverse */,
23 KITKAT = 19,
24 JELLY_BEAN_MR2 = 18,
25 JELLY_BEAN_MR1 = 17,
26 JELLY_BEAN = 16,
27 ICE_CREAM_SANDWICH_MR1 = 15,
28 GINGERBREAD_MR1 = 10,
29}
30
31enum KeyEvents {
32 KEYCODE_BACK = 4,
33 KEYCODE_DPAD_UP = 19,
34 KEYCODE_DPAD_DOWN = 20,
35 KEYCODE_DPAD_CENTER = 23,
36 KEYCODE_MENU = 82,
37}
38
39export enum AdbDeviceType {
40 AndroidSdkEmulator, // These seem to have emulator-<port> ids
41 RemoteDevice,
42 Other,
43}
44
45export interface IAdbDevice extends IDevice {
46 isOnline: boolean;
47 type: AdbDeviceType;
48}
49
50interface AdbDeviceTypePattern {
51 pattern: RegExp;
52 adbDeviceType: AdbDeviceType;
53}
54
55export class AdbHelper {
56 private nodeModulesRoot: string;
57 private launchActivity: string;
58 private childProcess: ChildProcess = new ChildProcess();
59 private commandExecutor: CommandExecutor;
60 private adbExecutable: string = "";
61
62 private static ADB_DEVICE_TYPE_PATTERNS: AdbDeviceTypePattern[] = [
63 {
64 pattern: /^emulator-\d{1,5}$/gm,
65 adbDeviceType: AdbDeviceType.AndroidSdkEmulator,
66 },
67 {
68 pattern: /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}|.*_adb-tls-connect\._tcp.*)$/gm,
69 adbDeviceType: AdbDeviceType.RemoteDevice,
70 },
71 ];
72
73 constructor(
74 projectRoot: string,
75 nodeModulesRoot: string,
76 logger?: ILogger,
77 launchActivity: string = "MainActivity",
78 ) {
79 this.nodeModulesRoot = nodeModulesRoot;
80 this.adbExecutable = this.getAdbPath(projectRoot, logger);
81 this.commandExecutor = new CommandExecutor(this.nodeModulesRoot);
82 this.launchActivity = launchActivity;
83 }
84
85 /**
86 * Gets the list of Android connected devices and emulators.
87 */
88 public getConnectedDevices(): Promise<IAdbDevice[]> {
89 return this.childProcess.execToString(`${this.adbExecutable} devices`).then(output => {
90 return this.parseConnectedDevices(output);
91 });
92 }
93
94 public setLaunchActivity(launchActivity: string): void {
95 this.launchActivity = launchActivity;
96 }
97
98 /**
99 * Broadcasts an intent to reload the application in debug mode.
100 */
101 public switchDebugMode(
102 projectRoot: string,
103 packageName: string,
104 enable: boolean,
105 debugTarget?: string,
106 appIdSuffix?: string,
107 ): Promise<void> {
108 let enableDebugCommand = `${this.adbExecutable} ${
109 debugTarget ? "-s " + debugTarget : ""
110 } shell am broadcast -a "${packageName}.RELOAD_APP_ACTION" --ez jsproxy ${enable}`;
111 return new CommandExecutor(this.nodeModulesRoot, projectRoot)
112 .execute(enableDebugCommand)
113 .then(() => {
114 // We should stop and start application again after RELOAD_APP_ACTION, otherwise app going to hangs up
115 return new Promise(resolve => {
116 setTimeout(() => {
117 this.stopApp(projectRoot, packageName, debugTarget, appIdSuffix).then(
118 () => {
119 return resolve(void 0);
120 },
121 );
122 }, 200); // We need a little delay after broadcast command
123 });
124 })
125 .then(() => {
126 return this.launchApp(projectRoot, packageName, debugTarget, appIdSuffix);
127 });
128 }
129
130 /**
131 * Sends an intent which launches the main activity of the application.
132 */
133 public launchApp(
134 projectRoot: string,
135 packageName: string,
136 debugTarget?: string,
137 appIdSuffix?: string,
138 ): Promise<void> {
139 let launchAppCommand = `${this.adbExecutable} ${
140 debugTarget ? "-s " + debugTarget : ""
141 } shell am start -n ${packageName}${appIdSuffix ? "." + appIdSuffix : ""}/${packageName}.${
142 this.launchActivity
143 }`;
144 return new CommandExecutor(projectRoot).execute(launchAppCommand);
145 }
146
147 public stopApp(
148 projectRoot: string,
149 packageName: string,
150 debugTarget?: string,
151 appIdSuffix?: string,
152 ): Promise<void> {
153 let stopAppCommand = `${this.adbExecutable} ${
154 debugTarget ? "-s " + debugTarget : ""
155 } shell am force-stop ${packageName}${appIdSuffix ? "." + appIdSuffix : ""}`;
156 return new CommandExecutor(projectRoot).execute(stopAppCommand);
157 }
158
159 public apiVersion(deviceId: string): Promise<AndroidAPILevel> {
160 return this.executeQuery(deviceId, "shell getprop ro.build.version.sdk").then(output =>
161 parseInt(output, 10),
162 );
163 }
164
165 public reverseAdb(deviceId: string, port: number): Promise<void> {
166 return this.execute(deviceId, `reverse tcp:${port} tcp:${port}`);
167 }
168
169 public showDevMenu(deviceId?: string): Promise<void> {
170 let command = `${this.adbExecutable} ${
171 deviceId ? "-s " + deviceId : ""
172 } shell input keyevent ${KeyEvents.KEYCODE_MENU}`;
173 return this.commandExecutor.execute(command);
174 }
175
176 public reloadApp(deviceId?: string): Promise<void> {
177 let command = `${this.adbExecutable} ${
178 deviceId ? "-s " + deviceId : ""
179 } shell input text "RR"`;
180 return this.commandExecutor.execute(command);
181 }
182
183 public getOnlineDevices(): Promise<IAdbDevice[]> {
184 return this.getConnectedDevices().then(devices => {
185 return devices.filter(device => device.isOnline);
186 });
187 }
188
189 public startLogCat(adbParameters: string[]): ISpawnResult {
190 return this.childProcess.spawn(this.adbExecutable.replace(/\"/g, ""), adbParameters);
191 }
192
193 public parseSdkLocation(fileContent: string, logger?: ILogger): string | null {
194 const matches = fileContent.match(/^sdk\.dir=(.+)$/m);
195 if (!matches || !matches[1]) {
196 if (logger) {
197 logger.info(
198 localize(
199 "NoSdkDirFoundInLocalPropertiesFile",
200 "No sdk.dir value found in local.properties file. Using Android SDK location from PATH.",
201 ),
202 );
203 }
204 return null;
205 }
206
207 let sdkLocation = matches[1].trim();
208 if (os.platform() === "win32") {
209 // For Windows we need to unescape files separators and drive letter separators
210 sdkLocation = sdkLocation.replace(/\\\\/g, "\\").replace("\\:", ":");
211 }
212 if (logger) {
213 logger.info(
214 localize(
215 "UsindAndroidSDKLocationDefinedInLocalPropertiesFile",
216 "Using Android SDK location defined in android/local.properties file: {0}.",
217 sdkLocation,
218 ),
219 );
220 }
221
222 return sdkLocation;
223 }
224
225 public getAdbPath(projectRoot: string, logger?: ILogger): string {
226 // Trying to read sdk location from local.properties file and if we succueded then
227 // we would run adb from inside it, otherwise we would rely to PATH
228 const sdkLocation = this.getSdkLocationFromLocalPropertiesFile(projectRoot, logger);
229 return sdkLocation ? `"${path.join(sdkLocation, "platform-tools", "adb")}"` : "adb";
230 }
231
232 public executeShellCommand(deviceId: string, command: string): Promise<string> {
233 return this.executeQuery(deviceId, `shell "${command}"`);
234 }
235
236 public executeQuery(deviceId: string, command: string): Promise<string> {
237 return this.childProcess.execToString(this.generateCommandForDevice(deviceId, command));
238 }
239
240 private parseConnectedDevices(input: string): IAdbDevice[] {
241 let result: IAdbDevice[] = [];
242 let regex = new RegExp("^(\\S+)\\t(\\S+)$", "mg");
243 let match = regex.exec(input);
244 while (match != null) {
245 result.push({
246 id: match[1],
247 isOnline: match[2] === "device",
248 type: this.extractDeviceType(match[1]),
249 });
250 match = regex.exec(input);
251 }
252 return result;
253 }
254
255 private extractDeviceType(id: string): AdbDeviceType {
256 for (let pattern of AdbHelper.ADB_DEVICE_TYPE_PATTERNS) {
257 if (id.match(pattern.pattern)) {
258 return pattern.adbDeviceType;
259 }
260 }
261 return AdbDeviceType.Other;
262 }
263
264 private execute(deviceId: string, command: string): Promise<void> {
265 return this.commandExecutor.execute(this.generateCommandForDevice(deviceId, command));
266 }
267
268 private generateCommandForDevice(deviceId: string, adbCommand: string): string {
269 return `${this.adbExecutable} -s "${deviceId}" ${adbCommand}`;
270 }
271
272 private getSdkLocationFromLocalPropertiesFile(
273 projectRoot: string,
274 logger?: ILogger,
275 ): string | null {
276 const localPropertiesFilePath = path.join(projectRoot, "android", "local.properties");
277 if (!fs.existsSync(localPropertiesFilePath)) {
278 if (logger) {
279 logger.info(
280 localize(
281 "LocalPropertiesFileDoesNotExist",
282 "local.properties file doesn't exist. Using Android SDK location from PATH.",
283 ),
284 );
285 }
286 return null;
287 }
288
289 let fileContent: string;
290 try {
291 fileContent = fs.readFileSync(localPropertiesFilePath).toString();
292 } catch (e) {
293 if (logger) {
294 logger.error(
295 localize(
296 "CouldNotReadFrom",
297 "Couldn't read from {0}.",
298 localPropertiesFilePath,
299 ),
300 e,
301 e.stack,
302 );
303 logger.info(
304 localize(
305 "UsingAndroidSDKLocationFromPATH",
306 "Using Android SDK location from PATH.",
307 ),
308 );
309 }
310 return null;
311 }
312 return this.parseSdkLocation(fileContent, logger);
313 }
314}
315