背景:
最近工作中,有些项目不允许所有APK都拥有安装权限,例如apk只能通过应用商城来安装或者升级,只允许某些特定的apk自升级,不允许pm install等。这就需要添加安装权限白名单来控制。
Android apk安装方式
先介绍Android中常用的几种安装方式,好针对这几种进行修改
1、 直接调用安装接口。
Uri mPackageURI = Uri.fromFile(new File(Environment.getExternalStorageDirectory() + apkName));
int installFlags = 0;
PackageManager pm = getPackageManager();
try{
PackageInfo pi = pm.getPackageInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
if(pi != null) {
installFlags |= PackageManager.REPLACE_EXISTING_PACKAGE;
}
}
catch (NameNotFoundException e){}
PackageInstallObserver observer = new PackageInstallObserver();
pm.installPackage(mPackageURI, observer, installFlags);
这种修改需要直接修改packageManagerService。对应下面的第一种方法。
2、通过Intent机制,调用packageInstaller进行安装。
String fileName = Environment.getExternalStorageDirectory() + apkName;
Uri uri = Uri.fromFile(new File(fileName));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri, application/vnd.android.package-archive"); startActivity(intent);
因为应用是通过packageInstaller进行安装的,相当于隔了一层代理,所以在packageManagerService并无法判断正在调用安装的是哪个app,只能在packageInstaller中进行修改,参考下面的第二中方法。
3、通过命令进行安装 pm install,参考第三种方法修改。
修改方法:
1、packageManagerService修改
packageManagerService的修改,我们在其中添加几个接口及代码来控制apk安装。
1)增加以下函数:
/*add for installer white list*/
private boolean isInstallerEnable(String packagename){
ArrayList<String> whiteListApp = new ArrayList<String>();
try{
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream("/system/etc/WhiteListAppFilter.properties")));
String line ="";
while ((line = br.readLine()) != null){
whiteListApp.add(line);
}
br.close();
}catch(java.io.FileNotFoundException ex){
return false;
}catch(java.io.IOException ex){
return false;
}
Iterator<String> it = whiteListApp.iterator();
while (it.hasNext()) {
String whitelisItem = it.next();
if (whitelisItem.equals(packagename)) {
return true;
}
}
return false;
}
isInstallerEnable函数会去读取白名单文件/system/etc/WhiteListAppFilter.properties,然后和我们传进来的包名进行匹配,在白名单中返回true,其他情况均返回false。
2)获取调用的包名
这个可以在installPackageWithVerificationAndEncryption函数中来获取,
if (uid == Process.SHELL_UID || uid == 0) {
if (DEBUG_INSTALL) {
Slog.v(TAG, "Install from ADB");
}
filteredFlags = flags | PackageManager.INSTALL_FROM_ADB;
} else {
filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB;
}
verificationParams.setInstallerUid(uid);
//add for installer white list
mCallingApp = mContext.getPackageManager().getNameForUid(uid);
Log.d(TAG, "!!!!!!!!!!!!!callingApp = " + mCallingApp);
//end
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
verificationParams, encryptionParams, user);
mHandler.sendMessage(msg);
接下来要在installPackageLI函数对调用安装的apk进行匹配,判断是否在白名单中,如果不在的话则提示错误,错误码是-xxx(自己定义),这个错误码会给packageInstaller,packageInstaller中需要做什么,就由自己定义了。
//add for installer white list
boolean caninstall =false;
if(mCallingApp != null && isInstallerEnable(mCallingApp)){
caninstall = true;
}
if(!caninstall){
Toast.makeText(mContext, R.string.install_error, Toast.LENGTH_LONG).show();
res.returnCode = -xxx;
return;
}
//end
if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
// Retrieve PackageSettings and parse package
int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
| (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
...省略
3)
/system/etc/WhiteListAppFilter.properties内容如下,在编译时可以在mk中修改拷贝到etc目录下,例如下面就是允许这三个包名有安装权限。
com.xxx.xxx1
com.xxx.xxx2
com.xxx.xxx3
2、packageInstaller的修改
还是参考packageManagerService的修改,增加isInstallerEnable函数,去读取白名单文件/system/etc/WhiteListAppFilter.properties,然后进行包名匹配,在白名单中返回true,其他情况均返回false。
我们在packageInstaller的PackageInstallerActivity.java中增加以下修改// add for installer enable/disable ,不在白名单中的app,会直接提示不允许安装后退出。
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// get intent information
final Intent intent = getIntent();
mPackageURI = intent.getData();
mPm = getPackageManager();
final int uid = getOriginatingUid(intent);
String callingApp = mPm.getNameForUid(uid);
final File sourceFile = new File(mPackageURI.getPath());
PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
// add for installer enable/disable
if (!isInstallerEnable(callingApp)) {
Toast.makeText(this, R.string.install_not_allow, Toast.LENGTH_LONG).show();
this.finish();
}
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
...省略
3、pm install的修改
禁止pm install,因为有些APK安装竟然是调用pm install命令去安装的。
修改要在pm.java修改,修改方法和上面基本一致。
可以看到,pm install其实调用的是run再去判断参数。
public static void main(String[] args) {
new Pm().run(args);
}
public void run(String[] args) {
...省略
if ("install".equals(op)) {
runInstall();
return;
}
...省略
那我们要添加的话,先获取app名,再和packageManagerService一样,增加isInstallerEnable去判断是不是要调用runInstall()就OK了。
String callingApp = "";
try {
callingApp = mPm.getNameForUid(Binder.getCallingUid());
} catch(RemoteException re) {
Log.e("Pm", Log.getStackTraceString(new Throwable()));
}