microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
c12fdaa8fc70228be3da01cbb96277c268e801f2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

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