microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dev/v-peq/stabilizeExperimentServiceTests

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

335lines · 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 installApplicationToEmulator(appPath: string): Promise<string> {
260 return this.childProcess.execToString(`adb install ${appPath}`);
261 }
262
263 public executeQuery(deviceId: string, command: string): Promise<string> {
264 return this.childProcess.execToString(this.generateCommandForTarget(deviceId, command));
265 }
266
267 private parseConnectedTargets(input: string): IDebuggableMobileTarget[] {
268 const result: IDebuggableMobileTarget[] = [];
269 const regex = new RegExp("^(\\S+)\\t(\\S+)$", "mg");
270 let match = regex.exec(input);
271 while (match != null) {
272 result.push({
273 id: match[1],
274 isOnline: match[2] === "device",
275 isVirtualTarget: this.isVirtualTarget(match[1]),
276 });
277 match = regex.exec(input);
278 }
279 return result;
280 }
281
282 public isVirtualTarget(id: string): boolean {
283 return !!id.match(AdbHelper.AndroidSDKEmulatorPattern);
284 }
285
286 private execute(deviceId: string, command: string): Promise<void> {
287 return this.commandExecutor.execute(this.generateCommandForTarget(deviceId, command));
288 }
289
290 private generateCommandForTarget(deviceId: string, adbCommand: string): string {
291 return `${this.adbExecutable} -s "${deviceId}" ${adbCommand}`;
292 }
293
294 private getSdkLocationFromLocalPropertiesFile(
295 projectRoot: string,
296 logger?: ILogger,
297 ): string | null {
298 const localPropertiesFilePath = path.join(projectRoot, "android", "local.properties");
299 if (!fs.existsSync(localPropertiesFilePath)) {
300 if (logger) {
301 logger.info(
302 localize(
303 "LocalPropertiesFileDoesNotExist",
304 "local.properties file doesn't exist. Using Android SDK location from PATH.",
305 ),
306 );
307 }
308 return null;
309 }
310
311 let fileContent: string;
312 try {
313 fileContent = fs.readFileSync(localPropertiesFilePath).toString();
314 } catch (e) {
315 if (logger) {
316 logger.error(
317 localize(
318 "CouldNotReadFrom",
319 "Couldn't read from {0}.",
320 localPropertiesFilePath,
321 ),
322 e as Error,
323 );
324 logger.info(
325 localize(
326 "UsingAndroidSDKLocationFromPATH",
327 "Using Android SDK location from PATH.",
328 ),
329 );
330 }
331 return null;
332 }
333 return this.parseSdkLocation(fileContent, logger);
334 }
335}
336