package org.bbs.apklauncher; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.ZipFile; import org.bbs.apklauncher.api.ExportApi; import org.bbs.apkparser.ApkManifestParser; import org.bbs.apkparser.PackageInfoX; import org.bbs.apkparser.PackageInfoX.ActivityInfoX; import org.bbs.apkparser.PackageInfoX.ApplicationInfoX; import org.bbs.apkparser.PackageInfoX.IntentFilterX; import org.bbs.apkparser.PackageInfoX.PermissionTreeX; import org.bbs.apkparser.PackageInfoX.ServiceInfoX; import org.bbs.apkparser.PackageInfoX.UsesPermissionX; import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PermissionGroupInfo; import android.content.pm.ResolveInfo; import android.content.res.AssetManager; import android.content.res.Resources; import android.text.TextUtils; import android.util.Log; import dalvik.system.DexClassLoader; import ext.com.android.server.IntentResolver; public class ApkPackageManager extends BasePackageManager { private static final String TAG = ApkPackageManager.class.getSimpleName(); private static final String DIR_PLACEHOLDER = "placeholder"; private static /*final*/ String DIR_PLUGIN = "plugin_data"; public static final String APK_FILE_REG = ".*\\.apk"; private static final String APK_FILE_SUFFIX = ".apk"; private static final String PREF_EXTRACT_APK = ApkPackageManager.class.getName() + ""; private static final String PERF_KEY_APK_HAS_SCANNED = "apk_has_scanned"; private static final boolean DEBUG = ApkLauncherConfig.DEBUG && true; private static final boolean PROFILE = ApkLauncherConfig.PROFILE && true; private static final boolean DEBUG_PARSE = true; private static ApkPackageManager sInstance; public static Map<String, Reference<ClassLoader>> sApk2ClassLoaderMap = new HashMap<String, Reference<ClassLoader>>(); public static Map<String, WeakReference<Application>> sApk2ApplicationtMap = new HashMap<String, WeakReference<Application>>(); public static Map<String, WeakReference<ResourcesMerger>> sApk2ResourceMap = new HashMap<String, WeakReference<ResourcesMerger>>(); private InstallApks mInstalledApk; private Application mApplication; // TODO remove public public static Context sFileContext; private SerializableUtil mSerUtil; private PackageInfoX mHostPkgInfo; private ClassLoaderFactory mClassLoaderFactory; private AtomicBoolean mInited; private ActivityIntentResolver mActResolver; public static void setPluginDataDir(String name){ DIR_PLUGIN = name; } private ApkPackageManager() { mInited = new AtomicBoolean(); mActResolver = new ActivityIntentResolver(); } @Override public boolean isPermissionRevokedByPolicy(String permName, String pkgName) { return false; } /** * @return * * NOTE: <p> * you must init this before use by {@link #init(Application, File)}. */ @ExportApi public static ApkPackageManager getInstance() { if (null == sInstance) { sInstance = new ApkPackageManager(); } return sInstance; } public void setClassLoaderFactory(ClassLoaderFactory f) { mClassLoaderFactory = f; } public void reset(){ // deleteFileOrDir(super.get(DIR_PLACEHOLDER).getParentFile()); } /** * @param context * @param apkDir where apk file located. * */ public void init(Application context, String assetsPath, boolean overwrite){ long time = 0; if (PROFILE){ time = System.currentTimeMillis(); Log.d(TAG, "start profile[init]. assetsPath:" + assetsPath); } synchronized (mInited) { if (mInited.get()) { Log.w(TAG, "has inited, ignore."); return; } mApplication = context; sFileContext = new SdkContext(context); mInstalledApk = new InstallApks(); mSerUtil = new SerializableUtil(context); Version version = Version.getInstance((Application) mApplication.getApplicationContext()); if (!version.appUpdated()) { initOnPluginUpdateOnly(assetsPath, overwrite | version.firstUsage()); } else { initOnAppUpdateOnly(assetsPath, true); } Log.i(TAG, "in debug mode, always scan asset dir: " + assetsPath); if (overwrite) { scanAssetDir(assetsPath, overwrite); } mInited.set(true); } if (PROFILE){ time = System.currentTimeMillis() - time; Log.d(TAG, "end profile[init]: " + ( time / 1000.) + "s"); } } private void initOnAppUpdateOnly(String assetsPath, boolean overwrite) { if (DEBUG_PARSE) { Log.d(TAG, "initOnAppUpdateOnly. assetsPath: " + assetsPath + " overwrite: " + overwrite); } // for app update we'll not copy apk & libs. scanApkDir(getApkDir(), false, APK_FILE_REG); scanAssetDir(assetsPath, overwrite); scanUpdatePlugin(); } private void initOnPluginUpdateOnly(String assetsPath, boolean overwrite) { if (DEBUG_PARSE) { Log.d(TAG, "initOnPluginUpdateOnly. assetsPath: " + assetsPath + " overwrite: " + overwrite); } // step 1 scan asset dir if need. scanAssetDir(assetsPath, overwrite); // XXX replace will failed for first time. ??? // if (!hasUpdatedPlugin() // || true // ) { // Version version = Version.getInstance((Application) mApplication.getApplicationContext()); // if (version.appUpdated() || version.firstUsage()) { // Log.i(TAG, "re-build plugin info."); // // re-build install apk info. // scanApkDir(getApkDir(), false, APK_FILE_REG); // // // mSerUtil.put(mInfos); // // // for first time or update force copy new/delete old files. // overwrite |= true; // } else { // Log.i(TAG, "parse installed plugin info. [not impled]"); // scanApkDir(getApkDir(), false, APK_FILE_REG); // // mInfos = mSerUtil.get(); // } // } else { // // } // step 2 scan plugin dir if need, // TODO can we get plugin info without parsing??? scanApkDir(getApkDir(), false, APK_FILE_REG); // step 3 scan update plugin if need. scanUpdatePlugin(); } private void scanUpdatePlugin() { if (hasUpdatedPlugin()) { if (DEBUG_PARSE) { Log.d(TAG, "has update plguin."); } File autoUpdateDir = getAutoUpdatePluginDir(); scanApkDir(autoUpdateDir, true, APK_FILE_REG); deleteFileOrDir(autoUpdateDir); } } public boolean inInited(){ return mInited.get(); } boolean hasUpdatedPlugin(){ boolean has = false; String[] files = getAutoUpdatePluginDir().list(); has = files != null && files.length > 0; return has; } public static ResourcesMerger getTargetResource(String mTargetApkPath, Context context) { WeakReference<ResourcesMerger> rr = ApkPackageManager.sApk2ResourceMap.get(mTargetApkPath); Resources targetRes; ResourcesMerger resMerger; if (rr != null && rr.get() != null) { resMerger = rr.get(); targetRes = resMerger.mFirst; } else { targetRes = ApkUtil.loadApkResource(mTargetApkPath, context); resMerger = new ResourcesMerger(targetRes, context.getResources()); ApkPackageManager.sApk2ResourceMap.put(mTargetApkPath, new WeakReference<ResourcesMerger>(resMerger)); } return resMerger; } public ClassLoader createClassLoader(Context baseContext, PackageInfoX pInfo){ return createClassLoader(baseContext, pInfo.applicationInfo.publicSourceDir, pInfo.applicationInfo.nativeLibraryDir, pInfo.packageName); } public ClassLoader createClassLoader(Context baseContext, String apkPath, String libPath, String targetPackageName) { return createClassLoader(baseContext, apkPath, libPath, targetPackageName, false); } public ClassLoader createClassLoader(Context baseContext, String apkPath, String libPath, String targetPackageName, boolean force) { ClassLoader cl = ApkPackageManager.getClassLoader(targetPackageName); if (null == cl || force) { if (mClassLoaderFactory != null) { cl = mClassLoaderFactory.createClassLoader(this, baseContext, apkPath, libPath, targetPackageName); } else { String optPath = getOptDir().getPath(); cl = new DexClassLoader(apkPath, optPath, libPath, baseContext.getClassLoader()); // cl = new TargetClassLoader(apkPath, optPath, libPath, baseContext.getClassLoader(), targetPackageName, baseContext); } ApkPackageManager.putClassLoader(targetPackageName, (cl)); } return cl; } public static ClassLoader getClassLoader(String packageName) { Reference<ClassLoader> reference = sApk2ClassLoaderMap.get(packageName); if (null != reference) { ClassLoader c = reference.get(); return c; } return null; } public static void putClassLoader(String packageName, ClassLoader classLoader) { sApk2ClassLoaderMap.put(packageName, new SoftReference<ClassLoader>(classLoader)); } @ExportApi public static Application getApplication(String packageName) { WeakReference<Application> weakReference = sApk2ApplicationtMap.get(packageName); if (null != weakReference) { return weakReference.get(); } return null; } public static void putApplication(String packageName, Application app) { sApk2ApplicationtMap.put(packageName, new WeakReference<Application>(app)); } /** * all plug-related dir SHOULD base on this dir. * @return */ @ExportApi public File getPluginDir() { // can we get data-dir directly? File placeHolder = mApplication.getDir(DIR_PLACEHOLDER, 0); File dir = new File(placeHolder.getParent(), DIR_PLUGIN); dir.mkdirs(); assureDir(dir); return dir; // return mContext.getDir(PLUGIN_DIR_NAME, 0); } private void assureDir(File dir) { if (null == dir || !dir.isDirectory()){ throw new RuntimeException("can not creaet dir: " + dir); } } @ExportApi public File getAutoUpdatePluginDir() { File dir = new File(getPluginDir(), "auto_update"); dir.mkdirs(); return dir; } @ExportApi public File getApkDir() { File dir = new File(getPluginDir(), "app"); dir.mkdirs(); return dir; } @ExportApi public File getOptDir() { File dir = new File(getPluginDir(), "dalvik-cache"); dir.mkdirs(); return dir; } public File getAppDataDir(String packageName){ File dir = new File(getPluginDir() + "/data", packageName); dir.mkdirs(); return dir; } @ExportApi private File getDataDir(String name) { File dir = new File(getPluginDir(), name); dir.mkdirs(); return dir; } public void scanAssetDir(String assetsPath, boolean overwritee){ if (DEBUG_PARSE){ Log.d(TAG, "scanAssetDir. assetsPath: " + assetsPath + " overwritee: " + overwritee); } Version version = Version.getInstance((Application) mApplication.getApplicationContext()); if (version.appUpdated() || version.firstUsage() || overwritee ) { doScanApk(assetsPath); } else { reScanApkIfNecessary(assetsPath); } if (DEBUG_PARSE){ Log.d(TAG, "scanAssetDir done."); } } private void doScanApk(String assetsPath) { File assetPlguinDir = getDataDir("asset_plugin"); AssetManager am = mApplication.getResources().getAssets(); extractApkFromAsset(am, assetsPath, new File(assetPlguinDir.getPath())); scanApkDir(assetPlguinDir); deleteFileOrDir(assetPlguinDir); SharedPreferences s = sFileContext.getSharedPreferences(PREF_EXTRACT_APK, 0); s.edit().putBoolean(PERF_KEY_APK_HAS_SCANNED, true).commit(); } public static List<File> extractApkFromAsset(AssetManager am, String assetDir, File destDir) { long time = 0; if (PROFILE) { time = System.currentTimeMillis(); Log.d(TAG, "start profile[extractApkFromAsset]. assetSrc:" + assetDir + " dst:" + destDir); } ArrayList<File> copiedFiles = AndroidUtil.extractAssetFile(am, assetDir, destDir); if (PROFILE) { time = System.currentTimeMillis() - time; Log.d(TAG, "end profile[extractApkFromAsset]: " + ( time / 1000.) + "s"); } return copiedFiles; } private void reScanApkIfNecessary(String assetsPath) { SharedPreferences s = sFileContext.getSharedPreferences(PREF_EXTRACT_APK, 0); boolean scanned = s.getBoolean(PERF_KEY_APK_HAS_SCANNED, false); if (!scanned) { doScanApk(assetsPath); } else { if (DEBUG_PARSE) { Log.i(TAG, "assets path has scanned before, ignore. path: " + assetsPath); } } } @ExportApi public void scanApkDir(File apkDir) { scanApkDir(apkDir, true, APK_FILE_REG); } public void scanApkDir(File apkDir, boolean overwrite, String reg) { if (DEBUG){ //==========123456789012345678 Log.d(TAG, "parse dir : " + apkDir); } if (null == apkDir || !apkDir.exists()) { //==========123456789012345678 Log.w(TAG, "invalid dir: " + apkDir); return ; } String[] files = apkDir.list(); if (null == files) { //==========123456789012345678 Log.w(TAG, "impty dir : " + apkDir); return; } for (String f : files) { File file = new File(apkDir.getAbsolutePath() + "/" + f); parseApkFile(file, overwrite, reg); } // mSerUtil.put(mInfos); if (DEBUG){ //==========123456789012345678 Log.d(TAG, "parse dir done."); } } public PackageInfoX parseApkFile(String file){ return parseApkFile(new File(file), true, APK_FILE_REG); } public PackageInfoX parseApkFile(File file){ return parseApkFile(file, true, APK_FILE_REG); } public PackageInfoX parseApkFile(File file, boolean overwrite, String reg) { long time = 0; PackageInfoX info = null; if (DEBUG){ //==========123456789012345678 Log.d(TAG, "parse file : " + file + " overwrite: " + overwrite); } String fileName = file.getName(); boolean keepGoing = file.exists() && (null == reg || fileName.matches(reg)); if (!keepGoing) { if (file.exists()){ //==========123456789012345678 Log.w(TAG, "ignre file : " + file + " reg: " + reg); } else { //==========123456789012345678 Log.w(TAG, "ignre file : " + file + " not exist."); } return info; } if (PROFILE) { time = System.currentTimeMillis(); Log.d(TAG, "start profile[parseApkFile]. apk:" + file + " overwrite:" + overwrite); } info = ApkManifestParser.parseAPk(mApplication, file.getAbsolutePath()); try { File dest = file; if (overwrite) { dest = new File(getApkDir(), info.packageName + APK_FILE_SUFFIX); deleteFileOrDir(dest); AndroidUtil.copyFile(file, dest); info = ApkManifestParser.parseAPk(mApplication, dest.getAbsolutePath()); } //==========123456789012345678 Log.i(TAG, "plugin info : " + appInfoStr(info)); compareInfo(getHostPacageInfoX(), info); String reqSdkV = ""; if (info.applicationInfo.metaData != null ) { reqSdkV = info.applicationInfo.metaData.getString(ApkLauncher.MANIFEST_META_REQUIRE_MIN_SDK_VERSION); } if (TextUtils.isEmpty(reqSdkV)){ Log.w(TAG, "no " + ApkLauncher.MANIFEST_META_REQUIRE_MIN_SDK_VERSION + " specified in Manifest."); } else { String reqVersion = org.bbs.apklauncher.Version.extractVersion(reqSdkV); String sdkVersion = org.bbs.apklauncher.Version.extractVersion(org.bbs.apklauncher.Version.VERSION); if (org.bbs.apklauncher.Version.isNewerRaw(reqVersion,sdkVersion)){ Log.w(TAG, "plug require a higher sdk version. req version: " + reqVersion + " our version: " + sdkVersion); } } File destLibDir = new File(getAppDataDir(info.packageName), "/lib"); if (overwrite) { //TODO native lib deleteFileOrDir(destLibDir); String abi = SystemPropertiesProxy.get(mApplication, "ro.product.cpu.abi"); String abi2 = SystemPropertiesProxy.get(mApplication, "ro.product.cpu.abi2"); Log.d(TAG, "abi: " + abi + " abi2: " + abi2); String[] abis = new String[]{abi, abi2}; // String[] abis = Build.SUPPORTED_ABIS; final int L = abis.length; for (int i = L - 1 ; i >= 0; i--){ AndroidUtil.extractZipEntry(new ZipFile(info.applicationInfo.publicSourceDir), "lib/"+ abis[i], destLibDir); } // AndroidUtil.extractZipEntry(new ZipFile(info.applicationInfo.publicSourceDir), "lib/armeabi", destLibDir); // AndroidUtil.extractZipEntry(new ZipFile(info.applicationInfo.publicSourceDir), "lib/armeabi-v7a", destLibDir); } info.applicationInfo.nativeLibraryDir = destLibDir.getPath(); // asume there is only one apk. // ClassLoader cl = createClassLoader(mContext, // info.applicationInfo.sourceDir, // info.mLibPath, // info.applicationInfo.packageName, // true); mInstalledApk.addOrUpdate(info); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (PROFILE){ time = System.currentTimeMillis() - time; Log.d(TAG, "end profile[parseApkFile]: " + ( time / 1000.) + "s"); } return info; } private void compareInfo(PackageInfoX hostPacageInfoX, PackageInfoX info) { checkPermission(hostPacageInfoX, info); } private boolean deleteFileOrDir(String file) { return deleteFileOrDir(new File(file)); } private boolean deleteFileOrDir(File file) { boolean isD = file.isDirectory(); //==========123456789012345678 Log.i(TAG, "delete file: " + file + (isD ? "[D]" : "")); boolean ret = deleteFile_intenal(file); return ret; } private boolean deleteFile_intenal(File file) { if (file.isDirectory()) { String[] children = file.list(); for (int i = 0; i < children.length; i++) { File f = new File(file, children[i]); boolean success = deleteFile_intenal(f); if (!success) { Log.w(TAG, "error on deleting file " + f); return false; } } } // The directory is now empty so now it can be smoked return file.delete(); } private void checkPermission(PackageInfoX hostPacageInfoX, PackageInfoX info) { PackageInfoX host = getHostPacageInfoX(); List<UsesPermissionX> hostL = toList(host.mUsedPermissions); List<UsesPermissionX> targetL =toList(info.mUsedPermissions); List<UsesPermissionX> l = substract(hostL, targetL); if (l.size() > 0) { //----------1234567890123456789 Log.w(TAG, "unused permission:"); for (UsesPermissionX p : l){ Log.w(TAG, "++" + p.name); } } l = substract(targetL, hostL); if (l.size() > 0) { //----------1234567890123456789 Log.w(TAG, "need permission:"); for (UsesPermissionX p : l){ Log.w(TAG, "--" + p.name); } } List<PermissionGroupInfo> hostPG = toList(host.mPermissionGroups); List<PermissionGroupInfo> targetPG = toList(info.mPermissionGroups); List<PermissionGroupInfo> pg = substract(hostPG, targetPG); if (pg.size() > 0) { //----------12345678901234567890123 Log.w(TAG, "unused permission group:"); for (PermissionGroupInfo g : pg){ Log.w(TAG, "++" + g.name); } } pg = substract(targetPG, hostPG); if (pg.size() > 0) { //----------12345678901234567890123 Log.w(TAG, "need permission group:"); for (PermissionGroupInfo g : pg){ Log.w(TAG, "--" + g.name); } } List<PermissionTreeX> hostPT = toList(host.mPermissionTrees); List<PermissionTreeX> targetPT = toList(info.mPermissionTrees); List<PermissionTreeX> pt = substract(hostPT, targetPT); if (pt.size() > 0) { //----------12345678901234567890123 Log.w(TAG, "unused permission true:"); for (PermissionTreeX g : pt){ Log.w(TAG, "++" + g.name); } } pt = substract(targetPT, hostPT); if (pt.size() > 0) { //----------12345678901234567890123 Log.w(TAG, "unused permission true:"); for (PermissionTreeX g : pt){ Log.w(TAG, "--" + g.name); } } } private <T> List toList( T[] pgs) { List<T> list = new ArrayList<>(); if (pgs != null){ for (T p : pgs) { list.add(p); } } return list; } // List<UsesPermissionX> toList(UsesPermissionX[] ps) { // List<UsesPermissionX> list = new ArrayList<>(); // if (ps != null){ // for (UsesPermissionX p : ps) { // list.add(p); // } // } // return list; // } <T extends PackageItemInfo>List substract(List<T> left, List<T> right){ List<T> list = new ArrayList<>(); for (int i = 0; i < left.size() ; i++) { boolean found = false; for (int j = 0; j < right.size() ; j++) { if (left.get(i).name.equals(right.get(j).name)){ found = true; break; } } if (!found) { list.add(left.get(i)); } } return list; } PackageInfoX getHostPacageInfoX(){; if (mHostPkgInfo == null ) { mHostPkgInfo = ApkManifestParser.parseAPk(mApplication, mApplication.getApplicationInfo().publicSourceDir); } return mHostPkgInfo; } @ExportApi public List<PackageInfoX> getAllApks(){ return mInstalledApk; } public void deleteApk(PackageInfoX pInfo){ boolean delete = mInstalledApk.remove(pInfo); deleteFileOrDir(pInfo.applicationInfo.publicSourceDir); // XXX FileContext.ENBABLE_FILE is false, how can we do this? File dataDir = getAppDataDir(pInfo.packageName); // String dataDir = pInfo.applicationInfo.dataDir; deleteFileOrDir(dataDir); // TODO if delete dataDir is fixed, comment this deleteFileOrDir(pInfo.applicationInfo.nativeLibraryDir); if (DEBUG){ Log.i(TAG, "delete plugin. pInfo: " + pInfo + (delete ? " success" : " failed")); } } @ExportApi public ApplicationInfoX getApplicationInfo(String packageName) { ApplicationInfoX a = null; boolean has = false; for (PackageInfoX i : mInstalledApk) { if (packageName.equals(i.packageName)) { has = true; a = (ApplicationInfoX) i.applicationInfo; break; } } return a; } @ExportApi public PackageInfoX getPackageInfo(String packageName){ PackageInfoX p = null; for (PackageInfoX i : mInstalledApk) { if (i.packageName.equals(packageName)){ p = i; break; } } return p; } @ExportApi public boolean hasApplicationInfo(String className) { boolean has = false; for (PackageInfoX m : mInstalledApk) { if (className.equals(m.applicationInfo.packageName)) { has = true; break; } } return has; } @ExportApi public ActivityInfoX getActivityInfo(Class clazz) { return getActivityInfo(clazz.getName()); } @ExportApi public ActivityInfoX getActivityInfo(String className) { for (PackageInfoX m : mInstalledApk) { if (m.activities != null) { for (ActivityInfo a : m.activities) { ActivityInfoX aX = (ActivityInfoX) a; if (a.name.equals(className)) { return aX; } } } } return null; } public List<ActivityInfoX> getLauncherActivityInfo(){ List<ActivityInfoX> result = new ArrayList<>(); for (PackageInfoX m : mInstalledApk) { if (m.activities != null) { for (ActivityInfo a : m.activities) { ActivityInfoX aX = (ActivityInfoX) a; IntentFilterX[] filters = aX.mIntentFilters; if (filters != null && filters.length > 0){ for (IntentFilterX f : filters) { if (f.hasCategory(Intent.CATEGORY_LAUNCHER)) { result.add(aX); break; } } } } } } return result; } public List<ActivityInfoX> getLauncherActivityInfo(String packageName){ List<ActivityInfoX> result = new ArrayList<>(); List<ActivityInfoX> launchers = getLauncherActivityInfo(); for (ActivityInfoX i : launchers){ if (i.packageName.equals(packageName)){ result.add(i); } } return result; } @ExportApi public ServiceInfoX getServiceInfo(String className) { for (PackageInfoX m : mInstalledApk) { if (m.services != null && m.services.length > 0) { final int count = m.services.length; for (int i = 0 ; i < count; i++){ ServiceInfoX a = (ServiceInfoX) m.services[i]; if (className.equals(a.name)) { return a; } }} } return null; } static String appInfoStr(PackageInfoX info) { return info.packageName + "|" + info.versionCode + "|" + info.versionName; } @ExportApi public List<ResolveInfo> queryIntentActivities(Intent intent, int flag) { ComponentName comn = intent.getComponent(); if (null != comn){ List<ResolveInfo> res = new ArrayList<>(); boolean found = false; for (PackageInfoX p : mInstalledApk){ if ((p.packageName).equals(comn.getPackageName())){ for (ActivityInfo a : p.activities){ if (a.name.equals(comn.getClassName())){ ResolveInfo r = new ResolveInfo(); r.activityInfo = a; res.add(r); found = true; } } } } if (found){ return res; } } return mActResolver.queryIntent(intent, null, true, 0); // List<ResolveInfo> result = new ArrayList<>(); // for (PackageInfoX p : mInfos){ // queryIntentActivities(p.packageName, intent, flag, result); // } // return result; } @ExportApi public List<ResolveInfo> queryIntentActivities(String packageName, Intent intent, int flag) { List<ResolveInfo> result = new ArrayList<>(); queryIntentActivities(packageName, intent, flag, result); return result; } @ExportApi private void queryIntentActivities(String packageName, Intent intent, int flag, List<ResolveInfo> result){ if (TextUtils.isEmpty(packageName)) return; String action = intent.getAction(); Set<String> categories = intent.getCategories(); for (PackageInfoX p : mInstalledApk){ if (packageName.equals(p.packageName)){ for (ActivityInfo a : p.activities){ ActivityInfoX aX = (ActivityInfoX) a; if (aX.mIntentFilters == null) { continue; } for( IntentFilter intentFilter: aX.mIntentFilters) { if (intentFilter.matchAction(action) && intentFilter.matchCategories(categories) == null) { ResolveInfo info = new ResolveInfo(); info.activityInfo = a; result.add(info); } } } } } } public interface ClassLoaderFactory { ClassLoader createClassLoader(ApkPackageManager apkPackageManager, Context baseContext, String apkPath, String libPath, String targetPackageName); } class InstallApks extends ArrayList<PackageInfoX> implements Serializable { public void addOrUpdate(PackageInfoX info){ int index = -1; final int SIZE = size(); for (int i = 0 ; i < SIZE ; i++){ if (isSame(get(i),info)){ index = i; break; } } if (index >= 0) { PackageInfoX old = remove(index); //==========123456789012345678 Log.i(TAG, "plugin updated:"); Log.i(TAG, "old plugin :" + appInfoStr(old) + " md5:" + fileDigest(old.applicationInfo.publicSourceDir)); Log.i(TAG, "new plugin :" + appInfoStr(info) + " md5:" + fileDigest(info.applicationInfo.publicSourceDir)); } else { Log.i(TAG, "new plugin added."); } add(info); addActToResolver(info); } private String fileDigest(String apk) { try { MessageDigest digester; digester = MessageDigest.getInstance("MD5"); byte[] bytes = new byte[8192]; int byteCount; FileInputStream in = new FileInputStream(new File(apk)); while ((byteCount = in.read(bytes)) > 0) { digester.update(bytes, 0, byteCount); } byte[] digest = digester.digest(); String digestStr = ""; int L = digest.length; for (byte b : digest){ digestStr = digestStr + Integer.toHexString(b); } return digestStr; // return new String(digest); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return ""; } private void addActToResolver(PackageInfoX info) { if (info == null || info.activities == null) { return; } for (ActivityInfo a: info.activities) { ActivityInfoX aX = (ActivityInfoX) a; mActResolver.addActivity(aX); } } boolean isSame(PackageInfoX l, PackageInfoX r){ return l.packageName.equals(r.packageName); } @Override public PackageInfoX remove(int index) { PackageInfoX old = super.remove(index); removeActFromResolver(old); return old; } public void removeActFromResolver(PackageInfoX pInfo){ if (null != pInfo){ if (null != pInfo.activities){ for (ActivityInfo a : pInfo.activities){ ActivityInfoX aX = (ActivityInfoX) a; mActResolver.removeActivity(aX); } } } } @Override public boolean remove(Object object) { Object oldO = object; boolean remove = super.remove(object); if (remove){ removeActFromResolver((PackageInfoX) oldO); } return remove; } } class SerializableUtil { public Context mContext; private File mFile; SerializableUtil(Application context){ mContext = context; mFile = new File(getPluginDir(), "plugins.xml"); } void put(InstallApks apk) { try { ObjectOutputStream oop = new ObjectOutputStream(new FileOutputStream(mFile)); oop.writeObject(apk); oop.flush(); oop.close(); } catch (IOException e) { e.printStackTrace(); } } InstallApks get(){ Object o = null; try { ObjectInputStream oin = new ObjectInputStream(new FileInputStream(mFile)); o = oin.readObject(); oin.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return (InstallApks) o; } } //http://my.oschina.net/chaselinfo/blog/213393?p=1 public static class SystemPropertiesProxy { /** * 根据给定Key获取值. * @return 如果不存在该key则返回空字符串 * @throws IllegalArgumentException 如果key超过32个字符则抛出该异常 */ public static String get(Context context, String key) throws IllegalArgumentException { String ret= ""; try{ ClassLoader cl = context.getClassLoader(); @SuppressWarnings("rawtypes") Class SystemProperties = cl.loadClass("android.os.SystemProperties"); //参数类型 @SuppressWarnings("rawtypes") Class[] paramTypes= new Class[1]; paramTypes[0]= String.class; Method get = SystemProperties.getMethod("get", paramTypes); //参数 Object[] params= new Object[1]; params[0]= new String(key); ret= (String) get.invoke(SystemProperties, params); }catch( IllegalArgumentException iAE ){ throw iAE; }catch( Exception e ){ ret= ""; //TODO } return ret; } } // copied from https://github.com/luoqii/android_common_lib/blob/master/library/src/org/bbs/android/commonlib/Version.java static class Version { private static final int INVALID_CODE = -1; private /*static*/ /*final*/ String PREF_NAME = Version.class.getSimpleName() + ""; private static final String KEY_PREVIOUS_V_CODE = "previous_version_code"; private static final String KEY_PREVIOUS_V_NAME = "previous_version_name"; // private static final String TAG = Version.class.getSimpleName(); private static Map<Reference<Application>, Version> sInstances = new HashMap<Reference<Application>, Version>(); public static Version getInstance(Application appContext){ Version v = null; for (Reference<Application> r : sInstances.keySet()) { if (r != null && r.get() == appContext) { v = sInstances.get(r); if (null != v){ return v; } } } if (null == v){ v = new Version(appContext); sInstances.put(new WeakReference<Application>(appContext), v); } return v; } private int mCurrentVersionCode; private String mCurrentVersionName; private int mPreviousVersionCode; private String mPreviousVersionName; private boolean mInited; private Version(Application appContext){ PREF_NAME = appContext.getPackageName() + "." + PREF_NAME; init(appContext); }; void init(Application appContext){ if (mInited) { Log.w(TAG, "this has inited already, ignore."); return; } try { Log.i(TAG, "sdk version: " + org.bbs.apklauncher.Version.VERSION); PackageInfo pInfo = appContext.getPackageManager().getPackageInfo(appContext.getPackageName(), 0); mCurrentVersionCode = pInfo.versionCode; mCurrentVersionName = pInfo.versionName; SharedPreferences p = sFileContext.getSharedPreferences(PREF_NAME, 0); mPreviousVersionCode = p.getInt(KEY_PREVIOUS_V_CODE, INVALID_CODE); mPreviousVersionName = p.getString(KEY_PREVIOUS_V_NAME, ""); Log.i(TAG, "currentVersionCode : " + mCurrentVersionCode); Log.i(TAG, "currentVersionName : " + mCurrentVersionName); Log.i(TAG, "previousVersionCode : " + mPreviousVersionCode); Log.i(TAG, "previousVersionName : " + mPreviousVersionName); p.edit() .putInt(KEY_PREVIOUS_V_CODE, mCurrentVersionCode) .putString(KEY_PREVIOUS_V_NAME, mCurrentVersionName) .commit(); } catch (NameNotFoundException e) { throw new RuntimeException("can not get packageinfo. "); } mInited = true; } public int getVersionCode(){ return mCurrentVersionCode; } public String getVersionName() { return mCurrentVersionName; } public int getPreviousVersionCode(){ return mPreviousVersionCode; } public String getPreviousVersionName() { return mPreviousVersionName; } public boolean firstUsage(){ return mPreviousVersionCode == INVALID_CODE; } public boolean appUpdated(){ return mPreviousVersionCode != mCurrentVersionCode; } } public static class SdkContext extends FileContext { public SdkContext(Context base) { super(base); } @Override public String getTargetPackageName() { return "org.bbs.apklauncher.sdk"; } } // copied from PackageMangerService#ActivityIntentResolver final static class ActivityIntentResolver extends IntentResolver<IntentFilterX, ResolveInfo> { public void addActivity(ActivityInfoX act){ if (act.mIntentFilters != null && act.mIntentFilters.length > 0) { for (IntentFilterX f : act.mIntentFilters){ f.mCookie = act; addFilter(f); } } } public void removeActivity(ActivityInfoX act){ if (act.mIntentFilters != null && act.mIntentFilters.length > 0) { for (IntentFilterX f : act.mIntentFilters){ removeFilter(f); } } } @Override protected boolean isPackageForFilter(String packageName, IntentFilterX filter) { // TODO Auto-generated method stub return false; } @Override protected IntentFilterX[] newArray(int size) { return new IntentFilterX[size]; } @Override protected ResolveInfo newResult(IntentFilterX filter, int match, int userId) { // return super.newResult(filter, match, userId); ResolveInfo rInfo = new ResolveInfo(); rInfo.activityInfo = (ActivityInfo) filter.mCookie; return rInfo; } @Override protected void sortResults(List<ResolveInfo> results) { // TODO Auto-generated method stub // super.sortResults(results); Log.w(TAG, "sort need impled."); } } final static class ActivityIntentInfo extends IntentFilterX { public ActivityIntentInfo(IntentFilterX f) { super(f); } // public ActivityInfoX mActInfo; } }