microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.9.3

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

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