Android7.0的静默安装失败问题研究
最近遇到了在Android7.0上静默安装失败的问题。应用程序放到系统分区(/system/priv-app/)执行pm命令实现静默安装的方式在其他版本上都可以实现静默安装,在7.0上就安装失败,报这个异常:
java.lang.SecurityException:
Permission Denial: runInstallCreate from pm command asks to run as user -1 but is calling from user 0;
this requires android.permission.INTERACT_ACROSS_USERS_FULL
至于安装失败肯定是pm instll的执行逻辑做了修改,我们直接从Pm的源码开始研究。
网上关于7.0静默安装的资料确实很少,没办法了只能先去查看源码看为啥报这个异常了。手头上也没有7.0的源码,只能去下载了,20个G的源码下载了三个小时左右。7.0源码下载,清华大学Android源码镜像站,在解压的aosp目录下面执行repo sync即可得到完整目录。
看frameworks的,源码查看工具我用的是understand,全局搜索runInstallCreate from pm command asks to run as user没搜到,说明这句话是组装成的,所以拆开搜索,全局搜索asks to run as user,这次搜到了。该方法在UserController类中
int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
int allowMode, String name, String callerPackage) {
//测试发现这个getUserId的返回值应该为0
final int callingUserId = UserHandle.getUserId(callingUid);
if (callingUserId == userId) {
return userId;
}
// Note that we may be accessing mCurrentUserId outside of a lock...
// shouldn't be a big deal, if this is being called outside
// of a locked context there is intrinsically a race with
// the value the caller will receive and someone else changing it.
// We assume that USER_CURRENT_OR_SELF will use the current user; later
// we will switch to the calling user if access to the current user fails.
int targetUserId = unsafeConvertIncomingUserLocked(userId);
if (callingUid != 0 && callingUid != SYSTEM_UID) {
final boolean allow;
if (mService.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
// If the caller has this permission, they always pass go. And collect $200.
allow = true;
} else if (allowMode == ALLOW_FULL_ONLY) {
// We require full access, sucks to be you.
allow = false;
} else if (mService.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
// If the caller does not have either permission, they are always doomed.
allow = false;
} else if (allowMode == ALLOW_NON_FULL) {
// We are blanket allowing non-full access, you lucky caller!
allow = true;
} else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
// We may or may not allow this depending on whether the two users are
// in the same profile.
allow = isSameProfileGroup(callingUserId, targetUserId);
} else {
throw new IllegalArgumentException("Unknown mode: " + allowMode);
}
if (!allow) {
if (userId == UserHandle.USER_CURRENT_OR_SELF) {
// In this case, they would like to just execute as their
// owner user instead of failing.
targetUserId = callingUserId;
} else {
StringBuilder builder = new StringBuilder(128);
builder.append("Permission Denial: ");
builder.append(name);
if (callerPackage != null) {
builder.append(" from ");
builder.append(callerPackage);
}
builder.append(" asks to run as user ");
builder.append(userId);
builder.append(" but is calling from user ");
builder.append(UserHandle.getUserId(callingUid));
builder.append("; this requires ");
builder.append(INTERACT_ACROSS_USERS_FULL);
if (allowMode != ALLOW_FULL_ONLY) {
builder.append(" or ");
builder.append(INTERACT_ACROSS_USERS);
}
String msg = builder.toString();
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
}
}
if (!allowAll && targetUserId < 0) {
throw new IllegalArgumentException(
"Call does not support special user #" + targetUserId);
}
// Check shell permission
if (callingUid == Process.SHELL_UID && targetUserId >= UserHandle.USER_SYSTEM) {
if (hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId)) {
throw new SecurityException("Shell does not have permission to access user "
+ targetUserId + "\n " + Debug.getCallers(3));
}
}
return targetUserId;
}
研究发现callingUserId = UserHandle.getUserId(callingUid)的返回值为0,如果userId的值和callingUserId的值相同就不会执行下面的方法,也就不会抛出异常了。那我们就继续追踪看看userId是什么。这次我们Pm的源码开始,pm install就是在Pm这个类中执行的,调用的是runInstall方法,runInstall的源码就不贴出来了,我们这里只关心两个方法
final InstallParams params = makeInstallParams();和final int sessionId = doCreateSession(params.sessionParams,params.installerPackageName, params.userId);
makeInstallParams的源码:该方法是解析命令中的参数的,这里只看主要的
private InstallParams makeInstallParams() {
final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
final InstallParams params = new InstallParams();
params.sessionParams = sessionParams;
String opt;
while ((opt = nextOption()) != null) {
switch (opt) {
....
case "-i":
params.installerPackageName = nextArg();
if (params.installerPackageName == null) {
throw new IllegalArgumentException("Missing installer package");
}
break;
...
...
case "--user":
params.userId = UserHandle.parseUserArg(nextOptionData());
break;
....
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
}
return params;
}
doCreateSession方法中回去调用userId = translateUserId(userId, "runInstallCreate");
translateUserId中回去调用ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, true, true, logContext, "pm command");看ActivityManagerService中的handleIncomingUser方法
@Override
public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
boolean requireFull, String name, String callerPackage) {
return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
requireFull ? ALLOW_FULL_ONLY : ALLOW_NON_FULL, name, callerPackage);
}
找到了,就是在这里调用的mUserController.handleIncomingUser方法,抛出的异常。这个userId就是我们调用doCreateSession方法传入的params.userId,我们继续看makeInstallParams的源码,params.userId是--user指定的。
我们在代码中执行Runtime.getRuntime().exec("pm install --user 0 apkpath"),log又抛出一个空指针异常。
调用链:doCreateSession--->mInstaller.createSession(params, installerPackageName, userId)-->createSessionInternal-->mAppOps.checkPackage(callingUid, installerPackageName);
checkPackageName会判断installerPackageName是不是为空,为空就抛出异常。installerPackageNameye也是调用doCreateSession时传入params.installerPackageName 该值也是pm命令指定的,为当前调用执行pm命令的包名
所以修改pm命令为:Runtime.getRuntime().exec("pm install -i 包名 --user 0 apkpath"),静默安装成功!!!
到这里就结束了,后面的源码没有贴出来,但是写出了调用过程,大家可以自己去追踪一下源码!