Skip to content

Commit

Permalink
grant and revoke app permission for Android 23 and above
Browse files Browse the repository at this point in the history
  • Loading branch information
sravanmedarapu committed Dec 1, 2016
1 parent ded1c1e commit f2876e6
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 0 deletions.
56 changes: 56 additions & 0 deletions lib/tools/adb-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,71 @@ methods.clear = async function (pkg) {
return await this.shell(['pm', 'clear', pkg]);
};

methods.grantAllPermissions = async function (pkg) {
let permissions = await this.getReqPermissions(pkg);
for (let permission of permissions) {
await this.grantPermission(pkg, permission);
}
};

methods.grantPermission = async function (pkg, permission) {
try {
await this.shell(['pm', 'grant', pkg, permission]);
} catch (error) {
if (!error.message.includes("not a changeable permission type")) {
throw error;
}
}
};

methods.revokePermission = async function (pkg, permission) {
try {
await this.shell(['pm', 'revoke', pkg, permission]);
} catch (error) {
if (!error.message.includes("not a changeable permission type")) {
throw error;
}
}
};

methods.getGrantedPermissions = async function (pkg) {
let stdout = await this.shell(['pm', 'dump', pkg]);
let reqPermissions = new RegExp(/install permissions:([\s\S]*?)DUMP OF SERVICE activity:/g).exec(stdout)[0].split("\n");
return await cleanUpReqPermissions(reqPermissions);
};

methods.getReqPermissions = async function (pkg) {
let stdout = await this.shell(['pm', 'dump', pkg]);
let reqPermissions = new RegExp(/requested permissions:([\s\S]*?)install permissions:/g).exec(stdout)[0].split("\n");
return await cleanUpReqPermissions(reqPermissions);
};

async function cleanUpReqPermissions (reqPermissions) {
return reqPermissions
.filter(p => p.trim().startsWith('android.permission'))
.map(p => p.trim().replace(': granted=true', ''));
}

methods.stopAndClear = async function (pkg) {
try {
await this.forceStop(pkg);
await this.clear(pkg);
let apiLevel = await this.getApiLevel();
let targetSdk = await this.getTargetSdkUsingPKG(pkg);
if (apiLevel >= 23 && targetSdk >= 23) {
await this.grantAllPermissions(pkg);
}
} catch (e) {
log.errorAndThrow(`Cannot stop and clear ${pkg}. Original error: ${e.message}`);
}
};

methods.getTargetSdkUsingPKG = async function (pkg) {
let stdout = await this.shell(['pm', 'dump', pkg]);
let targetSdk = new RegExp(/targetSdk=([^\s\s]+)/g).exec(stdout)[1];
return targetSdk;
};

methods.availableIMEs = async function () {
try {
return getIMEListFromOutput(await this.shell(['ime', 'list', '-a']));
Expand Down
13 changes: 13 additions & 0 deletions lib/tools/android-manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ manifestMethods.packageAndLaunchActivityFromManifest = async function (localApk)
}
};

manifestMethods.targetSdkVersionFromManifest = async function (localApk) {
try {
await this.initAapt();
log.info("Extracting package and launch activity from manifest");
let args = ['dump', 'badging', localApk];
let {stdout} = await exec(this.binaries.aapt, args);
let targetSdkVersion = new RegExp(/targetSdkVersion:'([^']+)'/g).exec(stdout);
return parseInt(targetSdkVersion[1], 10);
} catch (e) {
log.errorAndThrow(`targetSdkVersionFromManifest failed. Original error: ${e.message}`);
}
};

manifestMethods.compileManifest = async function (manifest, manifestPackage, targetPackage) {
log.debug(`Compiling manifest ${manifest}`);
let {platform, platformPath} = await getAndroidPlatformAndPath();
Expand Down
20 changes: 20 additions & 0 deletions lib/tools/apk-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ apkUtilsMethods.install = async function (apk, replace = true, timeout = 60000)
log.debug(`Application '${apk}' already installed. Continuing.`);
}
}
if (apk.includes('.apk')) {
let apiLevel = await this.getApiLevel();
let targetSdk = await this.targetSdkVersionFromManifest(apk);
if (apiLevel >= 23 && targetSdk >= 23) {
await this.grantAllPermissions(await this.getPackageName(apk));
}
}
};

