microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.5.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

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