From 7e18f98d101df72cdeb1ffb8f753bb0170cdfb8a Mon Sep 17 00:00:00 2001
From: rosen-vladimirov <rosen.vladimirov@telerik.com>
Date: Thu, 14 Jul 2016 10:54:25 +0300
Subject: [PATCH 1/2] Implement new events for debuggable WebViews detection

Implement `debuggableViewFound`, `debuggableViewLost` and `debuggableViewChanged` events that will be raised when there's a change in application's debuggable views.
Add new method to Public API: `getDebuggableViews` which will return debuggable views per application on device.
---
 README.md                                     | 210 +++++++++++++++++-
 appbuilder/device-emitter.ts                  |  12 +
 definitions/mobile.d.ts                       |  57 ++++-
 mobile/android/android-application-manager.ts |  25 +++
 mobile/application-manager-base.ts            |  51 ++++-
 mobile/ios/device/ios-application-manager.ts  |   5 +
 .../ios-simulator-application-manager.ts      |   5 +
 mobile/mobile-core/android-process-service.ts | 129 +++++++----
 mobile/mobile-core/devices-service.ts         |  10 +
 package.json                                  |   2 +-
 test/unit-tests/appbuilder/device-emitter.ts  |  57 ++++-
 .../mobile/application-manager-base.ts        | 196 +++++++++++++++-
 12 files changed, 709 insertions(+), 50 deletions(-)

diff --git a/README.md b/README.md
index 1606723d..c0a0289b 100644
--- a/README.md
+++ b/README.md
@@ -293,6 +293,14 @@ require("mobile-cli-lib").deviceEmitter.on("applicationInstalled",  function(ide
 });
 ```
 
+* `applicationUninstalled` - Raised when application is removed from device. The callback has two arguments - `deviceIdentifier` and `applicationIdentifier`. <br/><br/>
+Sample usage:
+```JavaScript
+require("mobile-cli-lib").deviceEmitter.on("applicationUninstalled",  function(identifier, applicationIdentifier) {
+	console.log("Application " + applicationIdentifier  + " has been uninstalled from device with id: " + identifier);
+});
+```
+
 * `debuggableAppFound` - Raised when application on a device becomes available for debugging. The callback has one argument - `applicationInfo`. <br/><br/>
 Sample usage:
 ```JavaScript
@@ -327,13 +335,71 @@ Sample result for `applicationInfo` will be:
 }
 ```
 
-* `applicationUninstalled` - Raised when application is removed from device. The callback has two arguments - `deviceIdentifier` and `applicationIdentifier`. <br/><br/>
+* `debuggableViewFound` - Raised when a new debuggable WebView is found. The callback has three arguments - `deviceIdentifier`, `appIdentifier` and `webViewInfo`.
+
 Sample usage:
 ```JavaScript
