最近拿到高通的新机器,开发中突然有同事发现一个问题,Contacts替换后android.permission.CALL_PHONE权限获取不到,系统的联系人应用居然没法打电话
看日志中拉起GrantPermissionsActivity的intent是已经发送的,那么就看权限对话框为何没有出现。
android/packages/apps/PackageInstaller/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
public void onCreate(Bundle icicle) {
...
if (!showNextPermissionGroupGrantRequest()) {
setResultAndFinish();
}
}
添加日志看到onCreate末尾的if语句条件符合所以activity直接finish了,那么就是系统判断无需申请权限
private boolean showNextPermissionGroupGrantRequest() {
final int groupCount = mRequestGrantPermissionGroups.size();
int currentIndex = 0;
for (GroupState groupState : mRequestGrantPermissionGroups.values()) {
if (groupState.mState == GroupState.STATE_UNKNOWN) {
...
return true;
}
currentIndex++;
}
return false;
}
如果mRequestGrantPermissionGroups不是空的话,会返回true的,那即是mRequestGrantPermissionGroups中没有任何元素
public void onCreate(Bundle icicle) {
...
for (String requestedPermission : mRequestedPermissions) {
Log.i(LOG_TAG, "requestedPermission = " + requestedPermission );
AppPermissionGroup group = null;
//mAppPermissions是app中AndroidManifest中声明的权限
for (AppPermissionGroup nextGroup : mAppPermissions.getPermissionGroups()) {
if (nextGroup.hasPermission(requestedPermission)) {
group = nextGroup;
break;
}
}
if (group == null) {
continue;
}
// We allow the user to choose only non-fixed permissions. A permission
// is fixed either by device policy or the user denying with prejudice.
if (!group.isUserFixed() && !group.isPolicyFixed()) {
switch (permissionPolicy) {
...
default: {
if (AppPermissionGroup.isStrictOpEnable()) {
//这里是从系统属性判断,adb shell getprop命令查看这个是false的
if (!group.checkRuntimePermission(null)) {
mRequestGrantPermissionGroups.put(group.getName(),
new GroupState(group));
} else {
group.grantRuntimePermissions(false);
updateGrantResults(group);
}
} else {
if (!group.areRuntimePermissionsGranted()) {
//日志查看最终是这条分支没有走
mRequestGrantPermissionGroups.put(group.getName(),
new GroupState(group));
} else {
group.grantRuntimePermissions(false);
updateGrantResults(group);
}
}
}
break;
}
} else {
Log.i(LOG_TAG, "requestedPermission22222 = " );
// if the permission is fixed, ensure that we return the right request result
updateGrantResults(group);
}
}
...
}
mRequestGrantPermissionGroups是在onCreate中添加元素的,日志分析结果居然是权限组已经是赋权的状态。
packages/apps/PackageInstaller/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java
public boolean areRuntimePermissionsGranted() {
return areRuntimePermissionsGranted(null);
}
public boolean areRuntimePermissionsGranted(String[] filterPermissions) {
if (LocationUtils.isLocationGroupAndProvider(mName, mPackageInfo.packageName)) {
return LocationUtils.isLocationEnabled(mContext);
}
final int permissionCount = mPermissions.size();
for (int i = 0; i < permissionCount; i++) {
Permission permission = mPermissions.valueAt(i);
if (filterPermissions != null
&& !ArrayUtils.contains(filterPermissions, permission.getName())) {
continue; //filterPermissions过滤掉了不参加判断的权限
}
if (mAppSupportsRuntimePermissions) {
if (permission.isGranted()) {
//权限组中只有有一条是赋权的,既满足条件
return true;
}
} else if (permission.isGranted() && (permission.getAppOp() == null
|| permission.isAppOpAllowed())) {
return true;
}
}
return false;
}
从代码看出权限组中只要有一条权限满足,就返回true
权限组和权限的概念可百度,基本的一条是如果已经给予权限组中的一条权限,那么后续同组的其它权限申请时会直接给予,而不用弹框。
例如拨号权限
frameworks/base/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
private static final Set PHONE_PERMISSIONS = new ArraySet<>();
static {
PHONE_PERMISSIONS.add(Manifest.permission.READ_PHONE_STATE);
PHONE_PERMISSIONS.add(Manifest.permission.CALL_PHONE);
PHONE_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG);
PHONE_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG);
PHONE_PERMISSIONS.add(Manifest.permission.ADD_VOICEMAIL);
PHONE_PERMISSIONS.add(Manifest.permission.USE_SIP);
PHONE_PERMISSIONS.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
}
可见PHONE组权限有7条
日志分析问题手机Contact中READ_PHONE_STATE权限是有效的,但是其余权限无效,所以导致申请权限每次都会直接finish掉。
对比问题手机基线代码和另一平台代码,发现mRequestGrantPermissionGroups的很多if判断是多出来的,尚不确定是google还是高通添加的。
是AndroidManifest中一条属性
对比代码发现高通的Contacts中没有 android:sharedUserId="android.uid.shared"这一条
删除后编译app并且push权限问题解决。在第一次使用的时候会申请全部的五条权限,而有share uid的Contacts.apk只会申请三条
packages/apps/ContactsCommon/src/com/android/contacts/common/activity/RequestPermissionsActivity.java
@Override
protected String[] getDesiredPermissions() {
return new String[]{
permission.ACCESS_FINE_LOCATION, // Location Group
permission.READ_CONTACTS, // Contacts group
permission.READ_CALL_LOG, // Permission group phone
permission.READ_CALENDAR, // Calendar group
permission.READ_SMS, // SMS group
};
}
Contacts申请权限的代码之前我有文章讲解过。
如代码中,有五个权限组,其中有share uid后Contacts group和Phone group的权限就不会弹框提示。
这个短短的一条属性如何会影响权限,其实这个在系统设置中可以看到一点端倪的。在系统设置中Dialer被当做普通程序,而Contact被当做系统应用
即有share uid且签名也是share签名,会被当做是系统应用。那么可以推测系统应用Android会特殊处理,例如默认赋予某些权限。例如Contacts默认拥有Contacts group和Phone group的权限不是非常自然的事情吗。研究下源码发现我的推测是正确的
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@Override
public void systemReady() {
...
for (int userId : grantPermissionsUserIds) {
mDefaultPermissionPolicy.grantDefaultPermissions(userId);
}
...
}
包管理器在systemReady回调中启动了特殊uid apk的赋权
frameworks/base/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
public void grantDefaultPermissions(int userId) {
grantPermissionsToSysComponentsAndPrivApps(userId);
grantDefaultSystemHandlerPermissions(userId);
grantDefaultPermissionExceptions(userId);
}
private void grantDefaultSystemHandlerPermissions(int userId) {
...
// Contacts
Intent contactsIntent = new Intent(Intent.ACTION_MAIN);
contactsIntent.addCategory(Intent.CATEGORY_APP_CONTACTS);
PackageParser.Package contactsPackage = getDefaultSystemHandlerActivityPackageLPr(
contactsIntent, userId);
if (contactsPackage != null
&& doesPackageSupportRuntimePermissions(contactsPackage)) {
grantRuntimePermissionsLPw(contactsPackage, CONTACTS_PERMISSIONS, userId);
grantRuntimePermissionsLPw(contactsPackage, PHONE_PERMISSIONS, userId);
}
...
}
Contacts被赋予了Contacts group和Phone group的权限
按照上述分析,有shared uid的话Phone group权限全部该赋权才对,为啥问题手机只有一条权限有效?还得继续分析源码
private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set permissions,
boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
...
List requestedPermissions = pkg.requestedPermissions;
Set grantablePermissions = null;
// If this is the default Phone or SMS app we grant permissions regardless
// whether the version on the system image declares the permission as used since
// selecting the app as the default Phone or SMS the user makes a deliberate
// choice to grant this app the permissions needed to function. For all other
// apps, (default grants on first boot and user creation) we don't grant default
// permissions if the version on the system image does not declare them.
if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) {
//isDefaultPhoneOrSms对Contacts来说当然是false的
//由于Contacts是push到手机替换原有Contacts的,所以这里肯定会走
PackageSetting sysPs = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
if (sysPs != null) {
if (sysPs.pkg.requestedPermissions.isEmpty()) {
return;
}
if (!requestedPermissions.equals(sysPs.pkg.requestedPermissions)) {
//这里grantablePermissions实际上就是新app声明的权限
grantablePermissions = new ArraySet<>(requestedPermissions);
//而requestedPermissions实际上是原app声明的权限
requestedPermissions = sysPs.pkg.requestedPermissions;
}
}
}
final int grantablePermissionCount = requestedPermissions.size();
for (int i = 0; i < grantablePermissionCount; i++) {
//循环是以原app的声明列表为基础的,表示新app声明的新加权限虽然可能在permissions参数中但是并不会被赋予。
String permission = requestedPermissions.get(i);
// If there is a disabled system app it may request a permission the updated
// version ot the data partition doesn't, In this case skip the permission.
if (grantablePermissions != null && !grantablePermissions.contains(permission)) {
//如果是新app没有声明的权限,当然就啥也不用做啦,直接跳出这次循环
continue;
}
if (permissions.contains(permission)) {
//具体的赋权代码
...
}
}
}
可以看出系统app替换和第一次正常安装走的流程有点不一样,新app新增的权限不会被赋权,google的意图应该是新增的权限要用户手动点击确认。不过对比新老Contacts的AndroidManifest文件,发现android.permission.CALL_PHONE都是在列的。但是从代码分析看只有“新app新增的权限不会被赋权”这一条才能解释为啥phone group只有一条权限是起作用的。代码没有问题的话,就可能是Contacts旧的权限数据有问题,对比问题手机和正常手机,在设置app的权限列表明显是不同的,问题手机只有4个权限开关,正常的手机有7个。问题手机居然没有phone group的开关,这也就印证了为啥赋权失败的问题。
问题手机在恢复出厂设置后恢复正常,证明了不是应用的问题。后续恢复出厂后再拿有无share uid的apk多次push,也无复现该问题,app的数据出错具体原因也就很难追踪了。