microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.6.15

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/adb.ts

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