microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.8.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

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