diff --git a/bin/templates/cordova/lib/device.js b/bin/templates/cordova/lib/device.js
index 84b50947..b186f817 100644
--- a/bin/templates/cordova/lib/device.js
+++ b/bin/templates/cordova/lib/device.js
@@ -19,7 +19,6 @@
        under the License.
 */
 
-var Q = require('q');
 var build = require('./build');
 var path = require('path');
 var Adb = require('./Adb');
@@ -53,13 +52,13 @@ module.exports.list = function (lookHarder) {
 module.exports.resolveTarget = function (target) {
     return this.list(true).then(function (device_list) {
         if (!device_list || !device_list.length) {
-            return Q.reject(new CordovaError('Failed to deploy to device, no devices found.'));
+            return Promise.reject(new CordovaError('Failed to deploy to device, no devices found.'));
         }
         // default device
         target = target || device_list[0];
 
         if (device_list.indexOf(target) < 0) {
-            return Q.reject('ERROR: Unable to find target \'' + target + '\'.');
+            return Promise.reject(new CordovaError('ERROR: Unable to find target \'' + target + '\'.'));
         }
 
         return build.detectArchitecture(target).then(function (arch) {
@@ -74,7 +73,7 @@ module.exports.resolveTarget = function (target) {
  * Returns a promise.
  */
 module.exports.install = function (target, buildResults) {
-    return Q().then(function () {
+    return Promise.resolve().then(function () {
         if (target && typeof target === 'object') {
             return target;
         }
diff --git a/bin/templates/cordova/lib/install-device b/bin/templates/cordova/lib/install-device
index 48b03f81..03873883 100755
--- a/bin/templates/cordova/lib/install-device
+++ b/bin/templates/cordova/lib/install-device
@@ -26,7 +26,7 @@ if (args.length > 2) {
     var install_target;
     if (args[2].substring(0, 9) === '--target=') {
         install_target = args[2].substring(9, args[2].length);
-        device.install(install_target).done(null, function (err) {
+        device.install(install_target).catch(function (err) {
             console.error('ERROR: ' + err);
             process.exit(2);
         });
@@ -35,7 +35,7 @@ if (args.length > 2) {
         process.exit(2);
     }
 } else {
-    device.install().done(null, function (err) {
+    device.install().catch(function (err) {
         console.error('ERROR: ' + err);
         process.exit(2);
     });
diff --git a/bin/templates/cordova/lib/list-devices b/bin/templates/cordova/lib/list-devices
index e0f38211..339c6658 100755
--- a/bin/templates/cordova/lib/list-devices
+++ b/bin/templates/cordova/lib/list-devices
@@ -23,7 +23,7 @@ var devices = require('./device');
 
 // Usage support for when args are given
 require('./check_reqs').check_android().then(function () {
-    devices.list().done(function (device_list) {
+    devices.list().then(function (device_list) {
         device_list && device_list.forEach(function (dev) {
             console.log(dev);
         });
diff --git a/spec/unit/device.spec.js b/spec/unit/device.spec.js
new file mode 100644
index 00000000..27b6c62c
--- /dev/null
+++ b/spec/unit/device.spec.js
@@ -0,0 +1,233 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+const rewire = require('rewire');
+
+const CordovaError = require('cordova-common').CordovaError;
+
+describe('device', () => {
+    const DEVICE_LIST = ['device1', 'device2', 'device3'];
+    let AdbSpy;
+    let device;
+
+    beforeEach(() => {
+        device = rewire('../../bin/templates/cordova/lib/device');
+        AdbSpy = jasmine.createSpyObj('Adb', ['devices', 'install', 'shell', 'start', 'uninstall']);
+        device.__set__('Adb', AdbSpy);
+    });
+
+    describe('list', () => {
+        it('should return the list from adb devices', () => {
+            AdbSpy.devices.and.returnValue(Promise.resolve(DEVICE_LIST));
+
+            return device.list().then(list => {
+                expect(list).toEqual(DEVICE_LIST);
+            });
+        });
+
+        it('should kill adb and try to get devices again if none are found the first time, and `lookHarder` is set', () => {
+            const spawnSpy = jasmine.createSpy('spawn').and.returnValue(Promise.resolve());
+            device.__set__('spawn', spawnSpy);
+            AdbSpy.devices.and.returnValues(Promise.resolve([]), Promise.resolve(DEVICE_LIST));
+
+            return device.list(true).then(list => {
+                expect(spawnSpy).toHaveBeenCalledWith('killall', ['adb']);
+                expect(list).toBe(DEVICE_LIST);
+                expect(AdbSpy.devices).toHaveBeenCalledTimes(2);
+            });
+        });
+
+        it('should return the empty list if killing adb fails', () => {
+            const emptyDevices = [];
+            const spawnSpy = jasmine.createSpy('spawn').and.returnValue(Promise.reject());
+            device.__set__('spawn', spawnSpy);
+            AdbSpy.devices.and.returnValues(Promise.resolve(emptyDevices));
+
+            return device.list(true).then(list => {
+                expect(spawnSpy).toHaveBeenCalledWith('killall', ['adb']);
+                expect(list).toBe(emptyDevices);
+                expect(AdbSpy.devices).toHaveBeenCalledTimes(1);
+            });
+        });
+    });
+
+    describe('resolveTarget', () => {
+        let buildSpy;
+
+        beforeEach(() => {
+            buildSpy = jasmine.createSpyObj('build', ['detectArchitecture']);
+            buildSpy.detectArchitecture.and.returnValue(Promise.resolve());
+            device.__set__('build', buildSpy);
+
+            spyOn(device, 'list').and.returnValue(Promise.resolve(DEVICE_LIST));
+        });
+
+        it('should select the first device to be the target if none is specified', () => {
+            return device.resolveTarget().then(deviceInfo => {
+                expect(deviceInfo.target).toBe(DEVICE_LIST[0]);
+            });
+        });
+
+        it('should use the given target instead of the default', () => {
+            return device.resolveTarget(DEVICE_LIST[2]).then(deviceInfo => {
+                expect(deviceInfo.target).toBe(DEVICE_LIST[2]);
+            });
+        });
+
+        it('should set emulator to false', () => {
+            return device.resolveTarget().then(deviceInfo => {
+                expect(deviceInfo.isEmulator).toBe(false);
+            });
+        });
+
+        it('should throw an error if there are no devices', () => {
+            device.list.and.returnValue(Promise.resolve([]));
+
+            return device.resolveTarget().then(
+                () => fail('Unexpectedly resolved'),
+                err => {
+                    expect(err).toEqual(jasmine.any(CordovaError));
+                }
+            );
+        });
+
+        it('should throw an error if the specified target does not exist', () => {
+            return device.resolveTarget('nonexistent-target').then(
+                () => fail('Unexpectedly resolved'),
+                err => {
+                    expect(err).toEqual(jasmine.any(CordovaError));
+                }
+            );
+        });
+
+        it('should detect the architecture and return it with the device info', () => {
+            const target = DEVICE_LIST[1];
+            const arch = 'unittestarch';
+
+            buildSpy.detectArchitecture.and.returnValue(Promise.resolve(arch));
+
+            return device.resolveTarget(target).then(deviceInfo => {
+                expect(buildSpy.detectArchitecture).toHaveBeenCalledWith(target);
+                expect(deviceInfo.arch).toBe(arch);
+            });
+        });
+    });
+
+    describe('install', () => {
+        let AndroidManifestSpy;
+        let AndroidManifestFns;
+        let AndroidManifestGetActivitySpy;
+        let buildSpy;
+        let target;
+
+        beforeEach(() => {
+            target = { target: DEVICE_LIST[0], arch: 'arm7', isEmulator: false };
+
+            buildSpy = jasmine.createSpyObj('build', ['findBestApkForArchitecture']);
+            device.__set__('build', buildSpy);
+
+            AndroidManifestFns = jasmine.createSpyObj('AndroidManifestFns', ['getPackageId', 'getActivity']);
+            AndroidManifestGetActivitySpy = jasmine.createSpyObj('getActivity', ['getName']);
+            AndroidManifestFns.getActivity.and.returnValue(AndroidManifestGetActivitySpy);
+            AndroidManifestSpy = jasmine.createSpy('AndroidManifest').and.returnValue(AndroidManifestFns);
+            device.__set__('AndroidManifest', AndroidManifestSpy);
+
+            AdbSpy.install.and.returnValue(Promise.resolve());
+            AdbSpy.shell.and.returnValue(Promise.resolve());
+            AdbSpy.start.and.returnValue(Promise.resolve());
+        });
+
+        it('should get the full target object if only id is specified', () => {
+            const targetId = DEVICE_LIST[0];
+            spyOn(device, 'resolveTarget').and.returnValue(Promise.resolve(target));
+
+            return device.install(targetId).then(() => {
+                expect(device.resolveTarget).toHaveBeenCalledWith(targetId);
+            });
+        });
+
+        it('should install to the passed target', () => {
+            return device.install(target).then(() => {
+                expect(AdbSpy.install).toHaveBeenCalledWith(target.target, undefined, jasmine.anything());
+            });
+        });
+
+        it('should install the correct apk based on the architecture and build results', () => {
+            const buildResults = {
+                apkPaths: 'path/to/apks',
+                buildType: 'debug',
+                buildMethod: 'foo'
+            };
+
+            const apkPath = 'my/apk/path/app.apk';
+            buildSpy.findBestApkForArchitecture.and.returnValue(apkPath);
+
+            return device.install(target, buildResults).then(() => {
+                expect(buildSpy.findBestApkForArchitecture).toHaveBeenCalledWith(buildResults, target.arch);
+                expect(AdbSpy.install).toHaveBeenCalledWith(jasmine.anything(), apkPath, jasmine.anything());
+            });
+        });
+
+        it('should uninstall and reinstall app if failure is due to different certificates', () => {
+            AdbSpy.install.and.returnValues(
+                Promise.reject('Failed to install: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES'),
+                Promise.resolve()
+            );
+
+            AdbSpy.uninstall.and.callFake(() => {
+                expect(AdbSpy.install).toHaveBeenCalledTimes(1);
+                return Promise.resolve();
+            });
+
+            return device.install(target).then(() => {
+                expect(AdbSpy.install).toHaveBeenCalledTimes(2);
+                expect(AdbSpy.uninstall).toHaveBeenCalled();
+            });
+        });
+
+        it('should throw any error not caused by different certificates', () => {
+            const errorMsg = new CordovaError('Failed to install');
+            AdbSpy.install.and.returnValues(Promise.reject(errorMsg));
+
+            return device.install(target).then(
+                () => fail('Unexpectedly resolved'),
+                err => {
+                    expect(err).toBe(errorMsg);
+                }
+            );
+        });
+
+        it('should unlock the screen on device', () => {
+            return device.install(target).then(() => {
+                expect(AdbSpy.shell).toHaveBeenCalledWith(target.target, 'input keyevent 82');
+            });
+        });
+
+        it('should start the newly installed app on the device', () => {
+            const packageId = 'unittestapp';
+            const activityName = 'TestActivity';
+            AndroidManifestFns.getPackageId.and.returnValue(packageId);
+            AndroidManifestGetActivitySpy.getName.and.returnValue(activityName);
+
+            return device.install(target).then(() => {
+                expect(AdbSpy.start).toHaveBeenCalledWith(target.target, `${packageId}/.${activityName}`);
+            });
+        });
+    });
+});