microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.9.0

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 * 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 // Trying to read sdk location from local.properties file and if we succueded then
232 // we would run adb from inside it, otherwise we would rely to PATH
233 const sdkLocation = this.getSdkLocationFromLocalPropertiesFile(projectRoot, logger);
234 return sdkLocation ? `"${path.join(sdkLocation, "platform-tools", "adb")}"` : "adb";
235 }
236
237 public executeShellCommand(deviceId: string, command: string): Promise<string> {
238 return this.executeQuery(deviceId, `shell "${command}"`);
239 }
240
241 public executeQuery(deviceId: string, command: string): Promise<string> {
242 return this.childProcess.execToString(this.generateCommandForTarget(deviceId, command));
243 }
244
245 private parseConnectedTargets(input: string): IDebuggableMobileTarget[] {
246 const result: IDebuggableMobileTarget[] = [];
247 const regex = new RegExp("^(\\S+)\\t(\\S+)$", "mg");
248 let match = regex.exec(input);
249 while (match != null) {
250 result.push({
251 id: match[1],
252 isOnline: match[2] === "device",
253 isVirtualTarget: this.isVirtualTarget(match[1]),
254 });
255 match = regex.exec(input);
256 }
257 return result;
258 }
259
260 public isVirtualTarget(id: string): boolean {
261 return !!id.match(AdbHelper.AndroidSDKEmulatorPattern);
262 }
263
264 private execute(deviceId: string, command: string): Promise<void> {
265 return this.commandExecutor.execute(this.generateCommandForTarget(deviceId, command));
266 }
267
268 private generateCommandForTarget(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