apkUtilsMethods.extractStringsFromApk = async function (apk, language, out) {
Expand Down Expand Up @@ -274,4 +281,17 @@ apkUtilsMethods.setDeviceLocale = async function (locale) {
await this.setDeviceSysLocale(locale);
};

apkUtilsMethods.getPackageName = async function (apk) {
let args = ['dump', 'badging', apk];
await this.initAapt();
let {stdout} = await exec(this.binaries.aapt, args);
let apkPackage = new RegExp(/package: name='([^']+)'/g).exec(stdout);
if (apkPackage && apkPackage.length >= 2) {
apkPackage = apkPackage[1];
} else {
apkPackage = null;
}
return apkPackage;
};

export default apkUtilsMethods;
Binary file added test/fixtures/ApiDemos-debug.apk
Binary file not shown.
31 changes: 31 additions & 0 deletions test/functional/adb-commands-e2e-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const apiLevel = '18',
'fixtures', 'ContactManager.apk'),
pkg = 'com.example.android.contactmanager',
activity = 'ContactManager';
let expect = chai.expect;

describe('adb commands', function () {
let adb;
Expand Down Expand Up @@ -98,4 +99,34 @@ describe('adb commands', function () {
logs.should.have.length.above(0);
await adb.stopLogcat();
});
describe('app permissions', async () => {
before(async function () {
let deviceApiLevel = await adb.getApiLevel();
if (deviceApiLevel < 23) {
//test should skip if the device API < 23
this.skip();
}
let isInstalled = await adb.isAppInstalled('io.appium.android.apis');
if (isInstalled) {
await adb.uninstallApk('io.appium.android.apis');
}
});
it('should install and grant all permission', async () => {
let apiDemos = path.resolve(rootDir, 'test',
'fixtures', 'ApiDemos-debug.apk');
await adb.install(apiDemos);
(await adb.isAppInstalled('io.appium.android.apis')).should.be.true;
let requestedPermissions = await adb.getReqPermissions('io.appium.android.apis');
expect(await adb.getGrantedPermissions('io.appium.android.apis')).to.have.members(requestedPermissions);
});
it('should revoke permission', async () => {
await adb.revokePermission('io.appium.android.apis', 'android.permission.RECEIVE_SMS');
expect(await adb.getGrantedPermissions('io.appium.android.apis')).to.not.have.members(['android.permission.RECEIVE_SMS']);
});
it('should grant permission', async () => {
await adb.grantPermission('io.appium.android.apis', 'android.permission.RECEIVE_SMS');
expect(await adb.getGrantedPermissions('io.appium.android.apis')).to.include.members(['android.permission.RECEIVE_SMS']);
});
});
});

14 changes: 14 additions & 0 deletions test/unit/adb-commands-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,20 @@ describe('adb commands', () => {
});
}));
});
describe('app permission', withMocks({adb}, (mocks) => {
it('should grant requested permission', async () => {
mocks.adb.expects("shell")
.once().withArgs(['pm', 'grant', 'io.appium.android.apis', 'android.permission.READ_EXTERNAL_STORAGE']);
await adb.grantPermission('io.appium.android.apis', 'android.permission.READ_EXTERNAL_STORAGE');
mocks.adb.verify();
});
it('should revoke requested permission', async () => {
mocks.adb.expects("shell")
.once().withArgs(['pm', 'revoke', 'io.appium.android.apis', 'android.permission.READ_EXTERNAL_STORAGE']);
await adb.revokePermission('io.appium.android.apis', 'android.permission.READ_EXTERNAL_STORAGE');
mocks.adb.verify();
});
}));
describe('sendTelnetCommand', withMocks({adb, net}, (mocks) => {
it('should call shell with correct args', async () => {
const port = 54321;
Expand Down

0 comments on commit f2876e6

Please sign in to comment.