解决低版本apk预制到高版本系统中出现丢失so库的问题,以及默认给予第三方apk添加特殊应用权限
前段时间在做一个学习平板的项目,客户的apk让我预制到系统中,但是客户的apk版本又很低,让我放到十三的系统中,用常规的方法(通过在package中的Android.mk文件预制)预制到系统中会出现丢失so库,导致客户的apk用不了,客户又不愿意改apk版本,让我自己想办法解决,这个bug搞了好久,现在有一种解决的办法,就是通过静默安装的方式可以实现这个需求,但是又因为安卓十三不适用以前的静默安装的方法,后面通过io流去静默安装的apk,解决逻辑就是这样,下面看实现方法。 首先是自己在init.rc文件中添加一个设备节点,这个节点的作用是在机器的内部存储路径创建一个自定义的文件夹
diff --git a/device/mediatek/mt6765/init.mt6765.rc b/device/mediatek/mt6765/init.mt6765.rc
index adc0aaf1e02..fd1081707fa 100755
--- a/device/mediatek/mt6765/init.mt6765.rc
+++ b/device/mediatek/mt6765/init.mt6765.rc
@@ -220,6 +220,11 @@ on post-fs-data
mkdir /mnt/vendor/cct
chown root system /mnt/vendor/cct
chmod 0771 /mnt/vendor/cct
+
+ #Create Apks DIR
+ mkdir /storage/emulated/0/Apks
+ chown root system /storage/emulated/0/Apks
+ chmod 0777 /storage/emulated/0/Apks
#Create flash folder
mkdir /data/vendor/flash
注:因为我使用的是mt6765的机器,这个根据自己所用的机器型号为准 然后再添加一个开机拷贝和静默安装的服务,如下:
diff --git a/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/CopyFileService.java b/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/CopyFileService.java
new file mode 100755
index 00000000000..e7d23a32cb1
--- /dev/null
+++ b/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/CopyFileService.java
@@ -0,0 +1,165 @@
+package com.android.settings;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Environment;
+import android.os.IBinder;
+import android.util.Log;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import android.net.Uri;
+import android.content.Context;
+import android.media.MediaScannerConnection;
+
+public class CopyFileService extends Service {
+
+ private final static String TAG = "Monkey_CopyFileService";
+ private static String PRELOAD_SRC = Environment.getRootDirectory()+"/media/apks";
+ private static String PRELOAD_DEST = "/storage/emulated/0/Apks";
+ private static Context mContext;
+
+ private CopyThread mCopyThread;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ Log.d(TAG,"CopyFileService---------------");
+ super.onCreate();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG,"onStartCommand---------------");
+ mContext = getApplicationContext();
+ if (mCopyThread == null) {
+ mCopyThread = new CopyThread();
+ mCopyThread.start();
+ }
+ return Service.START_REDELIVER_INTENT;
+ }
+
+ public class CopyThread extends Thread {
+ @Override
+ public void run() {
+ if (copyFolder(new File(PRELOAD_SRC), new File(PRELOAD_DEST))) {
+ Log.e(TAG, "doPreloadMedia success !");
+ } else {
+ Log.e(TAG, "doPreloadMedia failed 。");
+ }
+ Log.e(TAG, "PRELOAD_SRC:"+PRELOAD_SRC);
+ Log.e(TAG, "PRELOAD_DEST:"+PRELOAD_DEST);
+ //fileScan(PRELOAD_DEST);
+ updateMedia(PRELOAD_DEST);
+ Intent intent = new Intent();
+ intent.setAction("android.intent.action.installapk");
+ mContext.sendBroadcast(intent);
+ Log.d(TAG, "Send installapk intent!");
+ stopSelf();
+ }
+ }
+
+ public static void updateMedia(String filename){
+ MediaScannerConnection.scanFile(mContext,
+ new String[] { filename }, null,
+ new MediaScannerConnection.OnScanCompletedListener() {
+ public void onScanCompleted(String path, Uri uri) {
+ }
+ });
+ }
+
+ public void fileScan(String file) {
+ Uri data = Uri.parse("file://" + file);
+ Log.d("TAG", "file:" + file);
+ sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, data));
+ }
+
+ public boolean copyFolder(File srcFile, File destFile) {
+ if (!srcFile.isDirectory()) {
+ Log.i(TAG, "srcFile no isDirectory :"+srcFile.getName());
+ return false;
+ }
+ if (!srcFile.canRead()) {
+ Log.i(TAG, "no canRead :"+srcFile.getName());
+ }
+ if (!srcFile.canWrite()) {
+ Log.i(TAG, "no canWrite :"+srcFile.getName());
+ }
+
+ if (!destFile.exists()) {
+ boolean r = false;
+ synchronized (this) {
+ r = destFile.mkdirs();
+ }
+ Log.i(TAG, "destFile:exists " + r);
+ }
+ boolean result = true;
+ File[] list = srcFile.listFiles();
+
+ if (list == null) {
+ Log.i(TAG, "copyFolder: list null =" + srcFile.getName());
+ return false;
+ }
+ for (File f : list) {
+ Log.i(TAG,"f.getName():"+f.getName());
+ if (f.isDirectory()) {
+ result &= copyFolder(f, new File(destFile, f.getName()));
+ } else if(f.isFile()){
+ result &= copyFile(f, new File(destFile, f.getName()));
+ }
+ }
+ Log.i(TAG,"result:"+result);
+ return result;
+ }
+
+ public boolean copyFile(File srcFile, File destFile) {
+ boolean result = false;
+ try {
+ InputStream in = new FileInputStream(srcFile);
+ try {
+ result = copyToFile(in, destFile);
+ } finally {
+ in.close();
+ }
+ } catch (IOException e) {
+ result = false;
+ }
+ return result;
+ }
+
+ /**
+ * Copy data from a source stream to destFile.
+ * Return true if succeed, return false if failed.
+ */
+ public boolean copyToFile(InputStream inputStream, File destFile) {
+ try {
+ if (destFile.exists()) {
+ destFile.delete();
+ }
+ FileOutputStream out = new FileOutputStream(destFile);
+ try {
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) >= 0) {
+ out.write(buffer, 0, bytesRead);
+ }
+ } finally {
+ out.flush();
+ try {
+ out.getFD().sync();
+ } catch (IOException e) {
+ }
+ out.close();
+ }
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+}
注:拷贝完成的标志是:updateMedia(PRELOAD_DEST);在拷贝完成后发送一个静默安装的广播
diff --git a/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/InstallApkService.java b/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/InstallApkService.java
new file mode 100755
index 00000000000..b8b7783d568
--- /dev/null
+++ b/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/InstallApkService.java
@@ -0,0 +1,270 @@
+package com.android.settings;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Environment;
+import android.os.IBinder;
+import android.util.Log;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import android.net.Uri;
+import android.content.Context;
+import android.media.MediaScannerConnection;
+
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.content.pm.PackageManager;
+import android.os.SystemProperties;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.text.TextUtils;
+
+import android.app.PendingIntent;
+import android.content.pm.PackageInstaller;
+
+public class InstallApkService extends Service {
+
+ private final static String TAG = "InstallApkService";
+ private static String apksPath = "/storage/emulated/0/Apks/";
+ private static Context mContext;
+ private InstallThread mInstallThread;
+ private PackageManager mPackageManager;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ Log.d(TAG,"InstallApkService---------------");
+ super.onCreate();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG,"onStartCommand---------------");
+ mContext = getApplicationContext();
+ if (mInstallThread == null) {
+ mInstallThread = new InstallThread();
+ mInstallThread.start();
+ }
+ return Service.START_REDELIVER_INTENT;
+ }
+
+ public class InstallThread extends Thread {
+ @Override
+ public void run() {
+ try{
+ File apksFile = new File(apksPath);
+ mPackageManager = mContext.getPackageManager();
+ Log.d(TAG, " apksFile = " + apksFile);
+ if (apksFile.exists()) {
+ File[] files = apksFile.listFiles();
+ for (File f:files) {//遍历路径下所有文件
+ String filename = f.getName();
+ String type = filename.substring(filename.lastIndexOf(".")+1);
+ Log.d(TAG, "filename : " + filename + " type : " + type);
+ if(type != null && type.equals("apk")) {
+ String packageName = getApkInfo(mContext, apksPath + filename);
+ Log.d(TAG, "packageName : " + packageName);
+ if (!isInstallApp(mContext, packageName)) {
+ String apk = apksPath + filename;
+ Log.d(TAG, "apk =" + apk);
+ installApp(mContext, apk);
+ }
+ }
+ }
+ }
+ /* String packageName = getApkInfo(mContext, "/storage/emulated/0/Apks/lazyxxmk.apk");
+ Log.d(TAG, "packageName : " + packageName);
+ if(!isInstallApp(mContext, packageName)) {
+ String apk = "/storage/emulated/0/Apks/lazyxxmk.apk";
+ Log.d(TAG, "apk =" + apk);
+ installApp(mContext, apk);
+ } */
+ }catch(Exception e){
+ Log.d(TAG,"onReceive:install failed:" + e);
+ }
+ stopSelf();
+ }
+ }
+
+ /*
+ 路径权限
+ */
+ private void updateDataFilePermission(String path) {
+ String[] command = {"chmod", "777", path};
+ ProcessBuilder builder = new ProcessBuilder(command);
+ try {
+ builder.start();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void installApp(Context mContext, String apkFilePath) {
+ int mSessionId = -1;
+ Log.d(TAG, "installApp()------->" + apkFilePath);
+ File apkFile = new File(apkFilePath);
+ if (!apkFile.exists()) {
+ Log.d(TAG, "file not exit");
+ }
+ PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(apkFilePath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
+ if (packageInfo != null) {
+ String packageName = packageInfo.packageName;
+ int versionCode = packageInfo.versionCode;
+ String versionName = packageInfo.versionName;
+ Log.d(TAG, "packageName=" + packageName + ", versionCode=" + versionCode + ", versionName=" + versionName);
+ }
+ PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
+ PackageInstaller.SessionParams sessionParams
+ = new PackageInstaller.SessionParams(PackageInstaller
+ .SessionParams.MODE_FULL_INSTALL);
+ Log.d(TAG, "apkFile length = " + apkFile.length());
+ sessionParams.setSize(apkFile.length());
+ try {
+ mSessionId = packageInstaller.createSession(sessionParams);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ Log.d(TAG, "sessionId---->" + mSessionId);
+ if (mSessionId != -1) {
+ boolean copySuccess = onTransfesApkFile(mContext, apkFilePath, mSessionId);
+ Log.d(TAG, "copySuccess---->" + copySuccess);
+ if (copySuccess) {
+ execInstallAPP(mContext, mSessionId);
+ }
+ }
+ }
+
+ /**
+ * 执行安装并通知安装结果
+ */
+ private static void execInstallAPP(Context mContext, int mSessionId) {
+ Log.d(TAG, "--------------------->execInstallAPP()<------------------");
+ PackageInstaller.Session session = null;
+ try {
+ session = mContext.getPackageManager().getPackageInstaller().openSession(mSessionId);
+ Intent intent = new Intent();
+ PendingIntent pendingIntent;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
+ Log.d(TAG, "--------------FLAG_IMMUTABLE");
+ pendingIntent = PendingIntent.getBroadcast(mContext, 1, intent, PendingIntent.FLAG_IMMUTABLE);
+ } else {
+ Log.d(TAG, "--------------FLAG_UPDATE_CURRENT");
+ pendingIntent = PendingIntent.getBroadcast(mContext, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+ Log.d(TAG, "-----------pendingIntent");
+ session.commit(pendingIntent.getIntentSender());
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (null != session) {
+ session.close();
+ }
+ }
+ }
+
+ /**
+ * 通过文件流传输apk
+ *
+ * @param apkFilePath
+ * @return
+ */
+ private static boolean onTransfesApkFile(Context mContext, String apkFilePath, int mSessionId) {
+ Log.d(TAG, "---------->onTransfesApkFile()<---------------------");
+ InputStream in = null;
+ OutputStream out = null;
+ PackageInstaller.Session session = null;
+ boolean success = false;
+ try {
+ File apkFile = new File(apkFilePath);
+ session = mContext.getPackageManager().getPackageInstaller().openSession(mSessionId);
+ out = session.openWrite("base.apk", 0, apkFile.length());
+ in = new FileInputStream(apkFile);
+ int total = 0, c;
+ byte[] buffer = new byte[1024 * 1024];
+ while ((c = in.read(buffer)) != -1) {
+ total += c;
+ out.write(buffer, 0, c);
+ }
+ session.fsync(out);
+ Log.d(TAG, "streamed " + total + " bytes");
+ success = true;
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (null != session) {
+ session.close();
+ }
+ try {
+ if (null != out) {
+ out.close();
+ }
+ if (null != in) {
+ in.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return success;
+ }
+
+ public static void uninstall(Context mContext, String packageName) {
+
+ Intent broadcastIntent = new Intent();
+ PendingIntent pendingIntent;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
+ Log.d(TAG, "--------------FLAG_IMMUTABLE");
+ pendingIntent = PendingIntent.getBroadcast(mContext, 1, broadcastIntent, PendingIntent.FLAG_IMMUTABLE);
+ } else {
+ Log.d(TAG, "--------------FLAG_UPDATE_CURRENT");
+ pendingIntent = PendingIntent.getBroadcast(mContext, 1, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+ PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
+ packageInstaller.uninstall(packageName, pendingIntent.getIntentSender());
+ }
+
+ public static String getExternalStoragePath() {
+ String storagePath = Environment.getExternalStorageDirectory().getAbsolutePath();
+ try {
+ Log.d(TAG, "getExternalStoragePath path=" + storagePath);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "IllegalArgumentException when getExternalStoragePath:" + e);
+ }
+ Log.d(TAG, "getExternalStoragePath path=" + storagePath);
+ return storagePath ;
+ }
+
+ public static String getApkInfo(Context context, String apkPath) {
+ PackageManager pm = context.getPackageManager();
+ PackageInfo info = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
+ if (info != null) {
+ ApplicationInfo appInfo = info.applicationInfo;
+ String packageName = appInfo.packageName;
+ try {
+ return packageName;
+ } catch (OutOfMemoryError e) {
+
+ }
+ }
+ return null;
+ }
+
+ boolean isInstallApp(Context context,String packageName){
+ try {
+ mPackageManager.getApplicationInfo(packageName,PackageManager.GET_UNINSTALLED_PACKAGES);
+ return true;
+ } catch (NameNotFoundException e) {
+ // TODO: handle exception
+ return false;
+ }
+ }
+}
在AndroidManifest去注册一下:
diff --git a/vendor/mediatek/proprietary/packages/apps/MtkSettings/AndroidManifest.xml b/vendor/mediatek/proprietary/packages/apps/MtkSettings/AndroidManifest.xml
index 1ef5cb72af6..d75fd575810 100755
--- a/vendor/mediatek/proprietary/packages/apps/MtkSettings/AndroidManifest.xml
+++ b/vendor/mediatek/proprietary/packages/apps/MtkSettings/AndroidManifest.xml
@@ -708,6 +708,12 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+
+ <service android:name="com.android.settings.CopyFileService"
+ android:exported="true"/>
+
+ <service android:name="com.android.settings.InstallApkService"
+ android:exported="true"/>
<service android:name=".wifi.tether.TetherService"
android:exported="true"
这两个服务写完,还有一个问题需要解决,就是怎么让他开机的时候去拷贝,方法如下:
diff --git a/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigReceiver.java b/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigReceiver.java
old mode 100644
new mode 100755
index 369e61382db..ea69f01f8fd
--- a/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigReceiver.java
+++ b/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigReceiver.java
@@ -21,6 +21,12 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+import static com.android.settingslib.Utils.updateLocationEnabled;
+import android.provider.Settings;
+import android.os.UserHandle;
/**
* Receive broadcast when {@link StatsManager} restart, then check the anomaly config and
@@ -31,6 +37,16 @@ public class AnomalyConfigReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
+ SharedPreferences sharedPreferences = context.getSharedPreferences("first_boot",Context.MODE_PRIVATE);
+ boolean first_boot= sharedPreferences.getBoolean("first_boot", true);
+ if (first_boot) {
+ //SharedPreferences.Editor editor1 = sharedPreferences.edit();
+ //editor1.putBoolean("first_boot", false);
+ //editor1.apply();
+ Intent activityIntent = new Intent();
+ activityIntent.setClassName("com.android.settings", "com.android.settings.CopyFileService");
+ context.startService(activityIntent);
+ }
if (StatsManager.ACTION_STATSD_STARTED.equals(intent.getAction())
|| Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
final StatsManager statsManager = context.getSystemService(StatsManager.class);
以及还要添加一个静默安装的广播接收者:
diff --git a/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java b/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 94e0307df92..12b14aa1914 100755
--- a/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2081,6 +2081,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
filter = new IntentFilter();
filter.addAction(Intent.ACTION_DREAMING_STARTED);
filter.addAction(Intent.ACTION_DREAMING_STOPPED);
+ filter.addAction("android.intent.action.installapk");
context.registerReceiver(mDreamReceiver, filter);
// register for multiuser-relevant broadcasts
@@ -4518,6 +4519,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (mKeyguardDelegate != null) {
mKeyguardDelegate.onDreamingStopped();
}
+ } else if ("android.intent.action.installapk".equals(intent.getAction())) {
+ Intent apkInstallIntent = new Intent();
+ apkInstallIntent.setClassName("com.android.settings", "com.android.settings.InstallApkService");
+ context.startService(apkInstallIntent);
}
}
};
最后把需要安装的apk放在device/mediatek/common/mid/common/system/media/apks/路径下就可以了,在安卓源码编译中,放在这个路径下,它在编译完以后是在out/target/product/mssi_t_64_cn/system/media/apks/下面,我在拷贝服务里面的路径写的就是这个路径,在机器开机以后会把这个路径下的apk拷贝到/storage/emulated/0/Apks下面,然后就是静默安装了。 第三方apk添加特殊应用权限,在安卓十三的版本中不是在frameworks/base/core/java/android/app/AppOpsManager.java中,我改了好多次都没啥用,后面我问了公司的同事,他说是在alps_mssi/frameworks/base/services/core/java/com/android/server/appop/AppOpsService.java这个类中,方法如下:
diff --git a/alps_mssi/frameworks/base/services/core/java/com/android/server/appop/AppOpsService.java b/alps_mssi/frameworks/base/services/core/java/com/android/server/appop/AppOpsService.java
old mode 100644
new mode 100755
index e3c5e1d7d28..83a646c90cd
--- a/alps_mssi/frameworks/base/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/alps_mssi/frameworks/base/services/core/java/com/android/server/appop/AppOpsService.java
@@ -4823,6 +4823,7 @@ public class AppOpsService extends IAppOpsService.Stub {
return null;
}
+ edit = true;
if (uidState.pkgOps == null) {
if (!edit) {
return null;
@@ -4836,6 +4837,17 @@ public class AppOpsService extends IAppOpsService.Stub {
return null;
}
ops = new Ops(packageName, uidState);
+ Op op = new Op(uidState, packageName, AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uidState.uid);
+ op.mode = AppOpsManager.MODE_ALLOWED;
+ ops.put(op.op, op);
+
+ Op op1 = new Op(uidState, packageName, AppOpsManager.OP_GET_USAGE_STATS, uidState.uid);
+ op1.mode = AppOpsManager.MODE_ALLOWED;
+ ops.put(op1.op, op1);
+
+ Op op2 = new Op(uidState, packageName, AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE, uidState.uid);
+ op2.mode = AppOpsManager.MODE_ALLOWED;
+ ops.put(op2.op, op2);
uidState.pkgOps.put(packageName, ops);
}
这三个权限是:所有文件访问权限、显示在其他应用上层、使用情况访问权限,具体加什么看自己的需求,那个拷贝的方法不单单可以拷贝apk,视频、音频文件都可以拷贝,以及静默安装的方法也是支持多个apk静默安装的