背景
一个三方应用想要内置进我们 ROM,并且想要能够静默安装应用。
App的静默安装和卸载
Android系统本身提供了安装卸载功能,但是api接口是@hide的,不是公开的接口,所以在应用级别
是无法实现静默安装和卸载的,要实现静默安装和卸载需要是系统应用,要有系统签名和相应的权限。
思路1
- 通过反射获得安装接口installPackage和 卸载接口 deletePackage
- 在自己的包中引入两个接口IPackageInstallObserver和IPackageDeleteObserver的空实现
- 调用安装卸载的方法,回调上面的两个接口
- 添加权限
<uses-permission android:name="android.permission.DELETE_PACKAGES"/>
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
进行系统签名 - 将应用push到系统中,作为系统应用
在PackageManager中的提供的接口如下:
/**
* @deprecated replaced by {@link PackageInstaller}
* @hide
*/
@Deprecated
public abstract void installPackage(
Uri packageURI,
PackageInstallObserver observer,
@InstallFlags int flags,
String installerPackageName);
/**
* Attempts to delete a package. Since this may take a little while, the
* result will be posted back to the given observer. A deletion will fail if
* the calling context lacks the
* {@link android.Manifest.permission#DELETE_PACKAGES} permission, if the
* named package cannot be found, or if the named package is a system
* package.
*
* @param packageName The name of the package to delete
* @param observer An observer callback to get notified when the package
* deletion is complete.
* {@link android.content.pm.IPackageDeleteObserver#packageDeleted}
* will be called when that happens. observer may be null to
* indicate that no callback is desired.
* @hide
*/
@RequiresPermission(Manifest.permission.DELETE_PACKAGES)
public abstract void deletePackage(String packageName, IPackageDeleteObserver observer,
@DeleteFlags int flags);
引入两个回掉的空实现
在自己应用的工程中新建一个包android.content.pm
,并添加两个文件
- IPackageDeleteObserver.java
package android.content.pm;
public interface IPackageDeleteObserver extends android.os.IInterface {
public abstract static class Stub extends android.os.Binder implements android.content.pm.IPackageDeleteObserver {
public Stub() {
throw new RuntimeException("Stub!");
}
public static android.content.pm.IPackageDeleteObserver asInterface(android.os.IBinder obj) {
throw new RuntimeException("Stub!");
}
public android.os.IBinder asBinder() {
throw new RuntimeException("Stub!");
}
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
throws android.os.RemoteException {
throw new RuntimeException("Stub!");
}
}
public abstract void packageDeleted(java.lang.String packageName, int returnCode)
throws android.os.RemoteException;
}
- IPackageInstallObserver.java
package android.content.pm;
public interface IPackageInstallObserver extends android.os.IInterface {
public abstract static class Stub extends android.os.Binder implements android.content.pm.IPackageInstallObserver {
public Stub() {
throw new RuntimeException("Stub!");
}
public static android.content.pm.IPackageInstallObserver asInterface(android.os.IBinder obj) {
throw new RuntimeException("Stub!");
}
public android.os.IBinder asBinder() {
throw new RuntimeException("Stub!");
}
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
throws android.os.RemoteException {
throw new RuntimeException("Stub!");
}
}
public abstract void packageInstalled(java.lang.String packageName, int returnCode)
throws android.os.RemoteException;
}
自定义接口回调
- OnPackagedObserver.java
public interface OnPackagedObserver {
public void packageInstalled(String packageName, int returnCode);
public void packageDeleted(String packageName,int returnCode);
}
代码调用
- 反射调用
public static final int INSTALL_REPLACE_EXISTING = 0x00000002; //如果已经存在的包使用这个flag
public static final int INSTALL_ALL_USERS = 0x00000040;
PackageManager pm = getPackageManager();
Class<?>[] types = new Class[] {Uri.class, IPackageInstallObserver.class, int.class, String.class};
try {
Method method = pm.getClass().getMethod("installPackage", types);
method.invoke(pm,Uri.fromFile(file), new PackageInstallObserver(), INSTALL_ALL_USERS, null);
}catch (Exception e){
e.printStackTrace();
}
Class<?>[] uninstalltypes = new Class[] {String.class, IPackageDeleteObserver.class, int.class};
try {
Method uninstallmethod = pm.getClass().getMethod("deletePackage", uninstalltypes);
uninstallmethod.invoke(pm, "your packagename", new PackageDeleteObserver(), 0);
}catch (Exception e){
e.printStackTrace();
}
- 接口实现
private OnPackagedObserver onInstallOrDeleteObserver = new OnPackagedObserver() {
@Override
public void onPackageInstalled(String packageName, int returnCode) {
Log.d("test","onPackageInstalled");
}
@Override
public void onPackageDeleted(String packageName, int returnCode) {
Log.d("test","onPackageDeleted");
}
};
class PackageInstallObserver extends IPackageInstallObserver.Stub {
public void packageInstalled(String packageName, int returnCode) throws RemoteException {
if (onInstallOrDeleteObserver != null) {
onInstallOrDeleteObserver.onPackageInstalled(packageName, returnCode);
}
}
}
class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
public void packageDeleted(String packageName, int returnCode) throws RemoteException {
if (onInstallOrDeleteObserver != null) {
onInstallOrDeleteObserver.onPackageDeleted(packageName, returnCode);
}
}
}
签名
生成一个apk文件,需要对这个apk文件进行系统签名,由于<uses-permission android:name="android.permission.DELETE_PACKAGES"/>
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
是系统应用需要的权限,在开发应用时,如果加在AndroidManifest.xml
中会编译不过,需要先用工具 apktool
工具先把apk文件解压出来,用编辑器在AndroidManifest.xml
中加入上面的两个权限,然后在用工具 apktool
重新打包
- 反编译(解压)
apktool d -f test.apk
- 修改
AndroidManifest.xml
加入权限声明之后重新打包
apktool b test.apk
重新打包的时候很有可能会报错,仔细看下报错的位置,在解压后的文件中找到处理掉,该删的删。这一步成功后我们得到了一个 AndroidManifest中声明了权限的 apk, 这个时候这个apk是没有进行签名的,安装不了。需要再进行系统签名:
java -jar signapk.jar platform.x509.pem platform.pk8 unsign.apk signed.apk
signapk.jar
位于 out/host/linux-86/framework/signapk.jar
platform.x509.pem
platform.pk8
位于 build/target/product/security/platform.x509.pem, platform.pk8
最后生成的apk就是已经进行系统签名的apk
按照思路1的做法,我只做到了重新打包,生成了添加了权限后的apk文件,但是再使用上述方法给apk进行签名的时候始终报错,最后是采用mk编译的方法签名成功的。
思路2
其实归根结底,还是三方应用无法拿到 INSTALL_PACKAGES 和DELETE_PACKAGES权限,在AndroidManifest文件中声明的时候会编译不过。那么就可以在framework中权限检测的地方对这个特定包名赋予权限。这样即使没有在AndroidManifest中声明,也可以拿到权限。这样的话,思路1的4,5步操作即可省略。
07-18 11:48:26.705: W/System.err(12203): Caused by: java.lang.SecurityException: Neither user 10124 nor current process has android.permission.INSTALL_PACKAGES.
07-18 11:48:26.705: W/System.err(12203): at android.os.Parcel.readException(Parcel.java:2004)
07-18 11:48:26.705: W/System.err(12203): at android.os.Parcel.readException(Parcel.java:1950)
07-18 11:48:26.705: W/System.err(12203): at android.content.pm.IPackageManager$Stub$Proxy.installPackageAsUser(IPackageManager.java:4092)
07-18 11:48:26.705: W/System.err(12203): at android.app.ApplicationPackageManager.installCommon(ApplicationPackageManager.java:1828)
07-18 11:48:26.705: W/System.err(12203): at android.app.ApplicationPackageManager.installPackage(ApplicationPackageManager.java:1809)
07-18 11:48:26.705: W/System.err(12203): ... 11 more
根据异常的 log跟一下 framework 的代码,最终在AMS的checkComponentPermission方法中来做:
int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
.............
if ("android.permission.INSTALL_PACKAGES".equals(permission)) {
try {
String callingPkgName = AppGlobals.getPackageManager().getNameForUid(uid);
if ("your packagename".equals(callingPkgName)) {
return PackageManager.PERMISSION_GRANTED;
}
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, "install packages getNameForUid exception");
}
}
..............
..............
}
这样就好啦,省去了 反编译 和 重新打包 以及 重新签名 的过程。
附录
- Android 8.1 Delete Package返回码对照表
public static final int DELETE_SUCCEEDED = 1;
public static final int DELETE_FAILED_INTERNAL_ERROR = -1;
public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
public static final int DELETE_FAILED_USER_RESTRICTED = -3;
public static final int DELETE_FAILED_OWNER_BLOCKED = -4;
public static final int DELETE_FAILED_ABORTED = -5;
public static final int DELETE_FAILED_USED_SHARED_LIBRARY = -6;
- Android 8.1 Install Package返回码对照表
public static final int INSTALL_SUCCEEDED = 1;
public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
public static final int INSTALL_FAILED_INVALID_APK = -2;
public static final int INSTALL_FAILED_INVALID_URI = -3;
public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5;
public static final int INSTALL_FAILED_NO_SHARED_USER = -6;
public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7;
public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8;
public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9;
public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10;
public static final int INSTALL_FAILED_DEXOPT = -11;
public static final int INSTALL_FAILED_OLDER_SDK = -12;
public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13;
public static final int INSTALL_FAILED_NEWER_SDK = -14;
public static final int INSTALL_FAILED_MISSING_FEATURE = -17;
public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
public static final int INSTALL_FAILED_VERIFICATION_TIMEOUT = -21;
public static final int INSTALL_FAILED_VERIFICATION_FAILURE = -22;
public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23;
public static final int INSTALL_FAILED_UID_CHANGED = -24;
public static final int INSTALL_FAILED_VERSION_DOWNGRADE = -25;
public static final int INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE = -26;
public static final int INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE = -27;
public static final int INSTALL_PARSE_FAILED_NOT_APK = -100;
public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101;
public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102;
public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103;
public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104;
public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105;
public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106;
public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107;
public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108;
public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
public static final int INSTALL_FAILED_USER_RESTRICTED = -111;
public static final int INSTALL_FAILED_DUPLICATE_PERMISSION = -112;
public static final int INSTALL_FAILED_NO_MATCHING_ABIS = -113;
public static final int NO_NATIVE_LIBRARIES = -114;
public static final int INSTALL_FAILED_ABORTED = -115;
public static final int INSTALL_FAILED_INSTANT_APP_INVALID = -116;
更新一下,9.0上可以通过PackageInstaller.Session.commit 直接实现静默安装,前提是能拿到 <uses-permission android:name="android.permission.INSTALL_PACKAGES"/> 权限
private boolean install(Context context, String apkPath) {
String pkgName = null;
PackageManager packageManager = context.getPackageManager();
PackageInfo info = packageManager.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
if (info != null) {
ApplicationInfo appInfo = info.applicationInfo;
pkgName = appInfo.packageName;
}
if (pkgName == null) {
return false;
}
InputStream in = null;
OutputStream out = null;
PackageInstaller.Session session = null;
try {
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(pkgName);
int sessionId = packageInstaller.createSession(params);
session = packageInstaller.openSession(sessionId);
File apkFile = new File(apkPath);
long size = apkFile.length();
in = new FileInputStream(apkFile);
out = session.openWrite("PackageInstaller", 0, size);
byte[] buffer = new byte[65536];
int total = 0;
int count;
while ((count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
total += count;
final float fraction = (float) count / (float) size;
session.setStagingProgress(fraction);
}
session.fsync(out);
out.close();
Intent intent = new Intent("action_install_commmit");
int index = 0;
if (!TextUtils.isEmpty(pkgName)) {
intent.putExtra("pkgname", pkgName);
try {
index = pkgName.hashCode();
} catch (NumberFormatException e) {
// Ignore exception
e.printStackTrace();
}
}
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, index, intent,
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.gtIntentSender());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
小结
据我自己实操:
- 思路1
在重新打包 和 系统签名 的过程中都遇到了问题,重新打包的后来把报错的地方删掉打包成功了,但是系统签名按照上面 shell 方式签名始终没有成功。虽然最后使用 mk 方式,源码环境下编译签名成功了,但是思路1 的整个过程还是比较坎坷的 - 思路2
比较方便,基本没走弯路,相当于是 framework 给开了口子。
总而言之,在没有和ROM厂家合作的情况下,三方应用是不可能实现静默安装的。要么让它成为系统应用,要么让rom厂家在framework中开口子~