package com.tws.plugin.core; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.HashMap; import tws.component.log.TwsLog; import android.app.Application; import android.content.BroadcastReceiver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.preference.PreferenceManager; import android.util.ArrayMap; import android.view.LayoutInflater; import com.tws.plugin.content.PluginDescriptor; import com.tws.plugin.core.android.HackContextImpl; import com.tws.plugin.core.android.HackResources; import com.tws.plugin.core.compat.CompatForSharedPreferencesImpl; import com.tws.plugin.core.localservice.LocalServiceManager; import com.tws.plugin.core.multidex.PluginMultiDexHelper; import com.tws.plugin.util.ProcessUtil; /** * 注意:意外覆写父类方法可能会抛出LingageError 也就是说如果要在这个类里添加非override的public方法的话要小心了。 */ public class PluginContextTheme extends PluginBaseContextWrapper { private static final String TAG = "rick_Print_TT:PluginContextTheme"; private int mThemeResource; Resources.Theme mTheme; private LayoutInflater mInflater; private ApplicationInfo mApplicationInfo; Resources mResources; private final ClassLoader mClassLoader; private Application mPluginApplication; protected final PluginDescriptor mPluginDescriptor; private ArrayList<BroadcastReceiver> receivers = new ArrayList<BroadcastReceiver>(); private boolean crackPackageManager = false; public PluginContextTheme(PluginDescriptor pluginDescriptor, Context base, Resources resources, ClassLoader classLoader) { super(base); mPluginDescriptor = pluginDescriptor; mResources = resources; mClassLoader = classLoader; if (!ProcessUtil.isPluginProcess()) { throw new IllegalAccessError("本类仅在插件进程使用"); } } public void setPluginApplication(Application pluginApplication) { this.mPluginApplication = pluginApplication; } @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); } @Override public ClassLoader getClassLoader() { return mClassLoader; } @Override public AssetManager getAssets() { return mResources.getAssets(); } @Override public Resources getResources() { return mResources; } /** * 传0表示使用系统默认主题,最终的现实样式和客户端程序的minSdk应该有关系。 即系统针对不同的minSdk设置了不同的默认主题样式 * 传非0的话表示传过来什么主题就显示什么主题 */ @Override public void setTheme(int resid) { mThemeResource = resid; initializeTheme(); } @Override public Resources.Theme getTheme() { if (mTheme != null) { return mTheme; } Integer result = HackResources.selectDefaultTheme(mThemeResource, getBaseContext().getApplicationInfo().targetSdkVersion); if (result != null) { mThemeResource = result; } initializeTheme(); return mTheme; } @Override public Object getSystemService(String name) { if (LAYOUT_INFLATER_SERVICE.equals(name)) { if (mInflater == null) { mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); } return mInflater; } Object service = getBaseContext().getSystemService(name); if (service == null) { service = LocalServiceManager.getService(name); } return service; } private void initializeTheme() { final boolean first = mTheme == null; if (first) { mTheme = getResources().newTheme(); Resources.Theme theme = getBaseContext().getTheme(); if (theme != null) { mTheme.setTo(theme); } } mTheme.applyStyle(mThemeResource, true); } @Override public String getPackageName() { // packagemanager、activitymanager、wifi、window、inputservice、toast等等系统服务会获取上下文的packageName去查询信息, // 插件自身的packageName对系统而言是不合法的,这里必须用宿主的packageName return PluginLoader.getApplication().getPackageName(); } // @hide public String getBasePackageName() { // ViewRootImpl中会调用这个方法, 这是个hide方法. return PluginLoader.getApplication().getPackageName(); } // //@hide public String getOpPackageName() { return PluginLoader.getApplication().getPackageName(); } @Override public Context getApplicationContext() { return mPluginApplication; } @Override public ApplicationInfo getApplicationInfo() { // 这里的ApplicationInfo是从LoadedApk中取出来的 // 由于目前插件之间是共用1个插件进程。LoadedApk只有1个,而ApplicationInfo每个插件都有一个, // 所以不能通过直接修改loadedApk中的内容来修正这个方法的返回值,而是将修正的过程放在Context中去做, // 避免多个插件之间造成干扰 if (mApplicationInfo == null) { try { mApplicationInfo = getPackageManager().getApplicationInfo(mPluginDescriptor.getPackageName(), 0); // 这里修正packageManager中hook时设置的插件packageName mApplicationInfo.packageName = getPackageName(); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } return mApplicationInfo; } @Override public String getPackageCodePath() { return mPluginDescriptor.getInstalledPath(); } @Override public String getPackageResourcePath() { return mPluginDescriptor.getInstalledPath(); } public PluginDescriptor getPluginDescriptor() { return mPluginDescriptor; } @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { if (receiver != null) { receivers.add(receiver); } return super.registerReceiver(receiver, filter); } @Override public void unregisterReceiver(BroadcastReceiver receiver) { if (receivers.contains(receiver)) { super.unregisterReceiver(receiver); receivers.remove(receiver); } } public void unregisterAllReceiver() { for (BroadcastReceiver br : receivers) { if (br != null) { super.unregisterReceiver(br); } } receivers.clear(); } @Override public PackageManager getPackageManager() { if (crackPackageManager) { // 欺骗MultDexInstaller, 使MultDexInstaller能得到正确的插件信息 return PluginMultiDexHelper.fixPackageManagerForMultDexInstaller(mPluginDescriptor.getPackageName(), super.getPackageManager()); } return super.getPackageManager(); } public void setCrackPackageManager(boolean crackPackageManager) { this.crackPackageManager = crackPackageManager; } /** * 隔离插件间的SharedPreferences * * @param name * @param mode * @return */ @Override public SharedPreferences getSharedPreferences(String name, int mode) { if (Build.VERSION.SDK_INT > 23) { synchronized (PluginContextTheme.class) { HackContextImpl impl = new HackContextImpl(getContextImpl()); ArrayMap<String, File> mSharedPrefsPaths = impl.getSharedPrefsPaths(); File sharedPrefsFile = new File(getPluginRootDir(), "shared_prefs"); String sharedPrefsPath = sharedPrefsFile.getAbsolutePath(); TwsLog.w(TAG, "getSharedPreferences sharedPrefsFile is " + sharedPrefsPath); if (mSharedPrefsPaths != null) { File file = mSharedPrefsPaths.get(name); // log if (file != null) { TwsLog.w(TAG, "mSharedPrefsPaths.get:" + name + " is " + file.getAbsolutePath()); } if (file != null && !file.getParent().equals(sharedPrefsPath)) { TwsLog.w(TAG, "mSharedPrefsPaths.remove:" + name); mSharedPrefsPaths.remove(name);// 置空之后再get会触发重建,则getDataDir有机会生效 } } File mPreferencesDir = impl.getPreferencesDir(); if (mPreferencesDir == null || !mPreferencesDir.getAbsolutePath().equals(sharedPrefsPath)) { TwsLog.w(TAG, "impl.setPreferencesDir(sharedPrefsFile)"); impl.setPreferencesDir(sharedPrefsFile); } } return getSharedPreferencesEx(name, mode); } // 这里之所以需要追加前缀是因为ContextImpl类中的全局静态缓存sSharedPrefs if (!name.startsWith(mPluginDescriptor.getPackageName() + "_")) { name = mPluginDescriptor.getPackageName() + "_" + name; } // 4.4以上版本缓存是延迟初始化的,这里增加这句调用是为了确保已经初始化,防止反射为空 PreferenceManager.getDefaultSharedPreferences(getBaseContext()); Object cache = HackContextImpl.getSharedPrefs(); if (Build.VERSION.SDK_INT >= 19 && cache instanceof ArrayMap) { synchronized (PluginContextTheme.class) { ArrayMap<String, ArrayMap<String, Object>> sSharedPrefs = (ArrayMap<String, ArrayMap<String, Object>>) cache; final String packageName = getPackageName(); ArrayMap<String, Object> packagePrefs = sSharedPrefs.get(packageName); if (packagePrefs == null) { packagePrefs = new ArrayMap<String, Object>(); sSharedPrefs.put(packageName, packagePrefs); } Object sp = packagePrefs.get(name); if (sp == null) { packagePrefs.put(name, CompatForSharedPreferencesImpl.newSharedPreferencesImpl( getSharedPrefsFile(name), mode, getPackageName())); } } } else if (cache instanceof HashMap) { HashMap<String, Object> sSharedPrefs = (HashMap<String, Object>) cache; Object sp = sSharedPrefs.get(name); if (sp == null) { sSharedPrefs.put(name, CompatForSharedPreferencesImpl.newSharedPreferencesImpl( getSharedPrefsFile(name), mode, getPackageName())); } } return super.getSharedPreferences(name, mode); } // android-M // removed public File getSharedPreferencesPath(String name) { return getSharedPrefsFile(name); } public File getSharedPrefsFile(String name) { if (!name.startsWith(mPluginDescriptor.getPackageName() + "_")) { name = mPluginDescriptor.getPackageName() + "_" + name; } return makeFilename(new File(getPluginRootDir(), "shared_prefs"), name + ".xml"); } @Override public File getDir(String name, int mode) { File dir = makeFilename(getPluginRootDir(), "app_" + name); if (!dir.exists()) { dir.mkdirs(); // setpermisssion } return dir; } @Override public File getFilesDir() { File dir = new File(getPluginRootDir(), "files"); if (!dir.exists()) { dir.mkdirs(); // setpermisssion } return dir; } @Override public File getFileStreamPath(String name) { return makeFilename(getFilesDir(), name); } @Override public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { final boolean append = (mode & MODE_APPEND) != 0; File f = makeFilename(getFilesDir(), name); try { FileOutputStream fos = new FileOutputStream(f, append); // setFilePermissionsFromMode(f.getPath(), mode, 0); return fos; } catch (FileNotFoundException e) { } return super.openFileOutput(name, mode); } @Override public FileInputStream openFileInput(String name) throws FileNotFoundException { File f = makeFilename(getFilesDir(), name); return new FileInputStream(f); } @Override public File getNoBackupFilesDir() { File dir = new File(getPluginRootDir(), "no_backup"); if (!dir.exists()) { dir.mkdirs(); // setpermisssion } return dir; } @Override public File getCacheDir() { File dir = new File(getPluginRootDir(), "cache"); if (!dir.exists()) { dir.mkdirs(); // setpermisssion } return dir; } @Override public File getCodeCacheDir() { File dir = new File(getPluginRootDir(), "code_cache"); if (!dir.exists()) { dir.mkdirs(); // setpermisssion } return dir; } @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { name = getAbsuloteDatabasePath(name); return super.openOrCreateDatabase(name, mode, factory); } @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { name = getAbsuloteDatabasePath(name); return super.openOrCreateDatabase(name, mode, factory, errorHandler); } @Override public boolean deleteDatabase(String name) { name = getAbsuloteDatabasePath(name); return super.deleteDatabase(name); } @Override public File getDatabasePath(String name) { name = getAbsuloteDatabasePath(name); return super.getDatabasePath(name); } @Override public String[] databaseList() { File f = new File(getPluginRootDir(), "databases"); final String[] list = f.list(); return (list != null) ? list : EMPTY_STRING_ARRAY; } @Override public String[] fileList() { final String[] list = getFilesDir().list(); return (list != null) ? list : EMPTY_STRING_ARRAY; } @Override public boolean deleteFile(String name) { File f = makeFilename(getFilesDir(), name); return f.delete(); } private String getAbsuloteDatabasePath(String name) { if (name.charAt(0) != File.separatorChar) { File f = makeFilename(new File(getPluginRootDir(), "databases"), name); name = f.getAbsolutePath(); } return name; } private File makeFilename(File base, String name) { if (name.indexOf(File.separatorChar) < 0) { if (!base.exists()) { base.mkdirs(); // setpermisssion } return new File(base, name); } throw new IllegalArgumentException("File " + name + " contains a path separator"); } private static final String[] EMPTY_STRING_ARRAY = {}; private File dataDir; // android-N // @Override public File getPluginRootDir() { if (dataDir == null) { File pluginFile = new File(mPluginDescriptor.getInstalledPath()); TwsLog.d(TAG, "getDataDir pluginFile is " + pluginFile.getAbsolutePath()); dataDir = new File(pluginFile.getParentFile().getParentFile(), "data"); if (!dataDir.exists()) { dataDir.mkdirs(); } } return dataDir; } public Context getOuter() { Context base = getBaseContext(); while (base instanceof ContextWrapper) { base = ((ContextWrapper) base).getBaseContext(); } if (HackContextImpl.instanceOf(base)) { base = new HackContextImpl(base).getOuterContext(); } return base; } }