microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.7.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

300lines · 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 { IDevice } from "../../common/device";
7import * as path from "path";
8import * as fs from "fs";
9import { ILogger } from "../log/LogHelper";
10import * as os from "os";
11import * as nls from "vscode-nls";
12import { PromiseUtil } from "../../common/node/promise";
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 enum AdbDeviceType {
41 AndroidSdkEmulator, // These seem to have emulator-<port> ids
42 RemoteDevice,
43 Other,
44}
45
46export interface IAdbDevice extends IDevice {
47 isOnline: boolean;
48 type: AdbDeviceType;
49}
50
51interface AdbDeviceTypePattern {
52 pattern: RegExp;
53 adbDeviceType: AdbDeviceType;
54}
55
56export class AdbHelper {
57 private nodeModulesRoot: string;
58 private launchActivity: string;
59 private childProcess: ChildProcess = new ChildProcess();
60 private commandExecutor: CommandExecutor;
61 private adbExecutable: string = "";
62
63 private static ADB_DEVICE_TYPE_PATTERNS: AdbDeviceTypePattern[] = [
64 {
65 pattern: /^emulator-\d{1,5}$/gm,
66 adbDeviceType: AdbDeviceType.AndroidSdkEmulator,
67 },
68 {
69 pattern: /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}|.*_adb-tls-connect\._tcp.*)$/gm,
70 adbDeviceType: AdbDeviceType.RemoteDevice,
71 },
72 ];
73
74 constructor(
75 projectRoot: string,
76 nodeModulesRoot: string,
77 logger?: ILogger,
78 launchActivity: string = "MainActivity",
79 ) {
80 this.nodeModulesRoot = nodeModulesRoot;
81 this.adbExecutable = this.getAdbPath(projectRoot, logger);
82 this.commandExecutor = new CommandExecutor(this.nodeModulesRoot);
83 this.launchActivity = launchActivity;
84 }
85
86 /**
87 * Gets the list of Android connected devices and emulators.
88 */
89 public async getConnectedDevices(): Promise<IAdbDevice[]> {
90 const output = await this.childProcess.execToString(`${this.adbExecutable} devices`);
91 return this.parseConnectedDevices(output);
92 }
93
94 public setLaunchActivity(launchActivity: string): void {
95 this.launchActivity = launchActivity;
96 }
97
98 /**
99 * Broadcasts an intent to reload the application in debug mode.
100 */
101 public async switchDebugMode(
102 projectRoot: string,
103 packageName: string,
104 enable: boolean,
105 debugTarget?: string,
106 appIdSuffix?: string,
107 ): Promise<void> {
108 let enableDebugCommand = `${this.adbExecutable} ${
109 debugTarget ? "-s " + debugTarget : ""
110 } shell am broadcast -a "${packageName}.RELOAD_APP_ACTION" --ez jsproxy ${enable}`;
111 await new CommandExecutor(this.nodeModulesRoot, projectRoot).execute(enableDebugCommand);
112 // We should stop and start application again after RELOAD_APP_ACTION, otherwise app going to hangs up
113 await PromiseUtil.delay(200); // We need a little delay after broadcast command
114 await this.stopApp(projectRoot, packageName, debugTarget, appIdSuffix);
115 return this.launchApp(projectRoot, packageName, debugTarget, appIdSuffix);
116 }
117
118 /**
119 * Sends an intent which launches the main activity of the application.
120 */
121 public launchApp(
122 projectRoot: string,
123 packageName: string,
124 debugTarget?: string,
125 appIdSuffix?: string,
126 ): Promise<void> {
127 let launchAppCommand = `${this.adbExecutable} ${
128 debugTarget ? "-s " + debugTarget : ""
129 } shell am start -n ${packageName}${appIdSuffix ? "." + appIdSuffix : ""}/${packageName}.${
130 this.launchActivity
131 }`;
132 return new CommandExecutor(projectRoot).execute(launchAppCommand);
133 }
134
135 public stopApp(
136 projectRoot: string,
137 packageName: string,
138 debugTarget?: string,
139 appIdSuffix?: string,
140 ): Promise<void> {
141 let stopAppCommand = `${this.adbExecutable} ${
142 debugTarget ? "-s " + debugTarget : ""
143 } shell am force-stop ${packageName}${appIdSuffix ? "." + appIdSuffix : ""}`;
144 return new CommandExecutor(projectRoot).execute(stopAppCommand);
145 }
146
147 public async apiVersion(deviceId: string): Promise<AndroidAPILevel> {
148 const output = await this.executeQuery(deviceId, "shell getprop ro.build.version.sdk");
149 return parseInt(output, 10);
150 }
151
152 public reverseAdb(deviceId: string, port: number): Promise<void> {
153 return this.execute(deviceId, `reverse tcp:${port} tcp:${port}`);
154 }
155
156 public showDevMenu(deviceId?: string): Promise<void> {
157 let command = `${this.adbExecutable} ${
158 deviceId ? "-s " + deviceId : ""
159 } shell input keyevent ${KeyEvents.KEYCODE_MENU}`;
160 return this.commandExecutor.execute(command);
161 }
162
163 public reloadApp(deviceId?: string): Promise<void> {
164 let command = `${this.adbExecutable} ${
165 deviceId ? "-s " + deviceId : ""
166 } shell input text "RR"`;
167 return this.commandExecutor.execute(command);
168 }
169
170 public async getOnlineDevices(): Promise<IAdbDevice[]> {
171 const devices = await this.getConnectedDevices();
172 return devices.filter(device => device.isOnline);
173 }
174
175 public startLogCat(adbParameters: string[]): ISpawnResult {
176 return this.childProcess.spawn(this.adbExecutable.replace(/\"/g, ""), adbParameters);
177 }
178
179 public parseSdkLocation(fileContent: string, logger?: ILogger): string | null {
180 const matches = fileContent.match(/^sdk\.dir\s*=(.+)$/m);
181 if (!matches || !matches[1]) {
182 if (logger) {
183 logger.info(
184 localize(
185 "NoSdkDirFoundInLocalPropertiesFile",
186 "No sdk.dir value found in local.properties file. Using Android SDK location from PATH.",
187 ),
188 );
189 }
190 return null;
191 }
192
193 let sdkLocation = matches[1].trim();
194 if (os.platform() === "win32") {
195 // For Windows we need to unescape files separators and drive letter separators
196 sdkLocation = sdkLocation.replace(/\\\\/g, "\\").replace("\\:", ":");
197 }
198 if (logger) {
199 logger.info(
200 localize(
201 "UsindAndroidSDKLocationDefinedInLocalPropertiesFile",
202 "Using Android SDK location defined in android/local.properties file: {0}.",
203 sdkLocation,
204 ),
205 );
206 }
207
208 return sdkLocation;
209 }
210
211 public getAdbPath(projectRoot: string, logger?: ILogger): string {
212 // Trying to read sdk location from local.properties file and if we succueded then
213 // we would run adb from inside it, otherwise we would rely to PATH
214 const sdkLocation = this.getSdkLocationFromLocalPropertiesFile(projectRoot, logger);
215 return sdkLocation ? `"${path.join(sdkLocation, "platform-tools", "adb")}"` : "adb";
216 }
217
218 public executeShellCommand(deviceId: string, command: string): Promise<string> {
219 return this.executeQuery(deviceId, `shell "${command}"`);
220 }
221
222 public executeQuery(deviceId: string, command: string): Promise<string> {
223 return this.childProcess.execToString(this.generateCommandForDevice(deviceId, command));
224 }
225
226 private parseConnectedDevices(input: string): IAdbDevice[] {
227 let result: IAdbDevice[] = [];
228 let regex = new RegExp("^(\\S+)\\t(\\S+)$", "mg");
229 let match = regex.exec(input);
230 while (match != null) {
231 result.push({
232 id: match[1],
233 isOnline: match[2] === "device",
234 type: this.extractDeviceType(match[1]),
235 });
236 match = regex.exec(input);
237 }
238 return result;
239 }
240
241 private extractDeviceType(id: string): AdbDeviceType {
242 for (let pattern of AdbHelper.ADB_DEVICE_TYPE_PATTERNS) {
243 if (id.match(pattern.pattern)) {
244 return pattern.adbDeviceType;
245 }
246 }
247 return AdbDeviceType.Other;
248 }
249
250 private execute(deviceId: string, command: string): Promise<void> {
251 return this.commandExecutor.execute(this.generateCommandForDevice(deviceId, command));
252 }
253
254 private generateCommandForDevice(deviceId: string, adbCommand: string): string {
255 return `${this.adbExecutable} -s "${deviceId}" ${adbCommand}`;
256 }
257
258 private getSdkLocationFromLocalPropertiesFile(
259 projectRoot: string,
260 logger?: ILogger,
261 ): string | null {
262 const localPropertiesFilePath = path.join(projectRoot, "android", "local.properties");
263 if (!fs.existsSync(localPropertiesFilePath)) {
264 if (logger) {
265 logger.info(
266 localize(
267 "LocalPropertiesFileDoesNotExist",
268 "local.properties file doesn't exist. Using Android SDK location from PATH.",
269 ),
270 );
271 }
272 return null;
273 }
274
275 let fileContent: string;
276 try {
277 fileContent = fs.readFileSync(localPropertiesFilePath).toString();
278 } catch (e) {
279 if (logger) {
280 logger.error(
281 localize(
282 "CouldNotReadFrom",
283 "Couldn't read from {0}.",
284 localPropertiesFilePath,
285 ),
286 e,
287 e.stack,
288 );
289 logger.info(
290 localize(
291 "UsingAndroidSDKLocationFromPATH",
292 "Using Android SDK location from PATH.",
293 ),
294 );
295 }
296 return null;
297 }
298 return this.parseSdkLocation(fileContent, logger);
299 }
300}
301