前言
說下大體修改思路,app 安裝成功、卸載、升級分別對應 Intent.ACTION_PACKAGE_ADDED、Intent.ACTION_PACKAGE_REMOVED、Intent.ACTION_PACKAGE_REPLACED 廣播
這樣可以在收到安裝成功的廣播時給 app 授權,在 8.1 中收不到靜態註冊的廣播,所以需要動態註冊監聽 ACTION_PACKAGE_ADDED。之前看過 PackageInstaller 的源碼,通過
app 的包名可獲取到需要授權的權限清單列表並進行授權。
安裝成功權限列表
安裝時授權的log
代碼
源碼位置 vendor\mediatek\proprietary\packages\apps\PackageInstaller
1、新增 PackageChangedService.java 服務,在服務中動態註冊 app 狀態改變的廣播
package com.android.packageinstaller;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.util.Log;
import android.net.Uri;
public class PackageChangedService extends Service {
private final String TAG = "permission";
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate OK");
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand OK");
packageChangedBroadcastReceiver = new PackageChangedBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
intentFilter.addDataScheme("package");
registerReceiver(packageChangedBroadcastReceiver, intentFilter);
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
try{
unregisterReceiver(packageChangedBroadcastReceiver);
}catch(Exception e){
e.printStackTrace();
}
super.onDestroy();
}
private PackageChangedBroadcastReceiver packageChangedBroadcastReceiver;
private class PackageChangedBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try{
String action = intent.getAction();
String packageName = intent.getData().getSchemeSpecificPart();
Log.e(TAG, "PackageChangedBroadcastReceiver action==" + action);
Log.i(TAG, "PackageChangedBroadcastReceiver packageName==" + packageName);
if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
//給 app 授權
PermissionGrantHelper.slientGrantRuntimePermission(context, packageName);
} else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
} else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
//收到 app 替換成功的廣播,將廣播轉發給用戶,通過增加 0x01000000,可以通過靜態註冊接收
Intent ccIntent = new Intent();
ccIntent.setAction("android.intent.action.MY_PACKAGE_REPLACED");
ccIntent.setData(Uri.parse("package:" + packageName));
ccIntent.addFlags(0x01000000);
context.sendBroadcast(ccIntent);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
上面的代碼主要乾了兩件事,
一、收到 app 安裝成功的廣播,獲取 app 的包名,傳遞 app 包名進行權限清單查詢並判斷未授權則自動授權
二、解決 app 靜態註冊無法收到 升級成功替換的廣播,此處收到動態註冊的 PACKAGE_REPLACED 廣播,通過增加 addFlags(0x01000000) 屬性,以 MY_PACKAGE_REPLACED 的方式轉發出去
2、新增 PermissionGrantHelper.java 授權工具類,通過包名查詢權限清單並授權
package com.android.packageinstaller;
import android.content.Context;
import android.util.Log;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.AppPermissions;
import com.android.packageinstaller.permission.model.Permission;
import com.android.packageinstaller.permission.utils.ArrayUtils;
import com.android.packageinstaller.permission.utils.Utils;
import java.util.List;
public class PermissionGrantHelper{
public static void slientGrantRuntimePermission(Context context, String packageName){
PackageInfo packageInfo;
try {
packageInfo = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
} catch (PackageManager.NameNotFoundException e) {
Log.e("permission", "can't get PackageInfo for packageName="+ packageName);
return;
}
AppPermissions mAppPermissions = new AppPermissions(context, packageInfo, null, false,
new Runnable() {
@Override
public void run() {
}
});
Log.e("permission", " AppPermissionGroup size=="+mAppPermissions.getPermissionGroups().size());
if (mAppPermissions.getPermissionGroups().isEmpty()) {
Log.e("permission", "mAppPermissions size isEmpty");
return;
}
for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
String[] permissionsToGrant = null;
final int permissionCount = group.getPermissions().size();
for (int j = 0; j < permissionCount; j++) {
final Permission permission = group.getPermissions().get(j);
if (!permission.isGranted()) {
permissionsToGrant = ArrayUtils.appendString(
permissionsToGrant, permission.getName());
Log.e("permission", "permissionName=" + permission.getName());
}
}
if (permissionsToGrant != null) {
group.grantRuntimePermissions(false, permissionsToGrant);
Log.i("permission", "grantRuntimePermissions permissionsToGrant");
//group.revokeRuntimePermissions(false);
}
}
}
}
3、在配置文件中註冊 PackageChangedService,並收到開機廣播後啓動 PackageChangedService
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.packageinstaller" coreApp="true"
android:sharedUserId="android.uid.system">
....
<application android:label="@string/app_name"
android:allowBackup="false"
android:theme="@style/DialogWhenLarge"
android:supportsRtl="true"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true">
<receiver android:name=".TemporaryFileManager"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service android:name=".PackageChangedService" android:exported="false"/>
.....
</application>
</manifest>
注意上面配置文件中的 android:sharedUserId=“android.uid.system”,源碼默認未添加此屬性,8.1 中普通 app 的後臺開啓服務會報錯,
java.lang.IllegalStateException: Not allowed to start service Intent { flg=0x10000000 cmp=XXXX }: app is in background uid UidRecord{9327d82 u0a489 RCVR bg:+5m35s828ms idle change:uncached
查閱網上說的大都是將 startService(intent) 替換爲 startForegroundService(intent),還需要在 service 的 onStartCommand 中,調用 startForeground(1, new Notification()) 來保活
既然我們是安卓源碼直接添加 uid 屬性完事。
再在 TemporaryFileManager 中收到開機廣播後添加 context.startService(new Intent(context, PackageChangedService.class));