问题来源:
客户的一堆app预置到/system/preloadapp之后,开机通过shell脚本拷贝到/data/app安装正常,使用也正常,但问题在于首次使用时会有一堆确认授权的弹框,因为产品的特殊性,客户要求这种预置应用的授权不能让用户参与,也就是开机必须保证所有授权都能静默完成。
解决方法:
单独写了个应用,监听开机广播,在收到广播时进行授权处理。(最早是放在Provision应用中做的,但客户说最好能每次都检查,所以就这么处理了)
AndroidManifext.xml
如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicedemo"
android:sharedUserId="android.uid.system"
coreApp="true"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS"/>
<uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"/>
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES"/>
<uses-permission android:name="android.permission.GET_APP_OPS_STATS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<receiver android:name=".BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
arrays.xml:配置需要授权的应用白名单
如下:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<string-array name="app_packages">
<item>com.autonavi.amapautolite</item>
<item>ctrip.android.view</item>
<item>com.ecar.assistantnew</item>
<item>com.hdsc.edog</item>
<item>cn.kuwo.kwmusiccar</item>
<item>com.mapgoo.diruite</item>
</string-array>
</resources>
Android.mk:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := ServiceDemo
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_CERTIFICATE := platform
LOCAL_DEX_PREOPT := false
include $(BUILD_PACKAGE)
# Use the folloing include to make our test apk.
include $(call all-makefiles-under,$(LOCAL_PATH))
BootReceiver.java:
package com.example.servicedemo;
import java.io.File;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.app.AppOpsManager;
import android.app.AppOpsManager.PackageOps;
import android.app.AppOpsManager.OpEntry;
import android.util.Log;
import android.os.Environment;
import android.os.Process;
public class BootReceiver extends BroadcastReceiver {
private static final String TAG = "BootReceiver";
private JSONArray mFileList;
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Log.d(TAG, "----onReceive " + intent.toString());
long stime = System.currentTimeMillis();
String[] appPackages = context.getResources().getStringArray(
R.array.app_packages);
for (String appPackage : appPackages) {
setAppPermission(appPackage, context);
}
PackageManager pm = context.getPackageManager();
boolean debugAutoGrant = false;
//这个是遍历/data/app下面所有apk,根据apk解析包名再进行授权,应该是存在缺陷的
if (false) {
mFileList = new JSONArray();
getAllFiles("/data/app/", ".apk");
for (int i = 0; i < mFileList.length(); i++) {
try {
JSONObject jsonObject = mFileList.getJSONObject(i);
String pathString = jsonObject.getString("path");
Log.d(TAG, "-------apk path: " + pathString);
PackageInfo info = pm.getPackageArchiveInfo(pathString,
PackageManager.GET_ACTIVITIES);
if (info != null) {
ApplicationInfo appInfo = info.applicationInfo;
String appName = pm.getApplicationLabel(appInfo)
.toString();
String packname = appInfo.packageName;
String version = info.versionName;
Log.d(TAG, "-------package name: " + packname);
setAppPermission(packname, context);
} else {
Log.e(TAG, "========failed to get package info from "
+ pathString);
}
} catch (JSONException ex) {
ex.printStackTrace();
}
}
}
long etime = System.currentTimeMillis();
Log.d(TAG, "grant permission time: " + (etime - stime) + " ms");
}
//这个是获取/data/app下面的所有apk文件路径
private void getAllFiles(String dirPath, String fileType) {
Log.d(TAG, "dirPath: " + dirPath + " fileType: " + fileType);
File f = new File(dirPath);
if (!f.exists()) {
Log.e(TAG, "folder doesn't exist");
return;
}
File[] files = f.listFiles();
if (files == null) {
Log.e(TAG, "empty folder");
return;
}
for (File file : files) {
if (file.isFile() && file.getName().endsWith(fileType)) {
String name = file.getName();
String filePath = file.getAbsolutePath();
Log.d(TAG, "apk file is : " + filePath);
try {
JSONObject info = new JSONObject();
info.put("name", name);
info.put("path", filePath);
mFileList.put(info);
} catch (Exception e) {
}
} else if (file.isDirectory()) {
getAllFiles(file.getAbsolutePath(), fileType);
}
}
}
//授权关键函数
private void setAppPermission(String pkgName, Context context) {
Log.d(TAG, "setAppPermission for package: " + pkgName);
PackageManager pm = context.getPackageManager();
AppOpsManager mAppOps = context.getSystemService(AppOpsManager.class);
try {
PackageInfo pkgInfo = pm.getPackageInfo(pkgName,
PackageManager.GET_PERMISSIONS);
String sharedPkgList[] = pkgInfo.requestedPermissions;
for (int i = 0; i < sharedPkgList.length; i++) {
String permName = sharedPkgList[i];
PermissionInfo permissionInfo;
try {
permissionInfo = pm.getPermissionInfo(permName, 0);
} catch (PackageManager.NameNotFoundException e) {
continue;
}
final int opCode = AppOpsManager.permissionToOpCode(permName);
boolean hasAopsCode = opCode > AppOpsManager.OP_NONE
&& opCode < AppOpsManager._NUM_OP;
String aopStr = AppOpsManager.permissionToOp(permName);
boolean hasAopsOp = aopStr != null;
boolean onlyAops = false;
final boolean granted = (pkgInfo.requestedPermissionsFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) != PermissionInfo.PROTECTION_DANGEROUS
|| (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0
|| (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) != 0) {
if (permName.startsWith("android.")
&& (hasAopsCode || hasAopsOp)) {
onlyAops = true;
Log.d(TAG, "permissionName=" + permName + ",opCode="
+ opCode);
} else {
continue;
}
}
boolean isAopsAllowed = mAppOps.checkOpNoThrow(opCode,
pkgInfo.applicationInfo.uid, pkgInfo.packageName) == AppOpsManager.MODE_ALLOWED;
if ((hasAopsCode || onlyAops) && !isAopsAllowed) {
mAppOps.setMode(opCode, pkgInfo.applicationInfo.uid,
pkgInfo.packageName, AppOpsManager.MODE_ALLOWED);
}
if ((hasAopsOp || onlyAops) && !isAopsAllowed) {
mAppOps.setUidMode(aopStr, pkgInfo.applicationInfo.uid,
AppOpsManager.MODE_ALLOWED);
}
if (!granted && !onlyAops) {
Log.d(TAG, "grant permission " + permName + " to pkg: "
+ pkgName);
pm.grantRuntimePermission(pkgName, permName,
Process.myUserHandle());
}
// set flags
pm.updatePermissionFlags(
permName,
pkgName,
PackageManager.FLAG_PERMISSION_USER_FIXED
| PackageManager.FLAG_PERMISSION_USER_SET
| PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED,
0, Process.myUserHandle());
}
List<AppOpsManager.PackageOps> ops = mAppOps.getOpsForPackage(
pkgInfo.applicationInfo.uid, pkgName, null);
if (ops == null || ops.size() == 0) {
return;
}
for (PackageOps packageOps : ops) {
List<OpEntry> entries = packageOps.getOps();
if (entries == null || entries.size() == 0) {
continue;
}
for (OpEntry opEntry : entries) {
mAppOps.setMode(opEntry.getOp(), packageOps.getUid(),
pkgName, AppOpsManager.MODE_ALLOWED);
}
}
} catch (Exception e) {
Log.e(TAG, "setAppPermission exception, the packageName is "
+ pkgName);
e.printStackTrace();
}
}
}
其他:
通过白名单授权测试通过。
同时也测试了遍历/data/app下apk,再根据apk解析包名进行授权的解决方法,实测下来会有一点问题。个别apk无法解析包名,从而无法做授权确认。代码保留了做个参考吧。
参考:
https://www.cxyzjd.com/articl...
参考了上述文章,补全了其他细节(manifest、白名单机制、mk文件等)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。