Android版本: 8.1
問題描述: 要在launcher顯示通話記錄calllog 和未讀短信等內容,涉及運行時權限,導致必須申請運行時權限,這樣就會彈窗提示,客戶的需求當然是不許彈窗咯,第一次開機就要默認授權。
一,privapp-permissions-mediatek.xml無效了
記得以前做這種權限問題,都是直接在privapp-permissions權限白名單里加上對應權限,但是在當前項目不支持,找不到具體原因,猜測是簽名不同導致的,然而具體原因,在此只能打問號了。總之權限白名單失效了。
二,context.checkSelfPermission() 的流程
在前面白名單裏已經寫了,但是checkSelfPermission的時候還是返回了deny,於是我開始追尋checkSelfPermission的檢測過程了。
Context.java
@PackageManager.PermissionResult
public abstract int checkSelfPermission(@NonNull String permission);
checkSelfPermission是抽象方法,自然它的實際實現者就是ContextImpl了
ContextImpl.java
@Override
public int checkSelfPermission(String permission) {
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}
return checkPermission(permission, Process.myPid(), Process.myUid());
}
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}
final IActivityManager am = ActivityManager.getService();
if (am == null) {
// Well this is super awkward; we somehow don't have an active
// ActivityManager instance. If we're testing a root or system
// UID, then they totally have whatever permission this is.
final int appId = UserHandle.getAppId(uid);
if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission);
return PackageManager.PERMISSION_GRANTED;
}
}
try {
return am.checkPermission(permission, pid, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
這裏是通過IActivityManager接口來處理,當然實現IActivityManager接口的纔是實體類,ActivityManagerService extends IActivityManager.Stub
在ActivityManagerService 裏 checkPermission
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
}
return checkComponentPermission(permission, pid, uid, -1, true);
}
int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
return ActivityManager.checkComponentPermission(permission, uid,
owningUid, exported);
}
接着進入了ActivityManager裏
/** @hide */
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
// Root, system server get to do everything.
final int appId = UserHandle.getAppId(uid);
if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}
// Isolated processes don't get any permissions.
if (UserHandle.isIsolated(uid)) {
return PackageManager.PERMISSION_DENIED;
}
// If there is a uid that owns whatever is being accessed, it has
// blanket access to it regardless of the permissions it requires.
if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
return PackageManager.PERMISSION_GRANTED;
}
// If the target is not exported, then nobody else can get to it.
if (!exported) {
/*
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
here);
*/
return PackageManager.PERMISSION_DENIED;
}
if (permission == null) {
return PackageManager.PERMISSION_GRANTED;
}
try {
return AppGlobals.getPackageManager()
.checkUidPermission(permission, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
前面都是判斷特殊情況,比如
- appId == Process.ROOT_UID 意思就是appId是屬於root 用戶就直接返回PERMISSION_GRANTED
- exported == false,意思就是說比如這個組件 contentprovider, 或者activity 設置了exported false, 就直接返回PERMISSION_DENIED
而其餘普通情況就是走進AppGlobals.getPackageManager() .checkUidPermission
這裏獲取到的是Packagemanager接口
public static IPackageManager getPackageManager() {
return ActivityThread.getPackageManager();
}
實際對象是PackageManagerService
@Override
public int checkUidPermission(String permName, int uid) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
final int userId = UserHandle.getUserId(uid);
if (!sUserManager.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
/// M: CTA requirement - permission control @{
sCtaManager.reportPermRequestUsage(permName, uid);
//@}
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
if (obj instanceof SharedUserSetting) {
if (isCallerInstantApp) {
return PackageManager.PERMISSION_DENIED;
}
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
return PackageManager.PERMISSION_DENIED;
}
}
final SettingBase settingBase = (SettingBase) obj;
final PermissionsState permissionsState = settingBase.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
if (isUidInstantApp) {
BasePermission bp = mSettings.mPermissions.get(permName);
if (bp != null && bp.isInstant()) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
return PackageManager.PERMISSION_GRANTED;
}
}
// Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms != null) {
if (perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
.contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
return PackageManager.PERMISSION_GRANTED;
}
}
}
}
return PackageManager.PERMISSION_DENIED;
}
這個方法很重要,結果就是在這裏判斷的,
根據打印可知,mSettings.getUserIdLPr 返回對應於launcher id的object不是空,於是obj != null
而檢測結果顯示
permissionsState.hasPermission(permName, userId) == false
這就說明 ,在launcher的PermissionsState裏是不包含我們所要的運行權限的。
但是這個permissionsState是系統初始化的時候就對應好了的,也就是初始化的時候根本就沒有這個授予權限。
三,從系統初始化找爲什麼沒有自動授權
在系統初始化完成後PackageManagerService自然也要調用systemReady;
PackageManagerService–systemReady()
@Override
public void systemReady() {
...
// If we upgraded grant all default permissions before kicking off.
for (int userId : grantPermissionsUserIds) {
mDefaultPermissionPolicy.grantDefaultPermissions(userId);
}
...
}
這裏有個默認授權的地方。
public void grantDefaultPermissions(int userId) {
if (mService.hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
grantAllRuntimePermissions(userId);
} else {
grantPermissionsToSysComponentsAndPrivApps(userId);
grantDefaultSystemHandlerPermissions(userId);
grantDefaultPermissionExceptions(userId);
}
}
這裏PackageManager.FEATURE_EMBEDDED是沒有支持的,接着走下面三個方法,重點在第一個方法裏。
grantPermissionsToSysComponentsAndPrivApps
看這方法名不就是授權給系統組件和特權app嗎? 但是爲什麼launcher明明是priv app裏的,還是沒有權限?
private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
Log.i(TAG, "Granting permissions to platform components for user " + userId);
synchronized (mService.mPackages) {
for (PackageParser.Package pkg : mService.mPackages.values()) {
if (!isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg)
|| !doesPackageSupportRuntimePermissions(pkg)
|| pkg.requestedPermissions.isEmpty()) {
continue;
}
grantRuntimePermissionsForPackageLocked(userId, pkg);
}
}
}
遍歷系統packages, 做了三個判斷,只有通過判斷才能走下去
- isSysComponentOrPersistentPlatformSignedPrivAppLPr
- doesPackageSupportRuntimePermissions
- pkg.requestedPermissions.isEmpty()
這裏第一個方法就是判斷該組件是不是系統組件或者是Persistent的系統簽名的app
private boolean isSysComponentOrPersistentPlatformSignedPrivAppLPr(PackageParser.Package pkg) {
if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
return true;
}
if (!pkg.isPrivilegedApp()) {
return false;
}
PackageSetting sysPkg = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
if (sysPkg != null && sysPkg.pkg != null) {
if ((sysPkg.pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
return false;
}
} else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
return false;
}
return PackageManagerService.compareSignatures(mService.mPlatformPackage.mSignatures,
pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
}
對laucnher來說pkg.isPrivilegedApp()肯定是true ,但是這裏的ApplicationInfo.FLAG_PERSISTENT,launcher是沒有這個屬性的,然後後面就是系統簽名匹配,pkg.mSignatures,
也就是LOCAL_CERTIFICATE := platform ,launcher也是沒有設置的。所以這裏返回肯定是false。
然後第二項,檢查package 的targetSdkVersion是不是大於22,也就是android 6.0
private static boolean doesPackageSupportRuntimePermissions(PackageParser.Package pkg) {
return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
}
第三項,pkg.requestedPermissions.isEmpty()
權限列表是否爲空,
根據條件判斷,這裏必然三項檢查返回必須都是false才能走下去,也就是 必須是 系統簽名且FLAG_PERSISTENT的app 且 targetsdk>22 且 permissions 不爲空纔會往下走,否則continue跳到下一個package
只有滿足條件的app
才能走後面的grantRuntimePermissionsForPackageLocked
private void grantRuntimePermissionsForPackageLocked(int userId, PackageParser.Package pkg) {
Set<String> permissions = new ArraySet<>();
for (String permission : pkg.requestedPermissions) {
BasePermission bp = mService.mSettings.mPermissions.get(permission);
if (bp != null && bp.isRuntime()) {
permissions.add(permission);
}
}
if (!permissions.isEmpty()) {
grantRuntimePermissionsLPw(pkg, permissions, true, userId);
}
}
private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
boolean systemFixed, int userId) {
grantRuntimePermissionsLPw(pkg, permissions, systemFixed, false, userId);
}
接下來所做的就是授權了
private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
......
mService.grantRuntimePermission(pkg.packageName, permission, userId);
......
省略
private void grantRuntimePermission(String packageName, String name, final int userId,
boolean overridePolicy) {
......
final int result = permissionsState.grantRuntimePermission(bp, userId);
......
}
也就是前面提到的permissionsState裏給與對應權限,
public int grantRuntimePermission(BasePermission permission, int userId) {
enforceValidUserId(userId);
if (userId == UserHandle.USER_ALL) {
return PERMISSION_OPERATION_FAILURE;
}
return grantPermission(permission, userId);
}
流程就是這樣,後面不看了,對於launcher的問題,是出在前面的判斷裏,
打印知道,在launcher包做判斷檢查是否系統組件的時候返回了false,這就導致系統初始化的時候並不會自動授權launcher的運行權限。
而關鍵的地方
(pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0
PackageManagerService.compareSignatures(mService.mPlatformPackage.mSignatures,
pkg.mSignatures) == PackageManager.SIGNATURE_MATCH
這兩項必須滿足,所以要在 <application 標籤裏添加
android:persistent="true"
以及在makefile裏添加
LOCAL_CERTIFICATE := platform
只有這樣纔會滿足前面的條件。
重新編譯,launcher 再檢查權限,就是返回已經授權通話相關權限了。解決了默認runtime授權。
int result = mcontext.checkSelfPermission("android.permission.READ_PHONE_STATE")
result = PackageManager.PERMISSION_GRANTED
其實對於開發來說,還有更粗暴的方式,就是直接在前面判斷launcher包名返回true,不過這樣實在太粗暴了
if (pkg.packageName.contains("launcher3")) {
return true;
}
總結:
由於運行權限的問題,導致好幾個地方不能正確獲取權限,而又不允許彈窗申請,所以必須默認授權,這就一定要走源碼,通過走一下權限檢查流程,加深了我對權限這一塊的理解。以後再有這種問題,就直接上了。