microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
69ad2ab3726f88dfef08acbd2de49fa1555573c9

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

284lines · 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";
11nls.config({
12 messageFormat: nls.MessageFormat.bundle,
13 bundleFormat: nls.BundleFormat.standalone,
14})();
15const localize = nls.loadMessageBundle();
16
17// See android versions usage at: http://developer.android.com/about/dashboards/index.html
18export enum AndroidAPILevel {
19 Marshmallow = 23,
20 LOLLIPOP_MR1 = 22,
21 LOLLIPOP = 21 /* Supports adb reverse */,
22 KITKAT = 19,
23 JELLY_BEAN_MR2 = 18,
24 JELLY_BEAN_MR1 = 17,
25 JELLY_BEAN = 16,
26 ICE_CREAM_SANDWICH_MR1 = 15,
27 GINGERBREAD_MR1 = 10,
28}
29
30enum KeyEvents {
31 KEYCODE_BACK = 4,
32 KEYCODE_DPAD_UP = 19,
33 KEYCODE_DPAD_DOWN = 20,
34 KEYCODE_DPAD_CENTER = 23,
35 KEYCODE_MENU = 82,
36}
37
38export enum DeviceType {
39 AndroidSdkEmulator, // These seem to have emulator-<port> ids
40 Other,
41}
42
43export interface IDevice {
44 id: string;
45 isOnline: boolean;
46 type: DeviceType;
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<IDevice[]> {
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, packagerPort: number): Promise<void> {
143 return this.execute(deviceId, `reverse tcp:${packagerPort} tcp:${packagerPort}`);
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<IDevice[]> {
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 private parseConnectedDevices(input: string): IDevice[] {
210 let result: IDevice[] = [];
211 let regex = new RegExp("^(\\S+)\\t(\\S+)$", "mg");
212 let match = regex.exec(input);
213 while (match != null) {
214 result.push({
215 id: match[1],
216 isOnline: match[2] === "device",
217 type: this.extractDeviceType(match[1]),
218 });
219 match = regex.exec(input);
220 }
221 return result;
222 }
223
224 private extractDeviceType(id: string): DeviceType {
225 return id.match(AndroidSDKEmulatorPattern)
226 ? DeviceType.AndroidSdkEmulator
227 : DeviceType.Other;
228 }
229
230 private executeQuery(deviceId: string, command: string): Promise<string> {
231 return this.childProcess.execToString(this.generateCommandForDevice(deviceId, command));
232 }
233
234 private execute(deviceId: string, command: string): Promise<void> {
235 return this.commandExecutor.execute(this.generateCommandForDevice(deviceId, command));
236 }
237
238 private generateCommandForDevice(deviceId: string, adbCommand: string): string {
239 return `${this.adbExecutable} -s "${deviceId}" ${adbCommand}`;
240 }
241
242 private getSdkLocationFromLocalPropertiesFile(
243 projectRoot: string,
244 logger?: ILogger,
245 ): string | null {
246 const localPropertiesFilePath = path.join(projectRoot, "android", "local.properties");
247 if (!fs.existsSync(localPropertiesFilePath)) {
248 if (logger) {
249 logger.info(
250 localize(
251 "LocalPropertiesFileDoesNotExist",
252 "local.properties file doesn't exist. Using Android SDK location from PATH.",
253 ),
254 );
255 }
256 return null;
257 }
258
259 let fileContent: string;
260 try {
261 fileContent = fs.readFileSync(localPropertiesFilePath).toString();
262 } catch (e) {
263 if (logger) {
264 logger.error(
265 localize(
266 "CouldNotReadFrom",
267 "Couldn't read from {0}.",
268 localPropertiesFilePath,
269 ),
270 e,
271 e.stack,
272 );
273 logger.info(
274 localize(
275 "UsingAndroidSDKLocationFromPATH",
276 "Using Android SDK location from PATH.",
277 ),
278 );
279 }
280 return null;
281 }
282 return this.parseSdkLocation(fileContent, logger);
283 }
284}
285