Android7.0的靜默安裝失敗問題研究
最近遇到了在Android7.0上靜默安裝失敗的問題谢揪。應(yīng)用程序放到系統(tǒng)分區(qū)(/system/priv-app/)執(zhí)行pm命令實(shí)現(xiàn)靜默安裝的方式在其他版本上都可以實(shí)現(xiàn)靜默安裝统台,在7.0上就安裝失敗,報(bào)這個(gè)異常:
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的執(zhí)行邏輯做了修改,我們直接從Pm的源碼開始研究钓简。
網(wǎng)上關(guān)于7.0靜默安裝的資料確實(shí)很少油啤,沒辦法了只能先去查看源碼看為啥報(bào)這個(gè)異常了。手頭上也沒有7.0的源碼坯钦,只能去下載了预皇,20個(gè)G的源碼下載了三個(gè)小時(shí)左右。7.0源碼下載婉刀,清華大學(xué)Android源碼鏡像站吟温,在解壓的aosp目錄下面執(zhí)行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) {
//測試發(fā)現(xiàn)這個(gè)getUserId的返回值應(yīng)該為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;
}
研究發(fā)現(xiàn)callingUserId = UserHandle.getUserId(callingUid)的返回值為0堤尾,如果userId的值和callingUserId的值相同就不會(huì)執(zhí)行下面的方法,也就不會(huì)拋出異常了迁客。那我們就繼續(xù)追蹤看看userId是什么郭宝。這次我們Pm的源碼開始,pm install就是在Pm這個(gè)類中執(zhí)行的掷漱,調(diào)用的是runInstall方法粘室,runInstall的源碼就不貼出來了,我們這里只關(guān)心兩個(gè)方法
final InstallParams params = makeInstallParams();和final int sessionId = doCreateSession(params.sessionParams,params.installerPackageName, params.userId);
makeInstallParams的源碼:該方法是解析命令中的參數(shù)的卜范,這里只看主要的
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方法中回去調(diào)用userId = translateUserId(userId, "runInstallCreate");
translateUserId中回去調(diào)用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);
}
找到了衔统,就是在這里調(diào)用的mUserController.handleIncomingUser方法,拋出的異常。這個(gè)userId就是我們調(diào)用doCreateSession方法傳入的params.userId锦爵,我們繼續(xù)看makeInstallParams的源碼舱殿,params.userId是--user指定的。
我們在代碼中執(zhí)行Runtime.getRuntime().exec("pm install --user 0 apkpath")险掀,log又拋出一個(gè)空指針異常沪袭。
調(diào)用鏈:doCreateSession--->mInstaller.createSession(params, installerPackageName, userId)-->createSessionInternal-->mAppOps.checkPackage(callingUid, installerPackageName);
checkPackageName會(huì)判斷installerPackageName是不是為空,為空就拋出異常樟氢。installerPackageNameye也是調(diào)用doCreateSession時(shí)傳入params.installerPackageName 該值也是pm命令指定的冈绊,為當(dāng)前調(diào)用執(zhí)行pm命令的包名
所以修改pm命令為:Runtime.getRuntime().exec("pm install -i 包名 --user 0 apkpath"),靜默安裝成功2嚎小K佬!
到這里就結(jié)束了霸妹,后面的源碼沒有貼出來十电,但是寫出了調(diào)用過程,大家可以自己去追蹤一下源碼叹螟!