总体而言, Android P 和 Android O相比,PackageInstaller模块变化不大。因为Android O的时候没来及总结,所以一些 Android O上就出现的特性也会在这里记录学习一下。
界面跳转流程
从界面跳转流程上看,与 Android O相比,多了一个 DeleteStagedFileOnResult 页面,主要就是把原来 跳转PackageInstallerActivity 和删除 临时文件的动作 拎到这里来了。
一些重要的点
- Android O开始,三方应用如果想调起 系统 PackageInstaller 安装/卸载应用,必须在AndroidManifest中声明对应的权限:
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.DELETE_PACKAGES"/>'
否则无法实现安装会有类似 log打出来:
10-26 16:26:25.188 7457 7457 E InstallStart: Requesting uid 10106 needs to declare permission android.permission.REQUEST_INSTALL_PACKAGES
看一看这一部分的检测逻辑,代码位置在 InstallStart.java
的 oncreate
方法中
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
.............
.............
final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
final int originatingUid = getOriginatingUid(sourceInfo);
/**
* 下面是判定 未知来源 的逻辑
*/
boolean isTrustedSource = false;
//如果是 priv-app, 就去看Intent中 EXTRA_NOT_UNKNOWN_SOURCE 参数,也就是说,如果调起安装器的不是priv-app, 带不带这个参数都没用
//如果是 priv-app, 再看 EXTRA_NOT_UNKNOWN_SOURCE 参数的值
if (sourceInfo != null
&& (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
}
if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
if (targetSdkVersion < 0) {
Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
// Invalid originating uid supplied. Abort install.
mAbortInstall = true;
} else if (targetSdkVersion >= Build.VERSION_CODES.O && !declaresAppOpPermission(
//在 Android O 及以上版本中,调起安装器需要声明REQUEST_INSTALL_PACKAGES 权限,否则直接放弃安装
originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ Manifest.permission.REQUEST_INSTALL_PACKAGES);
mAbortInstall = true;
}
}
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
return;
}
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
// The the installation source as the nextActivity thinks this activity is the source, hence
// set the originating UID and sourceInfo explicitly
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
/*
几种类型的Uri, file,content,package, 不同的Uri跳转到不同的Activity
*/
if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
|| packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
// Copy file to prevent it from being changed underneath this process
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT,
PackageManager.INSTALL_FAILED_INVALID_URI);
setResult(RESULT_FIRST_USER, result);
nextActivity = null;
}
}
if (nextActivity != null) {
startActivity(nextActivity);
}
finish();
}
-
Android O上去除了 未知来源 总开关,改为单个渠道控制 未知来源的 安装请求。
这一部分检测逻辑如下,代码位置在 PackageInstallerActivity.java
private void handleUnknownSources() {
if (mOriginatingPackage == null) {
Log.i(TAG, "No source found for package " + mPkgInfo.packageName);
showDialogInner(DLG_ANONYMOUS_SOURCE);
return;
}
/**
* 检查callingPackage是否具有REQUEST_INSTALL_PACKAGES权限,如果 权限标志位 是 AppOpsManager.MODE_ERRORED,
* 就显示 渠道未知来源对话框,可以进入设置中设置开关
*/
// Shouldn't use static constant directly, see b/65534401.
final int appOpCode =
AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode,
mOriginatingUid, mOriginatingPackage);
switch (appOpMode) {
case AppOpsManager.MODE_DEFAULT:
try {
int result = mIpm.checkUidPermission(
Manifest.permission.REQUEST_INSTALL_PACKAGES, mOriginatingUid);
if (result == PackageManager.PERMISSION_GRANTED) {
initiateInstall();
break;
}
} catch (RemoteException exc) {
Log.e(TAG, "Unable to talk to package manager");
}
mAppOpsManager.setMode(appOpCode, mOriginatingUid,
mOriginatingPackage, AppOpsManager.MODE_ERRORED);
// fall through
//渠道未知来源对话框
case AppOpsManager.MODE_ERRORED:
showDialogInner(DLG_EXTERNAL_SOURCE_BLOCKED);
break;
case AppOpsManager.MODE_ALLOWED:
initiateInstall();
break;
default:
Log.e(TAG, "Invalid app op mode " + appOpMode
+ " for OP_REQUEST_INSTALL_PACKAGES found for uid " + mOriginatingUid);
finish();
break;
}
}