microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.11.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

332lines · 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, "shell getprop ro.build.version.sdk");
168 return parseInt(output, 10);
169 }
170
171 public reverseAdb(deviceId: string, port: number): Promise<void> {
172 return this.execute(deviceId, `reverse tcp:${port} tcp:${port}`);
173 }
174
175 public showDevMenu(deviceId?: string): Promise<void> {
176 const command = `${this.adbExecutable} ${
177 deviceId ? `-s ${deviceId}` : ""
178 } shell input keyevent ${KeyEvents.KEYCODE_MENU}`;
179 return this.commandExecutor.execute(command);
180 }
181
182 public reloadApp(deviceId?: string): Promise<void> {
183 const command = `${this.adbExecutable} ${
184 deviceId ? `-s ${deviceId}` : ""
185 } shell input text "RR"`;
186 return this.commandExecutor.execute(command);
187 }
188
189 public async getOnlineTargets(): Promise<IDebuggableMobileTarget[]> {
190 const devices = await this.getConnectedTargets();
191 return devices.filter(device => device.isOnline);
192 }
193
194 public startLogCat(adbParameters: string[]): ISpawnResult {
195 return this.childProcess.spawn(this.adbExecutable.replace(/"/g, ""), adbParameters);
196 }
197
198 public parseSdkLocation(fileContent: string, logger?: ILogger): string | null {
199 const matches = fileContent.match(/^sdk\.dir\s*=(.+)$/m);
200 if (!matches || !matches[1]) {
201 if (logger) {
202 logger.info(
203 localize(
204 "NoSdkDirFoundInLocalPropertiesFile",
205 "No sdk.dir value found in local.properties file. Using Android SDK location from PATH.",
206 ),
207 );
208 }
209 return null;
210 }
211
212 let sdkLocation = matches[1].trim();
213 if (os.platform() === "win32") {
214 // For Windows we need to unescape files separators and drive letter separators
215 sdkLocation = sdkLocation.replace(/\\\\/g, "\\").replace("\\:", ":");
216 }
217 if (logger) {
218 logger.info(
219 localize(
220 "UsindAndroidSDKLocationDefinedInLocalPropertiesFile",
221 "Using Android SDK location defined in android/local.properties file: {0}.",
222 sdkLocation,
223 ),
224 );
225 }
226
227 return sdkLocation;
228 }
229
230 public getAdbPath(projectRoot: string, logger?: ILogger): string {
231 const sdkLocation = this.getSdkLocationFromLocalPropertiesFile(projectRoot, logger);
232 if (sdkLocation) {
233 const localPropertiesSdkPath = path.join(
234 sdkLocation as string,
235 "platform-tools",
236 os.platform() === "win32" ? "adb.exe" : "adb",
237 );
238 const isExist = fs.existsSync(localPropertiesSdkPath);
239 if (isExist) {
240 return localPropertiesSdkPath;
241 }
242 if (logger) {
243 logger.warning(
244 localize(
245 "LocalPropertiesAndroidSDKPathNotExistingInLocal",
246 "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.",
247 ),
248 );
249 }
250 return "adb";
251 }
252 return "adb";
253 }
254
255 public executeShellCommand(deviceId: string, command: string): Promise<string> {
256 return this.executeQuery(deviceId, `shell "${command}"`);
257 }
258
259 public executeQuery(deviceId: string, command: string): Promise<string> {
260 return this.childProcess.execToString(this.generateCommandForTarget(deviceId, command));
261 }
262
263 private parseConnectedTargets(input: string): IDebuggableMobileTarget[] {
264 const result: IDebuggableMobileTarget[] = [];
265 const regex = new RegExp("^(\\S+)\\t(\\S+)$", "mg");
266 let match = regex.exec(input);
267 while (match != null) {
268 result.push({
269 id: match[1],
270 isOnline: match[2] === "device",
271 isVirtualTarget: this.isVirtualTarget(match[1]),
272 });
273 match = regex.exec(input);
274 }
275 return result;
276 }
277
278 public isVirtualTarget(id: string): boolean {
279 return !!id.match(AdbHelper.AndroidSDKEmulatorPattern);
280 }
281
282 private execute(deviceId: string, command: string): Promise<void> {
283 return this.commandExecutor.execute(this.generateCommandForTarget(deviceId, command));
284 }
285
286 private generateCommandForTarget(deviceId: string, adbCommand: string): string {
287 return `${this.adbExecutable} -s "${deviceId}" ${adbCommand}`;
288 }
289
290 private getSdkLocationFromLocalPropertiesFile(
291 projectRoot: string,
292 logger?: ILogger,
293 ): string | null {
294 const localPropertiesFilePath = path.join(projectRoot, "android", "local.properties");
295 if (!fs.existsSync(localPropertiesFilePath)) {
296 if (logger) {
297 logger.info(
298 localize(
299 "LocalPropertiesFileDoesNotExist",
300 "local.properties file doesn't exist. Using Android SDK location from PATH.",
301 ),
302 );
303 }
304 return null;
305 }
306
307 let fileContent: string;
308 try {
309 fileContent = fs.readFileSync(localPropertiesFilePath).toString();
310 } catch (e) {
311 if (logger) {
312 logger.error(
313 localize(
314 "CouldNotReadFrom",
315 "Couldn't read from {0}.",
316 localPropertiesFilePath,
317 ),
318 e,
319 e.stack,
320 );
321 logger.info(
322 localize(
323 "UsingAndroidSDKLocationFromPATH",
324 "Using Android SDK location from PATH.",
325 ),
326 );
327 }
328 return null;
329 }
330 return this.parseSdkLocation(fileContent, logger);
331 }
332}
333