microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.7.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

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