-require("mobile-cli-lib").deviceEmitter.on("applicationUninstalled",  function(identifier, applicationIdentifier) {
-	console.log("Application " + applicationIdentifier  + " has been uninstalled from device with id: " + identifier);
+require("mobile-cli-lib")
+	.deviceEmitter.on("debuggableViewFound",  function(deviceIdentifier, appIdentifier, debuggableViewInfo) {
+	console.log("On device " + deviceIdentifier + " the application " + appIdentifier  + " now has new WebView: " + debuggableViewInfo);
 });
 ```
+Sample result for `debuggableViewInfo` will be:
+```JSON
+{
+	"description": "",
+	"devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4050",
+	"id": "4050",
+	"title": "New tab",
+	"type": "page",
+	"url": "chrome-native://newtab/",
+	"webSocketDebuggerUrl": "ws://127.0.0.1:53213/devtools/page/4050"
+}
+```
+
+* `debuggableViewLost` - Raised when a debuggable WebView is lost. The callback has three arguments - `deviceIdentifier`, `appIdentifier` and `webViewInfo`.
+
+Sample usage:
+```JavaScript
+require("mobile-cli-lib")
+	.deviceEmitter.on("debuggableViewLost",  function(deviceIdentifier, appIdentifier, debuggableViewInfo) {
+	console.log("On device " + deviceIdentifier + " the application " + appIdentifier  + " now cannot debug WebView: " + debuggableViewInfo);
+});
+```
+Sample result for `debuggableViewInfo` will be:
+```JSON
+{
+	"description": "",
+	"devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4050",
+	"id": "4050",
+	"title": "New tab",
+	"type": "page",
+	"url": "chrome-native://newtab/",
+	"webSocketDebuggerUrl": "ws://127.0.0.1:53213/devtools/page/4050"
+}
+```
+
+* `debuggableViewChanged` - Raised when a property of debuggable WebView is changed, for example it's title. The callback has three arguments - `deviceIdentifier`, `appIdentifier` and `webViewInfo`.
+
+Sample usage:
+```JavaScript
+require("mobile-cli-lib")
+	.deviceEmitter.on("debuggableViewChanged",  function(deviceIdentifier, appIdentifier, debuggableViewInfo) {
+	console.log("On device " + deviceIdentifier + " the application " + appIdentifier  + " has changes in WebView: " + debuggableViewInfo);
+});
+```
+Sample result for `debuggableViewInfo` will be:
+```JSON
+{
+	"description": "",
+	"devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4050",
+	"id": "4050",
+	"title": "New tab 2",
+	"type": "page",
+	"url": "chrome-native://newtab/",
+	"webSocketDebuggerUrl": "ws://127.0.0.1:53213/devtools/page/4050"
+}
+```
 
 * `companionAppInstalled` - Raised when application is removed from device. The callback has two arguments - `deviceIdentifier` and `framwork`. <br/><br/>
 Sample usage:
@@ -548,6 +614,144 @@ Sample result will be:
 }]]
 ```
 
+* `getDebuggableApps(deviceIdentifiers: string[]): Promise<IDeviceApplicationInformation[]>[]` - This function checks the proc/net/unix file of each device from the deviceIdentifiers argument for web views connected to abstract ports and returns information about the applications.
+```JavaScript
+/**
+ * Describes basic information about application on device.
+ */
+interface IDeviceApplicationInformation {
+	/**
+	 * The device identifier.
+	 */
+	deviceIdentifier: string;
+
+	/**
+	 * The application identifier.
+	 */
+	appIdentifier: string;
+
+	/**
+	 * The framework of the project (Cordova or NativeScript).
+	 */
+	framework: string;
+}
+```
+
+Sample usage:
+```JavaScript
+Promise.all(require("mobile-cli-lib").devicesService.getDebuggableApps(["4df18f307d8a8f1b", "JJY5KBTW75TCHQUK"]))
+	.then(function(data) {
+		data.forEach(function(apps) {
+			console.log(apps);
+		});
+	}, function(err) {
+		console.log(err);
+	});
+```
+Sample result will be:
+```JSON
+[[{
+	"deviceIdentifier": "4df18f307d8a8f1b",
+	"appIdentifier": "com.telerik.Fitness",
+	"framework": "NativeScript"
+}, {
+	"deviceIdentifier": "4df18f307d8a8f1b",
+	"appIdentifier": "com.telerik.livesynctest",
+	"framework": "Cordova"
+}], [{
+	"deviceIdentifier": "JJY5KBTW75TCHQUK",
+	"appIdentifier": "com.telerik.PhotoAlbum",
+	"framework": "NativeScript"
+}]]
+```
+
+* `getDebuggableViews(deviceIdentifier: string, appIdentifier: string): Promise<IDebugWebViewInfo[]>` - This function returns WebViews that can be debugged for specified application on specified device.
+> NOTE: This method works only for Cordova based applications. DO NOT pass appIdentifier of NativeScript application.
+
+```JavaScript
+/**
+ * Describes information for WebView that can be debugged.
+ */
+interface IDebugWebViewInfo {
+	/**
+	 * Short description of the view.
+	 */
+	description: string;
+
+	/**
+	 * Url to the devtools.
+	 * @example http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4024
+	 */
+	devtoolsFrontendUrl: string;
+
+	/**
+	 * Unique identifier of the web view. Could be number or GUID.
+	 * @example 4027
+	 */
+	id: string;
+
+	/**
+	 * Title of the WebView.
+	 * @example https://bit.ly/12345V is not available
+	 */
+	title: string;
+
+	/**
+	 * Type of the WebView.
+	 * @example page
+	 */
+	type: string;
+
+	/**
+	 * URL loaded in the view.
+	 * @example https://bit.ly/12345V
+	 */
+	url: string;
+
+	/**
+	 * Debugger URL.
+	 * @example ws://127.0.0.1:53213/devtools/page/4027
+	 */
+	webSocketDebuggerUrl: string;
+}
+```
+
+Sample usage:
+```JavaScript
+require("mobile-cli-lib")
+	.devicesService
+	.getDebuggableViews("4df18f307d8a8f1b", "com.telerik.cordovaApp")
+	.then(function(data) {
+		console.log(data);
+	}, function(err) {
+		console.log(err);
+	});
+```
+
+Sample result will be:
+```JSON
+[{
+		"description": "",
+		"devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4050",
+		"id": "4050",
+		"title": "New tab",
+		"type": "page",
+		"url": "chrome-native://newtab/",
+		"webSocketDebuggerUrl": "ws://127.0.0.1:53213/devtools/page/4050"
+	},
+
+	{
+		"description": "",
+		"devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4032",
+		"id": "4032",
+		"title": "New tab",
+		"type": "page",
+		"url": "chrome-native://newtab/",
+		"webSocketDebuggerUrl": "ws://127.0.0.1:53213/devtools/page/4032"
+	}
+]
+```
+
 ### Module liveSyncService
 > Stability: 1 - Could be changed due to some new requirments.
 
diff --git a/appbuilder/device-emitter.ts b/appbuilder/device-emitter.ts
index 17fd18e5..bb1c075e 100644
--- a/appbuilder/device-emitter.ts
+++ b/appbuilder/device-emitter.ts
@@ -85,6 +85,18 @@ export class DeviceEmitter extends EventEmitter {
 		device.applicationManager.on("debuggableAppLost", (debuggableAppInfo: Mobile.IDeviceApplicationInformation) => {
 			this.emit("debuggableAppLost", debuggableAppInfo);
 		});
+
+		device.applicationManager.on("debuggableViewFound", (appIdentifier: string, debuggableWebViewInfo: Mobile.IDebugWebViewInfo) => {
+			this.emit("debuggableViewFound", device.deviceInfo.identifier, appIdentifier, debuggableWebViewInfo);
+		});
+
+		device.applicationManager.on("debuggableViewLost", (appIdentifier: string, debuggableWebViewInfo: Mobile.IDebugWebViewInfo) => {
+			this.emit("debuggableViewLost", device.deviceInfo.identifier, appIdentifier, debuggableWebViewInfo);
+		});
+
+		device.applicationManager.on("debuggableViewChanged", (appIdentifier: string, debuggableWebViewInfo: Mobile.IDebugWebViewInfo) => {
+			this.emit("debuggableViewChanged", device.deviceInfo.identifier, appIdentifier, debuggableWebViewInfo);
+		});
 	}
 
 	private checkCompanionAppChanged(device: Mobile.IDevice, applicationName: string, eventName: string): void {
diff --git a/definitions/mobile.d.ts b/definitions/mobile.d.ts
index 3b514751..30369de6 100644
--- a/definitions/mobile.d.ts
+++ b/definitions/mobile.d.ts
@@ -197,6 +197,7 @@ declare module Mobile {
 		getApplicationInfo(applicationIdentifier: string): IFuture<Mobile.IApplicationInfo>;
 		tryStartApplication(appIdentifier: string, framework?: string): IFuture<void>;
 		getDebuggableApps(): IFuture<Mobile.IDeviceApplicationInformation[]>;
+		getDebuggableAppViews(appIdentifiers: string[]): IFuture<IDictionary<Mobile.IDebugWebViewInfo[]>>;
 	}
 
 	/**
@@ -300,7 +301,7 @@ declare module Mobile {
 		getDeviceByIdentifier(identifier: string): Mobile.IDevice;
 		mapAbstractToTcpPort(deviceIdentifier: string, appIdentifier: string): IFuture<string>;
 		detectCurrentlyAttachedDevices(): IFuture<void>;
-		startEmulator(platform?: string): IFuture<void>; 
+		startEmulator(platform?: string): IFuture<void>;
 	}
 
 	/**
@@ -321,6 +322,60 @@ declare module Mobile {
 		 * @return {Mobile.IDeviceApplicationInformation[]} Returns array of applications information for the applications which are available for debugging.
 		 */
 		getDebuggableApps(deviceIdentifier: string): IFuture<Mobile.IDeviceApplicationInformation[]>;
+
+		/**
+		 * Gets all mapped abstract to tcp ports for specified device id and application identifiers.
+		 * @param deviceIdentifier {string} The identifier of the device.
+		 * @param appIdentifiers {string[]} Application identifiers that will be checked.
+		 * @return {IFuture<IDictionary<number>>} Dictionary, where the keys are app identifiers and the values are local ports.
+		 */
+		getMappedAbstractToTcpPorts(deviceIdentifier: string, appIdentifiers: string[]): IFuture<IDictionary<number>>;
+	}
+
+	/**
+	 * Describes information for WebView that can be debugged.
+	 */
+	interface IDebugWebViewInfo {
+		/**
+		 * Short description of the view.
+		 */
+		description: string;
+
+		/**
+		 * Url to the devtools.
+		 * @example http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4024
+		 */
+		devtoolsFrontendUrl: string;
+
+		/**
+		 * Unique identifier of the web view. Could be number or GUID.
+		 * @example 4027
+		 */
+		id: string;
+
+		/**
+		 * Title of the WebView.
+		 * @example https://bit.ly/12345V is not available
+		 */
+		title: string;
+
+		/**
+		 * Type of the WebView.
+		 * @example page
+		 */
+		type: string;
+
+		/**
+		 * URL loaded in the view.
+		 * @example https://bit.ly/12345V
+		 */
+		url: string;
+
+		/**
+		 * Debugger URL.
+		 * @example ws://127.0.0.1:53213/devtools/page/4027
+		 */
+		webSocketDebuggerUrl: string;
 	}
 
 	interface IiTunesValidator {
diff --git a/mobile/android/android-application-manager.ts b/mobile/android/android-application-manager.ts
index da4479fa..72868f76 100644
--- a/mobile/android/android-application-manager.ts
+++ b/mobile/android/android-application-manager.ts
@@ -11,6 +11,7 @@ export class AndroidApplicationManager extends ApplicationManagerBase {
 		private $options: ICommonOptions,
 		private $logcatHelper: Mobile.ILogcatHelper,
 		private $androidProcessService: Mobile.IAndroidProcessService,
+		private $httpClient: Server.IHttpClient,
 		$logger: ILogger) {
 		super($logger);
 	}
@@ -83,6 +84,30 @@ export class AndroidApplicationManager extends ApplicationManagerBase {
 		return this.$androidProcessService.getDebuggableApps(this.identifier);
 	}
 
+	public getDebuggableAppViews(appIdentifiers: string[]): IFuture<IDictionary<Mobile.IDebugWebViewInfo[]>> {
+		return ((): IDictionary<Mobile.IDebugWebViewInfo[]> => {
+			let mappedAppIdentifierPorts = this.$androidProcessService.getMappedAbstractToTcpPorts(this.identifier, appIdentifiers).wait(),
+				applicationViews: IDictionary<Mobile.IDebugWebViewInfo[]> = {};
+
+			_.each(mappedAppIdentifierPorts, (port: number, appIdentifier: string) => {
+				applicationViews[appIdentifier] = [];
+				let localAddress = `http://127.0.0.1:${port}/json`;
+
+				try {
+					if (port) {
+						let apps = this.$httpClient.httpRequest(localAddress).wait().body;
+						applicationViews[appIdentifier] = JSON.parse(apps);
+					}
+				} catch (err) {
+					this.$logger.trace(`Error while checking ${localAddress}. Error is: ${err.message}`);
+				}
+			});
+
+			return applicationViews;
+
+		}).future<IDictionary<Mobile.IDebugWebViewInfo[]>>()();
+	}
+
 	private getStartPackageActivity(framework?: string): string {
 		framework = framework || "";
 		return startPackageActivityNames[framework.toLowerCase()] || this.$staticConfig.START_PACKAGE_ACTIVITY_NAME;
diff --git a/mobile/application-manager-base.ts b/mobile/application-manager-base.ts
index 65e4ba2e..12dd95ff 100644
--- a/mobile/application-manager-base.ts
+++ b/mobile/application-manager-base.ts
@@ -1,8 +1,10 @@
 import { EventEmitter } from "events";
+import {TARGET_FRAMEWORK_IDENTIFIERS} from "../constants";
 
 export abstract class ApplicationManagerBase extends EventEmitter implements Mobile.IDeviceApplicationManager {
 	private lastInstalledAppIdentifiers: string[];
 	private lastAvailableDebuggableApps: Mobile.IDeviceApplicationInformation[];
+	private lastAvailableDebuggableAppViews: IDictionary<Mobile.IDebugWebViewInfo[]> = {};
 
 	constructor(protected $logger: ILogger) {
 		super();
@@ -82,6 +84,7 @@ export abstract class ApplicationManagerBase extends EventEmitter implements Mob
 	public abstract getApplicationInfo(applicationIdentifier: string): IFuture<Mobile.IApplicationInfo>;
 	public abstract canStartApplication(): boolean;
 	public abstract getDebuggableApps(): IFuture<Mobile.IDeviceApplicationInformation[]>;
+	public abstract getDebuggableAppViews(appIdentifiers: string[]): IFuture<IDictionary<Mobile.IDebugWebViewInfo[]>>;
 
 	private checkForAvailableDebuggableAppsChanges(): IFuture<void> {
 		return (() => {
@@ -93,8 +96,52 @@ export abstract class ApplicationManagerBase extends EventEmitter implements Mob
 
 			this.lastAvailableDebuggableApps = currentlyAvailableDebuggableApps;
 
-			_.each(newAvailableDebuggableApps, (appInfo: Mobile.IDeviceApplicationInformation) => this.emit("debuggableAppFound", appInfo));
-			_.each(notAvailableAppsForDebugging, (appInfo: Mobile.IDeviceApplicationInformation) => this.emit("debuggableAppLost", appInfo));
+			_.each(newAvailableDebuggableApps, (appInfo: Mobile.IDeviceApplicationInformation) => {
+				this.emit("debuggableAppFound", appInfo);
+			});
+
+			_.each(notAvailableAppsForDebugging, (appInfo: Mobile.IDeviceApplicationInformation) => {
+				this.emit("debuggableAppLost", appInfo);
+
+				if (_.has(this.lastAvailableDebuggableAppViews, appInfo.appIdentifier)) {
+					// Prevent emitting debuggableViewLost when application cannot be debugged anymore.
+					delete this.lastAvailableDebuggableAppViews[appInfo.appIdentifier];
+				}
+			});
+
+			let cordovaDebuggableAppIdentifiers = _(currentlyAvailableDebuggableApps)
+				.filter(c => c.framework === TARGET_FRAMEWORK_IDENTIFIERS.Cordova)
+				.map(c => c.appIdentifier)
+				.value();
+
+			let currentlyAvailableAppViews = this.getDebuggableAppViews(cordovaDebuggableAppIdentifiers).wait();
+
+			_.each(currentlyAvailableAppViews, (currentlyAvailableViews, appIdentifier) => {
+				let previouslyAvailableViews = this.lastAvailableDebuggableAppViews[appIdentifier];
+
+				let newAvailableViews = _.differenceBy(currentlyAvailableViews, previouslyAvailableViews, "id");
+				let notAvailableViews = _.differenceBy(previouslyAvailableViews, currentlyAvailableViews, "id");
+
+				_.each(notAvailableViews, debugWebViewInfo => {
+					this.emit("debuggableViewLost", appIdentifier, debugWebViewInfo);
+				});
+
+				_.each(newAvailableViews, debugWebViewInfo => {
+					this.emit("debuggableViewFound", appIdentifier, debugWebViewInfo);
+				});
+
+				// Determine which of the views had changed since last check and raise debuggableViewChanged event for them:
+				let keptViews = _.differenceBy(currentlyAvailableViews, newAvailableViews, "id");
+				_.each(keptViews, view => {
+					let previousTimeViewInfo = _.find(previouslyAvailableViews, previousView => previousView.id === view.id);
+					if (!_.isEqual(view, previousTimeViewInfo)) {
+						this.emit("debuggableViewChanged", appIdentifier, view);
+					}
+				});
+
+				this.lastAvailableDebuggableAppViews[appIdentifier] = currentlyAvailableViews;
+			});
+
 		}).future<void>()();
 	}
 }
diff --git a/mobile/ios/device/ios-application-manager.ts b/mobile/ios/device/ios-application-manager.ts
index 1778097d..6a9930cb 100644
--- a/mobile/ios/device/ios-application-manager.ts
+++ b/mobile/ios/device/ios-application-manager.ts
@@ -231,6 +231,11 @@ export class IOSApplicationManager extends ApplicationManagerBase {
 		return Future.fromResult([]);
 	}
 
+	public getDebuggableAppViews(appIdentifiers: string[]): IFuture<IDictionary<Mobile.IDebugWebViewInfo[]>> {
+		// Implement when we can find debuggable applications for iOS.
+		return Future.fromResult(null);
+	}
+
 	private lookupApplications(): IDictionary<Mobile.IDeviceApplication> {
 		let func = () => {
 			let dictionaryPointer = ref.alloc(CoreTypes.cfDictionaryRef);
diff --git a/mobile/ios/simulator/ios-simulator-application-manager.ts b/mobile/ios/simulator/ios-simulator-application-manager.ts
index 09e3149a..8dba3b66 100644
--- a/mobile/ios/simulator/ios-simulator-application-manager.ts
+++ b/mobile/ios/simulator/ios-simulator-application-manager.ts
@@ -95,4 +95,9 @@ export class IOSSimulatorApplicationManager extends ApplicationManagerBase {
 	public getDebuggableApps(): IFuture<Mobile.IDeviceApplicationInformation[]> {
 		return Future.fromResult([]);
 	}
+
+	public getDebuggableAppViews(appIdentifiers: string[]): IFuture<IDictionary<Mobile.IDebugWebViewInfo[]>> {
+		// Implement when we can find debuggable applications for iOS.
+		return Future.fromResult(null);
+	}
 }
diff --git a/mobile/mobile-core/android-process-service.ts b/mobile/mobile-core/android-process-service.ts
index ec6bbc14..2a486638 100644
--- a/mobile/mobile-core/android-process-service.ts
+++ b/mobile/mobile-core/android-process-service.ts
@@ -8,7 +8,6 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService {
 	constructor(private $errors: IErrors,
 		private $staticConfig: Config.IStaticConfig,
 		private $injector: IInjector,
-		private $httpClient: Server.IHttpClient,
 		private $net: INet) {
 		this._devicesAdbs = {};
 	}
@@ -27,14 +26,15 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService {
 	public mapAbstractToTcpPort(deviceIdentifier: string, appIdentifier: string): IFuture<string> {
 		return (() => {
 			let adb = this.getAdb(deviceIdentifier);
-			let processId = this.getProcessId(adb, appIdentifier).wait();
+			let processId = this.getProcessIds(adb, [appIdentifier]).wait()[appIdentifier];
 			let applicationNotStartedErrorMessage = `The application is not started on the device with identifier ${deviceIdentifier}.`;
 
 			if (!processId) {
 				this.$errors.failWithoutHelp(applicationNotStartedErrorMessage);
 			}
 
-			let abstractPort = this.getAbstractPortForApplication(adb, processId, appIdentifier).wait();
+			let abstractPortsInformation = this.getAbstractPortsInformation(adb).wait();
+			let abstractPort = this.getAbstractPortForApplication(adb, processId, appIdentifier, abstractPortsInformation).wait();
 
 			if (!abstractPort) {
 				this.$errors.failWithoutHelp(applicationNotStartedErrorMessage);
@@ -51,6 +51,39 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService {
 		}).future<string>()();
 	}
 
+	public getMappedAbstractToTcpPorts(deviceIdentifier: string, appIdentifiers: string[]): IFuture<IDictionary<number>> {
+		return ((): IDictionary<number> => {
+			let adb = this.getAdb(deviceIdentifier),
+				abstractPortsInformation = this.getAbstractPortsInformation(adb).wait(),
+				processIds = this.getProcessIds(adb, appIdentifiers).wait(),
+				adbForwardList = adb.executeCommand(["forward", "--list"]).wait(),
+				localPorts: IDictionary<number> = {};
+
+			_.each(appIdentifiers, appIdentifier => {
+				localPorts[appIdentifier] = null;
+				let processId = processIds[appIdentifier];
+
+				if (!processId) {
+					return;
+				}
+
+				let abstractPort = this.getAbstractPortForApplication(adb, processId, appIdentifier, abstractPortsInformation).wait();
+
+				if (!abstractPort) {
+					return;
+				}
+
+				let localPort = this.getAlreadyMappedPort(adb, deviceIdentifier, abstractPort, adbForwardList).wait();
+
+				if (localPort) {
+					localPorts[appIdentifier] = localPort;
+				}
+			});
+
+			return localPorts;
+		}).future<IDictionary<number>>()();
+	}
+
 	public getDebuggableApps(deviceIdentifier: string): IFuture<Mobile.IDeviceApplicationInformation[]> {
 		return ((): Mobile.IDeviceApplicationInformation[] => {
 			let adb = this.getAdb(deviceIdentifier);
@@ -58,9 +91,11 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService {
 
 			// TODO: Add tests and make sure only unique names are returned.
 			return _(androidWebViewPortInformation)
-				.map((line: string) => this.getApplicationInfoFromWebViewPortInformation(adb, deviceIdentifier, line).wait())
-				.filter(appIdentifier => !!appIdentifier)
-				.uniqBy("appIdentifier")
+				.map(line => this.getApplicationInfoFromWebViewPortInformation(adb, deviceIdentifier, line).wait()
+					|| this.getNativeScriptApplicationInformation(adb, deviceIdentifier, line).wait()
+				)
+				.filter(deviceAppInfo => !!deviceAppInfo)
+				.uniqBy(deviceAppInfo => deviceAppInfo.appIdentifier)
 				.value();
 		}).future<Mobile.IDeviceApplicationInformation[]>()();
 	}
@@ -68,29 +103,23 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService {
 	private getApplicationInfoFromWebViewPortInformation(adb: Mobile.IDeviceAndroidDebugBridge, deviceIdentifier: string, information: string): IFuture<Mobile.IDeviceApplicationInformation> {
 		return ((): Mobile.IDeviceApplicationInformation => {
 			// Need to search by processId to check for old Android webviews (@webview_devtools_remote_<processId>).
-			let processIdRegExp = /@webview_devtools_remote_(.+)/g;
-			let processIdMatches = processIdRegExp.exec(information);
-			let oldAndroidWebViewAppIdentifier: string;
+			let processIdRegExp = /@webview_devtools_remote_(.+)/g,
+				processIdMatches = processIdRegExp.exec(information),
+				cordovaAppIdentifier: string;
+
 			if (processIdMatches) {
 				let processId = processIdMatches[1];
-				// Process information will look like this (without the columns names):
-				// USER     PID   PPID  VSIZE   RSS   WCHAN    PC         NAME
-				// u0_a63   25512 1334  1519560 96040 ffffffff f76a8f75 S com.telerik.appbuildertabstest
-				let processIdInformation: string = adb.executeShellCommand(["ps", "|grep", processId]).wait();
-				oldAndroidWebViewAppIdentifier = _.last(processIdInformation.trim().split(/[ \t]/));
-			}
-
-			// Search for appIdentifier (@<appIdentifier>_devtools_remote).
-			let chromeAppIdentifierRegExp = /@(.+)_devtools_remote\s?/g;
-			let chromeAppIdentifierMatches = chromeAppIdentifierRegExp.exec(information);
-			let chromeAppIdentifier: string;
-
-			if (chromeAppIdentifierMatches && chromeAppIdentifierMatches.length > 0) {
-				chromeAppIdentifier = chromeAppIdentifierMatches[1];
+				cordovaAppIdentifier = this.getApplicationIdentifierFromPid(adb, processId).wait();
+			} else {
+				// Search for appIdentifier (@<appIdentifier>_devtools_remote).
+				let chromeAppIdentifierRegExp = /@(.+)_devtools_remote\s?/g;
+				let chromeAppIdentifierMatches = chromeAppIdentifierRegExp.exec(information);
+
+				if (chromeAppIdentifierMatches && chromeAppIdentifierMatches.length > 0) {
+					cordovaAppIdentifier = chromeAppIdentifierMatches[1];
+				}
 			}
 
-			let cordovaAppIdentifier = oldAndroidWebViewAppIdentifier || chromeAppIdentifier;
-
 			if (cordovaAppIdentifier) {
 				return {
 					deviceIdentifier: deviceIdentifier,
@@ -99,6 +128,12 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService {
 				};
 			}
 
+			return null;
+		}).future<Mobile.IDeviceApplicationInformation>()();
+	}
+
+	private getNativeScriptApplicationInformation(adb: Mobile.IDeviceAndroidDebugBridge, deviceIdentifier: string, information: string): IFuture<Mobile.IDeviceApplicationInformation> {
+		return ((): Mobile.IDeviceApplicationInformation => {
 			// Search for appIdentifier (@<appIdentifier-debug>).
 			let nativeScriptAppIdentifierRegExp = /@(.+)-debug/g;
 			let nativeScriptAppIdentifierMatches = nativeScriptAppIdentifierRegExp.exec(information);
@@ -116,13 +151,12 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService {
 		}).future<Mobile.IDeviceApplicationInformation>()();
 	}
 
-	private getAbstractPortForApplication(adb: Mobile.IDeviceAndroidDebugBridge, processId: string, appIdentifier: string): IFuture<string> {
+	private getAbstractPortForApplication(adb: Mobile.IDeviceAndroidDebugBridge, processId: string | number, appIdentifier: string, abstractPortsInformation: string): IFuture<string> {
 		return (() => {
 			// The result will look like this (without the columns names):
 			// Num       		 RefCount Protocol Flags    Type St Inode  Path
 			// 0000000000000000: 00000002 00000000 00010000 0001 01 189004 @webview_devtools_remote_25512
 			// The Path column is the abstract port.
-			let abstractPortsInformation = this.getAbstractPortsInformation(adb).wait();
 			return this.getPortInformation(abstractPortsInformation, processId) ||
 				this.getPortInformation(abstractPortsInformation, `${appIdentifier}_devtools_remote`) ||
 				this.getPortInformation(abstractPortsInformation, `${appIdentifier}-debug`);
@@ -133,36 +167,41 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService {
 		return adb.executeShellCommand(["cat", "/proc/net/unix"]);
 	}
 
-	private getPortInformation(abstractPortsInformation: string, searchedInfo: string): string {
+	private getPortInformation(abstractPortsInformation: string, searchedInfo: string | number): string {
 		let processRegExp = new RegExp(`\\w+:\\s+(?:\\w+\\s+){1,6}@(.*?${searchedInfo})$`, "gm");
 
 		let match = processRegExp.exec(abstractPortsInformation);
 		return match && match[1];
 	}
 
-	private getProcessId(adb: Mobile.IDeviceAndroidDebugBridge, appIdentifier: string): IFuture<string> {
+	private getProcessIds(adb: Mobile.IDeviceAndroidDebugBridge, appIdentifiers: string[]): IFuture<IDictionary<number>> {
 		return (() => {
 			// Process information will look like this (without the columns names):
 			// USER     PID   PPID  VSIZE   RSS   WCHAN    PC         NAME
 			// u0_a63   25512 1334  1519560 96040 ffffffff f76a8f75 S com.telerik.appbuildertabstest
-			let processIdRegExp = new RegExp(`^\\w*\\s*(\\d+).*?${appIdentifier}$`);
+			let result: IDictionary<number> = {};
 			let processIdInformation: string = adb.executeShellCommand(["ps"]).wait();
+			_.each(appIdentifiers, appIdentifier => {
+				let processIdRegExp = new RegExp(`^\\w*\\s*(\\d+).*?${appIdentifier}$`);
+				result[appIdentifier] = this.getFirstMatchingGroupFromMultilineResult<number>(processIdInformation, processIdRegExp);
+			});
 
-			return this.parseMultilineResult(processIdInformation, processIdRegExp);
-		}).future<string>()();
+			return result;
+		}).future<IDictionary<number>>()();
 	}
 
-	private getAlreadyMappedPort(adb: Mobile.IDeviceAndroidDebugBridge, deviceIdentifier: string, abstractPort: string): IFuture<number> {
+	private getAlreadyMappedPort(adb: Mobile.IDeviceAndroidDebugBridge, deviceIdentifier: string, abstractPort: string, adbForwardList?: any): IFuture<number> {
 		return ((): number => {
-			let allForwardedPorts: string = adb.executeCommand(["forward", "--list"]).wait() || "";
+			let allForwardedPorts: string = adbForwardList || adb.executeCommand(["forward", "--list"]).wait() || "";
+
 			// Sample output:
 			// 5e2e580b tcp:62503 localabstract:webview_devtools_remote_7985
 			// 5e2e580b tcp:62524 localabstract:webview_devtools_remote_7986
 			// 5e2e580b tcp:63160 localabstract:webview_devtools_remote_7987
 			// 5e2e580b tcp:57577 localabstract:com.telerik.nrel-debug
-			let regex = new RegExp(`${deviceIdentifier}\\s+?tcp:(\\d+?)\\s+?.*?${abstractPort}.*$`);
+			let regex = new RegExp(`${deviceIdentifier}\\s+?tcp:(\\d+?)\\s+?.*?${abstractPort}$`);
 
-			return this.parseMultilineResult(allForwardedPorts, regex);
+			return this.getFirstMatchingGroupFromMultilineResult<number>(allForwardedPorts, regex);
 		}).future<number>()();
 	}
 
@@ -174,8 +213,18 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService {
 		return this._devicesAdbs[deviceIdentifier];
 	}
 
-	private parseMultilineResult(input: string, regex: RegExp): number {
-		let selectedNumber: number;
+	private getApplicationIdentifierFromPid(adb: Mobile.IDeviceAndroidDebugBridge, pid: string, psData?: string): IFuture<string> {
+		return ((): string => {
+			psData = psData || adb.executeShellCommand(["ps"]).wait();
+			// Process information will look like this (without the columns names):
+			// USER     PID   PPID  VSIZE   RSS   WCHAN    PC         NAME
+			// u0_a63   25512 1334  1519560 96040 ffffffff f76a8f75 S com.telerik.appbuildertabstest
+			return this.getFirstMatchingGroupFromMultilineResult<string>(psData, new RegExp(`\\s+${pid}(?:\\s+\\d+){3}\\s+.*\\s+(.*?)$`));
+		}).future<string>()();
+	}
+
+	private getFirstMatchingGroupFromMultilineResult<T>(input: string, regex: RegExp): T {
+		let result: T;
 
 		_((input || "").split('\n'))
 			.map(line => line.trim())
@@ -183,12 +232,12 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService {
 			.each(line => {
 				let matches = line.match(regex);
 				if (matches && matches[1]) {
-					selectedNumber = +matches[1];
+					result = <any>matches[1];
 					return false;
 				}
 			});
 
-		return selectedNumber;
+		return result;
 	}
 }
 
diff --git a/mobile/mobile-core/devices-service.ts b/mobile/mobile-core/devices-service.ts
index 7b9bd6e0..31d45587 100644
--- a/mobile/mobile-core/devices-service.ts
+++ b/mobile/mobile-core/devices-service.ts
@@ -407,6 +407,16 @@ export class DevicesService implements Mobile.IDevicesService {
 		return _.map(deviceIdentifiers, (deviceIdentifier: string) => this.getDebuggableAppsCore(deviceIdentifier));
 	}
 
+	@exportedPromise("devicesService")
+	public getDebuggableViews(deviceIdentifier: string, appIdentifier: string): IFuture<Mobile.IDebugWebViewInfo[]> {
+		return ((): Mobile.IDebugWebViewInfo[] => {
+			let device = this.getDeviceByIdentifier(deviceIdentifier),
+				debuggableViewsPerApp = device.applicationManager.getDebuggableAppViews([appIdentifier]).wait();
+
+			return debuggableViewsPerApp && debuggableViewsPerApp[appIdentifier];
+		}).future<Mobile.IDebugWebViewInfo[]>()();
+	}
+
 	private getDebuggableAppsCore(deviceIdentifier: string): IFuture<Mobile.IDeviceApplicationInformation[]> {
 		return ((): Mobile.IDeviceApplicationInformation[] => {
 			let device = this.getDeviceByIdentifier(deviceIdentifier);
diff --git a/package.json b/package.json
index 05f12518..86206914 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
     "gaze": "1.0.0",
     "iconv-lite": "0.4.3",
     "inquirer": "0.8.2",
-    "ios-sim-portable": "1.1.1",
+    "ios-sim-portable": "~1.1.4",
     "lodash": "4.13.1",
     "log4js": "0.6.9",
     "marked": "0.3.3",
diff --git a/test/unit-tests/appbuilder/device-emitter.ts b/test/unit-tests/appbuilder/device-emitter.ts
index 5489d77c..57847db8 100644
--- a/test/unit-tests/appbuilder/device-emitter.ts
+++ b/test/unit-tests/appbuilder/device-emitter.ts
@@ -252,7 +252,6 @@ describe("deviceEmitter", () => {
 
 		_.each(["debuggableAppFound", "debuggableAppLost"], (applicationEvent: string) => {
 			describe(applicationEvent, () => {
-				// deviceIdentifier, appIdentifier, framework
 
 				let attachDebuggableEventVerificationHandler = (expectedDebuggableAppInfo: Mobile.IDeviceApplicationInformation, done: mocha.Done) => {
 					deviceEmitter.on(applicationEvent, (debuggableAppInfo: Mobile.IDeviceApplicationInformation) => {
@@ -301,6 +300,62 @@ describe("deviceEmitter", () => {
 			});
 		});
 
+		_.each(["debuggableViewFound", "debuggableViewLost", "debuggableViewChanged"], (applicationEvent: string) => {
+			describe(applicationEvent, () => {
+
+				let createDebuggableWebView = (uniqueId: string) => {
+					return {
+						description: `description_${uniqueId}`,
+						devtoolsFrontendUrl: `devtoolsFrontendUrl_${uniqueId}`,
+						id: `${uniqueId}`,
+						title: `title_${uniqueId}`,
+						type: `type_${uniqueId}`,
+						url: `url_${uniqueId}`,
+						webSocketDebuggerUrl: `webSocketDebuggerUrl_${uniqueId}`,
+					};
+				};
+
+				let appId = "appId";
+
+				let attachDebuggableEventVerificationHandler = (expectedDeviceIdentifier: string, expectedAppIdentifier: string, expectedDebuggableViewInfo: Mobile.IDebugWebViewInfo, done: mocha.Done) => {
+					deviceEmitter.on(applicationEvent, (deviceIdentifier: string, appIdentifier: string, debuggableViewInfo: Mobile.IDebugWebViewInfo) => {
+						assert.deepEqual(deviceIdentifier, expectedDeviceIdentifier);
+
+						assert.deepEqual(appIdentifier, expectedAppIdentifier);
+
+						assert.deepEqual(debuggableViewInfo, expectedDebuggableViewInfo);
+
+						// Wait for all operations to be completed and call done after that.
+						setTimeout(done, 0);
+					});
+				};
+
+				it("is raised when working with android device", (done) => {
+					let expectedDebuggableViewInfo: Mobile.IDebugWebViewInfo = createDebuggableWebView("test1");
+
+					attachDebuggableEventVerificationHandler(androidDevice.deviceInfo.identifier, appId, expectedDebuggableViewInfo, done);
+					androidDeviceDiscovery.emit("deviceFound", androidDevice);
+					androidDevice.applicationManager.emit(applicationEvent, appId, expectedDebuggableViewInfo);
+				});
+
+				it("is raised when working with iOS device", (done) => {
+					let expectedDebuggableViewInfo: Mobile.IDebugWebViewInfo = createDebuggableWebView("test1");
+
+					attachDebuggableEventVerificationHandler(iOSDevice.deviceInfo.identifier, appId, expectedDebuggableViewInfo, done);
+					iOSDeviceDiscovery.emit("deviceFound", iOSDevice);
+					iOSDevice.applicationManager.emit(applicationEvent, appId, expectedDebuggableViewInfo);
+				});
+
+				it("is raised when working with iOS simulator", (done) => {
+					let expectedDebuggableViewInfo: Mobile.IDebugWebViewInfo = createDebuggableWebView("test1");
+
+					attachDebuggableEventVerificationHandler(iOSSimulator.deviceInfo.identifier, appId, expectedDebuggableViewInfo, done);
+					iOSSimulatorDiscovery.emit("deviceFound", iOSSimulator);
+					iOSSimulator.applicationManager.emit(applicationEvent, appId, expectedDebuggableViewInfo);
+				});
+			});
+		});
+
 		_.each(["companionAppInstalled", "companionAppUninstalled"], (applicationEvent: string) => {
 			describe(applicationEvent, () => {
 				_.each(companionAppIdentifiers, (companionAppIdentifersForPlatform: any, applicationFramework: string) => {
diff --git a/test/unit-tests/mobile/application-manager-base.ts b/test/unit-tests/mobile/application-manager-base.ts
index cb2c4d46..0394ce8d 100644
--- a/test/unit-tests/mobile/application-manager-base.ts
+++ b/test/unit-tests/mobile/application-manager-base.ts
@@ -5,7 +5,8 @@ import { ApplicationManagerBase } from "../../../mobile/application-manager-base
 import Future = require("fibers/future");
 
 let currentlyAvailableAppsForDebugging: Mobile.IDeviceApplicationInformation[],
-	currentlyInstalledApps: string[];
+	currentlyInstalledApps: string[],
+	currentlyAvailableAppWebViewsForDebugging: IDictionary<Mobile.IDebugWebViewInfo[]>;
 
 class ApplicationManager extends ApplicationManagerBase {
 	constructor($logger: ILogger) {
@@ -47,6 +48,10 @@ class ApplicationManager extends ApplicationManagerBase {
 	public getDebuggableApps(): IFuture<Mobile.IDeviceApplicationInformation[]> {
 		return Future.fromResult(currentlyAvailableAppsForDebugging);
 	}
+
+	public getDebuggableAppViews(appIdentifiers: string[]): IFuture<IDictionary<Mobile.IDebugWebViewInfo[]>> {
+		return Future.fromResult(_.cloneDeep(currentlyAvailableAppWebViewsForDebugging));
+	}
 }
 
 function createTestInjector(): IInjector {
@@ -64,6 +69,27 @@ function createAppsAvailableForDebugging(count: number): Mobile.IDeviceApplicati
 	}));
 }
 
+function createDebuggableWebView(uniqueId: string) {
+	return {
+		description: `description_${uniqueId}`,
+		devtoolsFrontendUrl: `devtoolsFrontendUrl_${uniqueId}`,
+		id: `${uniqueId}`,
+		title: `title_${uniqueId}`,
+		type: `type_${uniqueId}`,
+		url: `url_${uniqueId}`,
+		webSocketDebuggerUrl: `webSocketDebuggerUrl_${uniqueId}`,
+	};
+}
+
+function createDebuggableWebViews(appInfos: Mobile.IDeviceApplicationInformation[], numberOfViews: number): IDictionary<Mobile.IDebugWebViewInfo[]> {
+	let result: IDictionary<Mobile.IDebugWebViewInfo[]> = {};
+	_.each(appInfos, (appInfo, index) => {
+		result[appInfo.appIdentifier] = _.times(numberOfViews, (currentViewIndex: number) => createDebuggableWebView(`${index}_${currentViewIndex}`));
+	});
+
+	return result;
+}
+
 describe("ApplicationManagerBase", () => {
 	let applicationManager: ApplicationManager,
 		testInjector: IInjector;
@@ -71,6 +97,7 @@ describe("ApplicationManagerBase", () => {
 	beforeEach(() => {
 		testInjector = createTestInjector();
 		currentlyAvailableAppsForDebugging = null;
+		currentlyAvailableAppWebViewsForDebugging = null;
 		applicationManager = testInjector.resolve("applicationManager");
 	});
 
@@ -205,6 +232,171 @@ describe("ApplicationManagerBase", () => {
 				applicationManager.checkForApplicationUpdates().wait();
 				Future.wait(futures);
 			});
+
+			it("emits debuggableViewFound when new views are available for debug", (done) => {
+				currentlyAvailableAppsForDebugging = createAppsAvailableForDebugging(2);
+				let numberOfViewsPerApp = 2;
+				currentlyAvailableAppWebViewsForDebugging = createDebuggableWebViews(currentlyAvailableAppsForDebugging, numberOfViewsPerApp);
+				let currentDebuggableViews: IDictionary<Mobile.IDebugWebViewInfo[]> = {};
+				applicationManager.on("debuggableViewFound", (appIdentifier: string, d: Mobile.IDebugWebViewInfo) => {
+					currentDebuggableViews[appIdentifier] = currentDebuggableViews[appIdentifier] || [];
+					currentDebuggableViews[appIdentifier].push(d);
+					let numberOfFoundViewsPerApp = _.uniq(_.values(currentDebuggableViews).map(arr => arr.length));
+					if (_.keys(currentDebuggableViews).length === currentlyAvailableAppsForDebugging.length
+						&& numberOfFoundViewsPerApp.length === 1 // for all apps we've found exactly two apps.
+						&& numberOfFoundViewsPerApp[0] === numberOfViewsPerApp) {
+						_.each(currentDebuggableViews, (webViews, appId) => {
+							_.each(webViews, webView => {
+								let expectedWebView = _.find(currentlyAvailableAppWebViewsForDebugging[appId], c => c.id === webView.id);
+								assert.isTrue(_.isEqual(webView, expectedWebView));
+							});
+						});
+						setTimeout(done, 0);
+					}
+				});
+
+				applicationManager.checkForApplicationUpdates().wait();
+			});
+
+			it("emits debuggableViewLost when views for debug are removed", (done) => {
+				currentlyAvailableAppsForDebugging = createAppsAvailableForDebugging(2);
+				let numberOfViewsPerApp = 2;
+				currentlyAvailableAppWebViewsForDebugging = createDebuggableWebViews(currentlyAvailableAppsForDebugging, numberOfViewsPerApp);
+				let expectedResults = _.cloneDeep(currentlyAvailableAppWebViewsForDebugging);
+				let currentDebuggableViews: IDictionary<Mobile.IDebugWebViewInfo[]> = {};
+
+				applicationManager.checkForApplicationUpdates().wait();
+
+				applicationManager.on("debuggableViewLost", (appIdentifier: string, d: Mobile.IDebugWebViewInfo) => {
+					currentDebuggableViews[appIdentifier] = currentDebuggableViews[appIdentifier] || [];
+					currentDebuggableViews[appIdentifier].push(d);
+					let numberOfFoundViewsPerApp = _.uniq(_.values(currentDebuggableViews).map(arr => arr.length));
+					if (_.keys(currentDebuggableViews).length === currentlyAvailableAppsForDebugging.length
+						&& numberOfFoundViewsPerApp.length === 1 // for all apps we've found exactly two apps.
+						&& numberOfFoundViewsPerApp[0] === numberOfViewsPerApp) {
+						_.each(currentDebuggableViews, (webViews, appId) => {
+							_.each(webViews, webView => {
+								let expectedWebView = _.find(expectedResults[appId], c => c.id === webView.id);
+								assert.isTrue(_.isEqual(webView, expectedWebView));
+							});
+						});
+						setTimeout(done, 0);
+					}
+				});
+
+				currentlyAvailableAppWebViewsForDebugging = _.mapValues(currentlyAvailableAppWebViewsForDebugging, (a) => []);
+
+				applicationManager.checkForApplicationUpdates().wait();
+			});
+
+			it("emits debuggableViewFound when new views are available for debug", (done) => {
+				currentlyAvailableAppsForDebugging = createAppsAvailableForDebugging(2);
+				let numberOfViewsPerApp = 2;
+				currentlyAvailableAppWebViewsForDebugging = createDebuggableWebViews(currentlyAvailableAppsForDebugging, numberOfViewsPerApp);
+
+				applicationManager.checkForApplicationUpdates().wait();
+
+				let expectedViewToBeFound = createDebuggableWebView("uniqueId"),
+					expectedAppIdentifier = currentlyAvailableAppsForDebugging[0].appIdentifier,
+					isLastCheck = false;
+
+				applicationManager.on("debuggableViewFound", (appIdentifier: string, d: Mobile.IDebugWebViewInfo) => {
+					assert.deepEqual(appIdentifier, expectedAppIdentifier);
+					assert.isTrue(_.isEqual(d, expectedViewToBeFound));
+
+					if (isLastCheck) {
+						setTimeout(done, 0);
+					}
+				});
+
+				currentlyAvailableAppWebViewsForDebugging[expectedAppIdentifier].push(_.cloneDeep(expectedViewToBeFound));
+				applicationManager.checkForApplicationUpdates().wait();
+
+				expectedViewToBeFound = createDebuggableWebView("uniqueId1");
+				currentlyAvailableAppWebViewsForDebugging[expectedAppIdentifier].push(_.cloneDeep(expectedViewToBeFound));
+				applicationManager.checkForApplicationUpdates().wait();
+
+				expectedViewToBeFound = createDebuggableWebView("uniqueId2");
+				expectedAppIdentifier = currentlyAvailableAppsForDebugging[1].appIdentifier;
+				isLastCheck = true;
+
+				currentlyAvailableAppWebViewsForDebugging[expectedAppIdentifier].push(_.cloneDeep(expectedViewToBeFound));
+				applicationManager.checkForApplicationUpdates().wait();
+			});
+
+			it("emits debuggableViewLost when views for debug are not available anymore", (done) => {
+				currentlyAvailableAppsForDebugging = createAppsAvailableForDebugging(2);
+				let numberOfViewsPerApp = 2;
+				currentlyAvailableAppWebViewsForDebugging = createDebuggableWebViews(currentlyAvailableAppsForDebugging, numberOfViewsPerApp);
+
+				applicationManager.checkForApplicationUpdates().wait();
+
+				let expectedAppIdentifier = currentlyAvailableAppsForDebugging[0].appIdentifier,
+					expectedViewToBeLost = currentlyAvailableAppWebViewsForDebugging[expectedAppIdentifier].splice(0, 1)[0],
+					isLastCheck = false;
+
+				applicationManager.on("debuggableViewLost", (appIdentifier: string, d: Mobile.IDebugWebViewInfo) => {
+					assert.deepEqual(appIdentifier, expectedAppIdentifier);
+					assert.isTrue(_.isEqual(d, expectedViewToBeLost));
+
+					if (isLastCheck) {
+						setTimeout(done, 0);
+					}
+				});
+
+				applicationManager.checkForApplicationUpdates().wait();
+
+				expectedViewToBeLost = currentlyAvailableAppWebViewsForDebugging[expectedAppIdentifier].splice(0, 1)[0];
+				applicationManager.checkForApplicationUpdates().wait();
+
+				expectedAppIdentifier = currentlyAvailableAppsForDebugging[1].appIdentifier;
+				expectedViewToBeLost = currentlyAvailableAppWebViewsForDebugging[expectedAppIdentifier].splice(0, 1)[0];
+
+				isLastCheck = true;
+				applicationManager.checkForApplicationUpdates().wait();
+			});
+
+			it("emits debuggableViewChanged when view's property is modified (each one except id)", (done) => {
+				currentlyAvailableAppsForDebugging = createAppsAvailableForDebugging(1);
+				currentlyAvailableAppWebViewsForDebugging = createDebuggableWebViews(currentlyAvailableAppsForDebugging, 2);
+				let viewToChange = currentlyAvailableAppWebViewsForDebugging[currentlyAvailableAppsForDebugging[0].appIdentifier][0];
+				let expectedView = _.cloneDeep(viewToChange);
+				expectedView.title = "new title";
+
+				applicationManager.on("debuggableViewChanged", (appIdentifier: string, d: Mobile.IDebugWebViewInfo) => {
+					assert.isTrue(_.isEqual(d, expectedView));
+					setTimeout(done, 0);
+				});
+
+				applicationManager.checkForApplicationUpdates().wait();
+				viewToChange.title = "new title";
+				applicationManager.checkForApplicationUpdates().wait();
+			});
+
+			it("does not emit debuggableViewChanged when id is modified", (done) => {
+				currentlyAvailableAppsForDebugging = createAppsAvailableForDebugging(1);
+				currentlyAvailableAppWebViewsForDebugging = createDebuggableWebViews(currentlyAvailableAppsForDebugging, 2);
+				let viewToChange = currentlyAvailableAppWebViewsForDebugging[currentlyAvailableAppsForDebugging[0].appIdentifier][0];
+				let expectedView = _.cloneDeep(viewToChange);
+
+				applicationManager.checkForApplicationUpdates().wait();
+				applicationManager.on("debuggableViewChanged", (appIdentifier: string, d: Mobile.IDebugWebViewInfo) => {
+					setTimeout(() => done(new Error("When id is changed, debuggableViewChanged must not be emitted.")), 0);
+				});
+
+				applicationManager.on("debuggableViewLost", (appIdentifier: string, d: Mobile.IDebugWebViewInfo) => {
+					assert.isTrue(_.isEqual(d, expectedView));
+				});
+
+				applicationManager.on("debuggableViewFound", (appIdentifier: string, d: Mobile.IDebugWebViewInfo) => {
+					expectedView.id = "new id";
+					assert.isTrue(_.isEqual(d, expectedView));
+					setTimeout(done, 0);
+				});
+
+				viewToChange.id = "new id";
+				applicationManager.checkForApplicationUpdates().wait();
+			});
 		});
 
 		describe("installed and uninstalled apps", () => {
@@ -556,7 +748,7 @@ describe("ApplicationManagerBase", () => {
 			it("when startApplications throws", () => {
 				applicationManager.canStartApplication = () => true;
 				applicationManager.isApplicationInstalled = (appId: string) => Future.fromResult(true);
-				assertDoesNotThrow({shouldStartApplicatinThrow: true});
+				assertDoesNotThrow({ shouldStartApplicatinThrow: true });
 			});
 		});
 

From 198ae8eee48833bcdf3a6ca64e58afb5b04e3821 Mon Sep 17 00:00:00 2001
From: rosen-vladimirov <rosen.vladimirov@telerik.com>
Date: Thu, 14 Jul 2016 16:50:01 +0300
Subject: [PATCH 2/2] Set versiont to 0.18.0

---
 README.md    | 4 ++--
 package.json | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index c0a0289b..197624c7 100644
--- a/README.md
+++ b/README.md
@@ -7,9 +7,9 @@ Contains common infrastructure for CLIs - mainly AppBuilder and NativeScript.
 Installation
 ===
 
-Latest version: 0.17.3
+Latest version: 0.18.0
 
-Release date: 2016, July 12
+Release date: 2016, July 14
 
 ### System Requirements
 
diff --git a/package.json b/package.json
index 86206914..4922d35a 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "mobile-cli-lib",
   "preferGlobal": false,
-  "version": "0.17.3",
+  "version": "0.18.0",
   "author": "Telerik <support@telerik.com>",
   "description": "common lib used by different CLI",
   "bin": {