默认给与预装应用"安装未知应用"权限
本来以为这是个简单的修改,万万没想到这里面坑很大。
APP更新就会使用到此权限,这个功能与普通的动态申请完全不同,正常APP需要实现此功能的话,首先,
1.在清单文件里添加权限:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
2.用canRequestPackageInstalls()
方法判断你的应用是否有这个权限:
boolean haveInstallPermission = getPackageManager().canRequestPackageInstalls();
如果haveInstallPermission 为 true,则说明你的应用有安装未知来源应用的权限,你直接执行安装应用的操作即可。
如果haveInstallPermission 为 false,则说明你的应用没有安装未知来源应用的权限,则无法安装应用。由于这个权限不是运行时权限,所以无法再代码中请求权限,还是需要用户跳转到设置界面中自己去打开权限。
弹出弹窗告知用户,“安装应用需要打开未知来源权限,请去设置中开启权限”,然后用户点击确定之后跳转到未知来源应用权限管理列表:
Uri packageURI = Uri.parse("package:" + getPackageName());
//注意这个是8.0新API
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
startActivityForResult(intent, 666);
然后在onActivityResult中去接收结果:
if (resultCode == RESULT_OK && requestCode == 666) {
installProcess();//再次执行安装流程,包含权限判等
}
//安装应用的流程
private void installProcess() {
boolean haveInstallPermission;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//先获取是否有安装未知来源应用的权限
haveInstallPermission = getPackageManager().canRequestPackageInstalls();
if (!haveInstallPermission) {//没有权限
DialogUtils.showDialog(this, "安装应用需要打开未知来源权限,请去设置中开启权限",
new View.OnClickListener() {
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startInstallPermissionSettingActivity();
}
}
}, null);
return;
}
}
//有权限,开始安装应用程序
installApk(apk);
}
//安装应用
private void installApk(File apk) {
Intent intent = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
intent.setDataAndType(Uri.fromFile(apk), "application/vnd.android.package- archive");
} else {//Android7.0之后获取uri要用contentProvider
Uri uri = AppCommonUtils.getUriFromFile(getBaseContext(), apk);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getBaseContext().startActivity(intent);
}
以上需确认你的系统是8.0或以上,这也是系统新特性。
以上是正常实现功能,那么我的是预置软件,需要默认拥有此权限,首先想到的是在DefaultPermissionGrantPolicy.class
里配置权限(不了解的请看下面预装应用给与权限):
private static final Set<String> REQUEST_INSTALL_PACKAGES = new ArraySet<>();
static {
REQUEST_INSTALL_PACKAGES.add(Manifest.permission.REQUEST_INSTALL_PACKAGES);
}
......
private void grantDefaultSystemHandlerPermissions(int userId) {
......
grantRuntimePermissions(app, REQUEST_INSTALL_PACKAGES, userId);
......
无效,并且还报错,提示此权限无法动态分配。
普通权限最多动态申请,但是这个正常流程下还需要跳转到系统页面让用户点击开关,就可以看出来与普通权限的区别的,处理也有区别。
第一个方法不行,我就转而去看源码了。根据页面的文字提示,找到RestrictedSwitchPreference
,再找到业务类packages/apps/Settings/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.class
找到开关切换的监听:
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean checked = (Boolean) newValue;
if (preference == mSwitchPref) {
if (mInstallAppsState != null && checked != mInstallAppsState.canInstallApps()) {
if (Settings.ManageAppExternalSourcesActivity.class.getName().equals(
getIntent().getComponent().getClassName())) {
setResult(checked ? RESULT_OK : RESULT_CANCELED);
}
setCanInstallApps(checked);
refreshUi();
}
return true;
}
return false;
}
明显setCanInstallApps(checked);
就是设置下去的代码:
private void setCanInstallApps(boolean newState) {
mAppOpsManager.setMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
mPackageInfo.applicationInfo.uid, mPackageName,
newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
}
找到AppOpsManager.class
内的setMode()
函数:
/** @hide */
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setMode(int code, int uid, String packageName, int mode) {
try {
mService.setMode(code, uid, packageName, mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@SystemApi
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setMode(String op, int uid, String packageName, int mode) {
try {
mService.setMode(strOpToOp(op), uid, packageName, mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
里面有俩个函数,刚才调用的是第一个,但是我们需要在外部调用,可以看到代码有/** @hide */
,明显不希望外部调用,虽然被注释了。那么我们调用第二个函数。
第一个参数op填:public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages";
第四个参数mode填:public static final int MODE_ALLOWED = 0;
既然希望默认给与我们的APP这个权限,我想到的是在launch的启动页里加设置此权限的代码,因为setMode()
上有权限限制注释:@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
我们先给Launck的清单文件里添加权限:
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
然后在packages/apps/Launcher3/src/com/android/launcher3/Launcher.class
里添加代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
......
try {
//给与平板软件安装未知应用的权限
PackageInfo mPackageInfo = getPackageManager().getPackageInfo("com.android.app", 0);
AppOpsManager mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mAppOpsManager.setMode("android:request_install_packages",
mPackageInfo.applicationInfo.uid, "com.android.app",0);
} catch (PackageManager.NameNotFoundException ex) {
Log.e(TAG, "NameNotFoundException");
}
......
然后发现Launch没有设置此权限的权限,就算给了<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
也没用,原因暂时不明,但是不需要钻牛角尖,我们只需要系统默认给与此权限而已,Launch不行就在Settings的默认拉起项里加也一样,Settings是肯定有此权限对吧。
找到Settings的一个默认拉起项:/packages/apps/TvSettings/Settings/src/com/android/tv/settings/system/FallbackHome.java
,这是一个Activity,在onCreate()
里添加代码即可:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......
try {
//给与平板软件安装未知应用的权限
PackageInfo mPackageInfo = getPackageManager().getPackageInfo("com.android.app", 0);
AppOpsManager mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mAppOpsManager.setMode("android:request_install_packages",
mPackageInfo.applicationInfo.uid, "com.android.app",0);
} catch (PackageManager.NameNotFoundException ex) {
Log.e(TAG, "NameNotFoundException");
}
......
记得给类里加导入包:
import android.app.AppOpsManager;
import android.content.pm.PackageInfo;
大功告成!
应用分配权限
权限分配对象可分为俩种,第一种是预装的软件,出厂自带的APP,大部分厂商定制系统都需要把公司的APP预装进去,第二种是之后安装的APP
预装应用给与权限
给与预装APP权限需要先找到类
frameworks/base/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.class
类中找到函数grantDefaultSystemHandlerPermissions()
,看里面代码应该会很熟悉,里面能找到Camera,Storage provider等等软件的权限给与逻辑,照着写就完事了,例:
//你的软件
PackageParser.Package xhorse = null;
testApp = mServiceInternal.getPackage("com.android.test");
if (testApp != null
&& doesPackageSupportRuntimePermissions(testApp)) {
grantRuntimePermissions(testApp, STORAGE_PERMISSIONS, userId);
grantRuntimePermissions(testApp, CAMERA_PERMISSIONS, userId);
grantRuntimePermissions(testApp, LOCATION_PERMISSIONS, userId);
grantRuntimePermissions(testApp, CALENDAR_PERMISSIONS, userId);
grantRuntimePermissions(testApp, PHONE_PERMISSIONS, userId);
grantRuntimePermissions(testApp, SMS_PERMISSIONS, userId);
grantRuntimePermissions(testApp, SENSORS_PERMISSIONS,userId);
grantRuntimePermissions(testApp, MICROPHONE_PERMISSIONS, userId);
grantRuntimePermissions(testApp, REQUEST_INSTALL_PACKAGES, userId);
}
简单明了,不多介绍。
第三方APP给与权限
手动安装的第三方app的权限默认开启是修改PackageManagerService.java
。找到函数
void doHandleMessage(Message msg)
,在case POST_INSTALL
的地方修改里面的变量
grantPermissions = true
,修改如下:
void doHandleMessage(Message msg) {
......
case POST_INSTALL: {
if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1);
PostInstallData data = mRunningInstalls.get(msg.arg1);
final boolean didRestore = (msg.arg2 != 0);
mRunningInstalls.delete(msg.arg1);
if (data != null) {
InstallArgs args = data.args;
PackageInstalledInfo parentRes = data.res;
//修改grantPermissions为true
final boolean grantPermissions = true;
//final boolean grantPermissions = (args.installFlags
// & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0;
final boolean killApp = (args.installFlags
& PackageManager.INSTALL_DONT_KILL_APP) == 0;
final boolean virtualPreload = ((args.installFlags
& PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
final String[] grantedPermissions = args.installGrantPermissions;
......
使之后安装的第三方APP拥有申请的所有权限。