microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
ea42d4004854292e5d6c536c4d57f8a3b348ee18

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

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