diff --git a/lib/common/mobile/mobile-core/devices-service.ts b/lib/common/mobile/mobile-core/devices-service.ts index a9b9330578..30d04dc344 100644 --- a/lib/common/mobile/mobile-core/devices-service.ts +++ b/lib/common/mobile/mobile-core/devices-service.ts @@ -13,6 +13,7 @@ import { performanceLog } from "../../decorators"; export class DevicesService extends EventEmitter implements Mobile.IDevicesService { private static DEVICE_LOOKING_INTERVAL = 200; + private static EMULATOR_IMAGES_DETECTION_INTERVAL = 60 * 1000; private _devices: IDictionary = {}; private _availableEmulators: IDictionary = {}; private _platform: string; @@ -21,8 +22,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi private _data: Mobile.IDevicesServicesInitializationOptions; private _otherDeviceDiscoveries: Mobile.IDeviceDiscovery[] = []; private _allDeviceDiscoveries: Mobile.IDeviceDiscovery[] = []; - private deviceDetectionInterval: any; - private isDeviceDetectionIntervalInProgress: boolean; + private deviceDetectionIntervals: NodeJS.Timer[] = []; constructor(private $logger: ILogger, private $errors: IErrors, @@ -287,43 +287,51 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi } } - protected async startDeviceDetectionInterval(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise { + protected async startDeviceDetectionIntervals(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise { this.$processService.attachToProcessExitSignals(this, this.clearDeviceDetectionInterval); - if (this.deviceDetectionInterval) { - this.$logger.trace("Device detection interval is already started. New Interval will not be started."); + if (this.deviceDetectionIntervals.length) { + this.$logger.trace("Device detection intervals are already started. New intervals will not be started."); return; } - let isFirstExecution = true; - return new Promise((resolve, reject) => { - this.deviceDetectionInterval = setInterval(async () => { - if (this.isDeviceDetectionIntervalInProgress) { - return; - } + let isDeviceDetectionIntervalInProgress = false; + const deviceDetectionInterval = setInterval(async () => { + if (isDeviceDetectionIntervalInProgress) { + return; + } - this.isDeviceDetectionIntervalInProgress = true; + isDeviceDetectionIntervalInProgress = true; - await this.detectCurrentlyAttachedDevices(deviceInitOpts); - await this.detectCurrentlyAvailableEmulators(); + await this.detectCurrentlyAttachedDevices(deviceInitOpts); - try { - const trustedDevices = _.filter(this._devices, device => device.deviceInfo.status === constants.CONNECTED_STATUS); - await settlePromises(_.map(trustedDevices, device => device.applicationManager.checkForApplicationUpdates())); - } catch (err) { - this.$logger.trace("Error checking for application updates on devices.", err); - } + try { + const trustedDevices = _.filter(this._devices, device => device.deviceInfo.status === constants.CONNECTED_STATUS); + await settlePromises(_.map(trustedDevices, device => device.applicationManager.checkForApplicationUpdates())); + } catch (err) { + this.$logger.trace("Error checking for application updates on devices.", err); + } - if (isFirstExecution) { - isFirstExecution = false; - resolve(); - this.deviceDetectionInterval.unref(); - } + isDeviceDetectionIntervalInProgress = false; - this.isDeviceDetectionIntervalInProgress = false; + }, DevicesService.DEVICE_LOOKING_INTERVAL); - }, DevicesService.DEVICE_LOOKING_INTERVAL); - }); + deviceDetectionInterval.unref(); + this.deviceDetectionIntervals.push(deviceDetectionInterval); + + let isEmulatorDetectionIntervalRunning = false; + const emulatorDetectionInterval = setInterval(async () => { + if (isEmulatorDetectionIntervalRunning) { + return; + } + + isEmulatorDetectionIntervalRunning = true; + await this.detectCurrentlyAvailableEmulators(); + isEmulatorDetectionIntervalRunning = false; + }, DevicesService.EMULATOR_IMAGES_DETECTION_INTERVAL); + + emulatorDetectionInterval.unref(); + this.deviceDetectionIntervals.push(emulatorDetectionInterval); } /** @@ -348,14 +356,17 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi /** * Starts looking for running devices. All found devices are pushed to _devices variable. */ - private startLookingForDevices(deviceInitOpts?: Mobile.IDevicesServicesInitializationOptions): Promise { + private async startLookingForDevices(deviceInitOpts?: Mobile.IDevicesServicesInitializationOptions): Promise { this.$logger.trace("startLookingForDevices; platform is %s", this._platform); if (this._platform) { return this.detectCurrentlyAttachedDevices(deviceInitOpts); } - return this.startDeviceDetectionInterval(deviceInitOpts); + await this.detectCurrentlyAttachedDevices(deviceInitOpts); + await this.detectCurrentlyAvailableEmulators(); + + await this.startDeviceDetectionIntervals(deviceInitOpts); } /** @@ -690,10 +701,14 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi } private clearDeviceDetectionInterval(): void { - if (this.deviceDetectionInterval) { - clearInterval(this.deviceDetectionInterval); + if (this.deviceDetectionIntervals.length) { + for (const interval of this.deviceDetectionIntervals) { + clearInterval(interval); + } + + this.deviceDetectionIntervals.splice(0, this.deviceDetectionIntervals.length); } else { - this.$logger.trace("Device detection interval is not started, so it cannot be stopped."); + this.$logger.trace("Device detection intervals are not started, so it cannot be stopped."); } } diff --git a/lib/common/test/unit-tests/mobile/devices-service.ts b/lib/common/test/unit-tests/mobile/devices-service.ts index 6735332eda..fe7df258ff 100644 --- a/lib/common/test/unit-tests/mobile/devices-service.ts +++ b/lib/common/test/unit-tests/mobile/devices-service.ts @@ -28,8 +28,8 @@ class DevicesServiceInheritor extends DevicesService { return super.startEmulatorIfNecessary(data); } - public startDeviceDetectionInterval(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise { - return super.startDeviceDetectionInterval(deviceInitOpts); + public startDeviceDetectionIntervals(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise { + return super.startDeviceDetectionIntervals(deviceInitOpts); } public detectCurrentlyAttachedDevices(options?: Mobile.IDevicesServicesInitializationOptions): Promise { @@ -236,6 +236,15 @@ function resetDefaultSetInterval(): void { global.setInterval = originalSetInterval; } +async function assertOnNextTick(assertionFunction: Function): Promise { + await new Promise(resolve => { + process.nextTick(() => { + assertionFunction(); + resolve(); + }); + }); +} + describe("devicesService", () => { let counter = 0; const iOSDevice = { @@ -1184,7 +1193,7 @@ describe("devicesService", () => { }); }); - describe("startDeviceDetectionInterval", () => { + describe("startDeviceDetectionIntervals", () => { let setIntervalsCalledCount: number; beforeEach(() => { @@ -1203,7 +1212,7 @@ describe("devicesService", () => { hasStartedDeviceDetectionInterval = true; }); - await devicesService.startDeviceDetectionInterval(); + await devicesService.startDeviceDetectionIntervals(); assert.isTrue(hasStartedDeviceDetectionInterval); }); @@ -1227,11 +1236,11 @@ describe("devicesService", () => { }; }; - await devicesService.startDeviceDetectionInterval(); - await devicesService.startDeviceDetectionInterval(); - await devicesService.startDeviceDetectionInterval(); + await devicesService.startDeviceDetectionIntervals(); + await devicesService.startDeviceDetectionIntervals(); + await devicesService.startDeviceDetectionIntervals(); - assert.deepEqual(setIntervalsCalledCount, 1); + assert.deepEqual(setIntervalsCalledCount, 2); }); describe("ios devices check", () => { @@ -1248,7 +1257,7 @@ describe("devicesService", () => { hasCheckedForIosDevices = true; }; - await devicesService.startDeviceDetectionInterval(); + await devicesService.startDeviceDetectionIntervals(); assert.isTrue(hasCheckedForIosDevices); }); @@ -1256,7 +1265,7 @@ describe("devicesService", () => { it("should not throw if ios device check fails throws an exception.", async () => { ($iOSDeviceDiscovery).checkForDevices = throwErrorFunction; - await assert.isFulfilled(devicesService.startDeviceDetectionInterval()); + await assert.isFulfilled(devicesService.startDeviceDetectionIntervals()); }); }); @@ -1267,22 +1276,22 @@ describe("devicesService", () => { $androidDeviceDiscovery = testInjector.resolve("androidDeviceDiscovery"); }); - it("should check for android devices.", async () => { + it("should start interval that will check for android devices.", async () => { let hasCheckedForAndroidDevices = false; $androidDeviceDiscovery.startLookingForDevices = async (): Promise => { hasCheckedForAndroidDevices = true; }; - await devicesService.startDeviceDetectionInterval(); - - assert.isTrue(hasCheckedForAndroidDevices); + mockSetInterval(); + await devicesService.startDeviceDetectionIntervals(); + await assertOnNextTick(() => assert.isTrue(hasCheckedForAndroidDevices)); }); it("should not throw if android device check fails throws an exception.", async () => { $androidDeviceDiscovery.startLookingForDevices = throwErrorFunction; - await assert.isFulfilled(devicesService.startDeviceDetectionInterval()); + await assert.isFulfilled(devicesService.startDeviceDetectionIntervals()); }); }); @@ -1305,7 +1314,7 @@ describe("devicesService", () => { it("should not throw if ios simulator check fails throws an exception.", async () => { ($iOSSimulatorDiscovery).checkForDevices = throwErrorFunction; - await assert.isFulfilled(devicesService.startDeviceDetectionInterval()); + await assert.isFulfilled(devicesService.startDeviceDetectionIntervals()); }); }); @@ -1317,22 +1326,23 @@ describe("devicesService", () => { devicesService.addDeviceDiscovery(customDeviceDiscovery); }); - it("should check for devices.", async () => { + it("should check for devices in interval", async () => { let hasCheckedForDevices = false; customDeviceDiscovery.startLookingForDevices = async (): Promise => { hasCheckedForDevices = true; }; - await devicesService.startDeviceDetectionInterval(); + mockSetInterval(); + await devicesService.startDeviceDetectionIntervals(); - assert.isTrue(hasCheckedForDevices); + await assertOnNextTick(() => assert.isTrue(hasCheckedForDevices)); }); it("should not throw if device check fails throws an exception.", async () => { customDeviceDiscovery.startLookingForDevices = throwErrorFunction; - await assert.isFulfilled(devicesService.startDeviceDetectionInterval()); + await assert.isFulfilled(devicesService.startDeviceDetectionIntervals()); }); }); @@ -1361,26 +1371,30 @@ describe("devicesService", () => { }); it("should check for application updates for all connected devices.", async () => { - await devicesService.startDeviceDetectionInterval(); + await devicesService.startDeviceDetectionIntervals(); - assert.isTrue(hasCheckedForAndroidAppUpdates); - assert.isTrue(hasCheckedForIosAppUpdates); + await assertOnNextTick(() => { + assert.isTrue(hasCheckedForAndroidAppUpdates); + assert.isTrue(hasCheckedForIosAppUpdates); + }); }); it("should check for application updates if the check on one device throws an exception.", async () => { iOSDevice.applicationManager.checkForApplicationUpdates = throwErrorFunction; - await devicesService.startDeviceDetectionInterval(); + await devicesService.startDeviceDetectionIntervals(); - assert.isTrue(hasCheckedForAndroidAppUpdates); + await assertOnNextTick(() => assert.isTrue(hasCheckedForAndroidAppUpdates)); }); it("should check for application updates only on devices with status Connected", async () => { androidDevice.deviceInfo.status = constants.UNREACHABLE_STATUS; - await devicesService.startDeviceDetectionInterval(); + await devicesService.startDeviceDetectionIntervals(); - assert.isFalse(hasCheckedForAndroidAppUpdates); - assert.isTrue(hasCheckedForIosAppUpdates); + await assertOnNextTick(() => { + assert.isFalse(hasCheckedForAndroidAppUpdates); + assert.isTrue(hasCheckedForIosAppUpdates); + }); }); it("should not throw if all checks for application updates on all devices throw exceptions.", () => { @@ -1388,7 +1402,7 @@ describe("devicesService", () => { androidDevice.applicationManager.checkForApplicationUpdates = throwErrorFunction; const callback = () => { - devicesService.startDeviceDetectionInterval.call(devicesService); + devicesService.startDeviceDetectionIntervals.call(devicesService); }; assert.doesNotThrow(callback);