microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
revert-2693-dev/lucygramley/networkIsolationPolicy

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

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