diff --git a/.gitignore b/.gitignore index ea1103f..cb203ea 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ dependency-reduced-pom.xml buildNumber.properties local.properties +buildSrc diff --git a/AndroidStub/build.gradle b/AndroidStub/build.gradle index cbd6829..a522ba1 100644 --- a/AndroidStub/build.gradle +++ b/AndroidStub/build.gradle @@ -62,5 +62,7 @@ uploadArchives { } dependencies { - compile 'com.android.support:support-annotations:22.2.0' + api 'com.android.support:support-annotations:22.2.0' + api 'com.android.databinding:library:1.3.1' + api 'com.android.databinding:baseLibrary:3.0.0' } \ No newline at end of file diff --git a/AndroidStub/src/main/aidl/android/app/IServiceConnection.aidl b/AndroidStub/src/main/aidl/android/app/IServiceConnection.aidl index fb19b0b..cd5b86f 100644 --- a/AndroidStub/src/main/aidl/android/app/IServiceConnection.aidl +++ b/AndroidStub/src/main/aidl/android/app/IServiceConnection.aidl @@ -21,8 +21,8 @@ import android.content.ComponentName; /** @hide */ oneway interface IServiceConnection { - void connected(in ComponentName name, IBinder service); +// void connected(in ComponentName name, IBinder service); /** Added in Android O */ - //void connected(in ComponentName name, IBinder service, boolean dead); + void connected(in ComponentName name, IBinder service, boolean dead); } diff --git a/AndroidStub/src/main/java/android/app/Instrumentation.java b/AndroidStub/src/main/java/android/app/Instrumentation.java new file mode 100644 index 0000000..0b73401 --- /dev/null +++ b/AndroidStub/src/main/java/android/app/Instrumentation.java @@ -0,0 +1,68 @@ +package android.app; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.os.PersistableBundle; + +/** + * Created by qiaopu on 2018/5/7. + */ +public class Instrumentation { + + public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { + throw new RuntimeException("Stub!"); + } + + public void callApplicationOnCreate(Application app) { + throw new RuntimeException("Stub!"); + } + + public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { + throw new RuntimeException("Stub!"); + } + + public void callActivityOnCreate(Activity activity, Bundle icicle) { + throw new RuntimeException("Stub!"); + } + + public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) { + throw new RuntimeException("Stub!"); + } + + public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) { + throw new RuntimeException("Stub!"); + } + + public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { + throw new RuntimeException("Stub!"); + } + + public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options) { + throw new RuntimeException("Stub!"); + } + + public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) { + throw new RuntimeException("Stub!"); + } + + public Context getContext() { + throw new RuntimeException("Stub!"); + } + + public Context getTargetContext() { + throw new RuntimeException("Stub!"); + } + + public ComponentName getComponentName() { + throw new RuntimeException("Stub!"); + } + + public static final class ActivityResult { + public ActivityResult(int resultCode, Intent resultData) { + throw new RuntimeException("Stub!"); + } + } +} diff --git a/AndroidStub/src/main/java/android/app/ResourcesManager.java b/AndroidStub/src/main/java/android/app/ResourcesManager.java new file mode 100644 index 0000000..20ee690 --- /dev/null +++ b/AndroidStub/src/main/java/android/app/ResourcesManager.java @@ -0,0 +1,16 @@ +package android.app; + +/** + * Created by qiaopu on 2018/4/25. + */ +public class ResourcesManager { + + public static ResourcesManager getInstance() { + throw new RuntimeException("Stub!"); + } + + public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) { + throw new RuntimeException("Stub!"); + } + +} \ No newline at end of file diff --git a/AndroidStub/src/main/java/android/content/ContentResolver.java b/AndroidStub/src/main/java/android/content/ContentResolver.java new file mode 100644 index 0000000..f3c3ed3 --- /dev/null +++ b/AndroidStub/src/main/java/android/content/ContentResolver.java @@ -0,0 +1,40 @@ +package android.content; + +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Created by qiaopu on 2018/5/7. + */ +public abstract class ContentResolver { + + public ContentResolver(Context context) { + throw new RuntimeException("Stub!"); + } + + public final @Nullable + Bundle call(@NonNull Uri uri, @NonNull String method, + @Nullable String arg, @Nullable Bundle extras) { + throw new RuntimeException("Stub!"); + } + + protected abstract IContentProvider acquireProvider(Context c, String name); + + protected IContentProvider acquireExistingProvider(Context c, String name) { + throw new RuntimeException("Stub!"); + } + + public abstract boolean releaseProvider(IContentProvider icp); + + protected abstract IContentProvider acquireUnstableProvider(Context c, String name); + + public abstract boolean releaseUnstableProvider(IContentProvider icp); + + public abstract void unstableProviderDied(IContentProvider icp); + + public void appNotRespondingViaProvider(IContentProvider icp) { + throw new RuntimeException("Stub!"); + } +} diff --git a/AndroidStub/src/main/java/android/content/pm/PackageParser.java b/AndroidStub/src/main/java/android/content/pm/PackageParser.java index 573c1bd..aa5f7c3 100644 --- a/AndroidStub/src/main/java/android/content/pm/PackageParser.java +++ b/AndroidStub/src/main/java/android/content/pm/PackageParser.java @@ -377,6 +377,7 @@ public final static class Package { // Signatures that were read from the package. public Signature[] mSignatures; + public SigningDetails mSigningDetails; public Certificate[][] mCertificates; // For use by package manager service for quick lookup of @@ -737,5 +738,9 @@ public PackageParserException(int error, String detailMessage, Throwable throwab } } + + public static class SigningDetails { + public Signature[] signatures; + } } diff --git a/AndroidStub/src/main/java/android/content/res/CompatibilityInfo.java b/AndroidStub/src/main/java/android/content/res/CompatibilityInfo.java new file mode 100644 index 0000000..92fc45d --- /dev/null +++ b/AndroidStub/src/main/java/android/content/res/CompatibilityInfo.java @@ -0,0 +1,41 @@ +package android.content.res; + +import android.content.pm.ApplicationInfo; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Created by qiaopu on 2018/5/3. + */ +public class CompatibilityInfo implements Parcelable { + + public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, + boolean forceCompat) { + throw new RuntimeException("Stub!"); + } + + @Override + public int describeContents() { + throw new RuntimeException("Stub!"); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + throw new RuntimeException("Stub!"); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public CompatibilityInfo createFromParcel(Parcel source) { + throw new RuntimeException("Stub!"); + } + + @Override + public CompatibilityInfo[] newArray(int size) { + throw new RuntimeException("Stub!"); + } + }; + +} + diff --git a/AndroidStub/src/main/java/android/content/res/Resources.java b/AndroidStub/src/main/java/android/content/res/Resources.java new file mode 100644 index 0000000..bf2c66e --- /dev/null +++ b/AndroidStub/src/main/java/android/content/res/Resources.java @@ -0,0 +1,79 @@ +package android.content.res; + +import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; + +/** + * Created by qiaopu on 2018/5/18. + */ +public class Resources { + + public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) { + throw new RuntimeException("Stub!"); + } + + public final AssetManager getAssets() { + throw new RuntimeException("Stub!"); + } + + public int getColor(int id) throws NotFoundException { + throw new RuntimeException("Stub!"); + } + + public Configuration getConfiguration() { + throw new RuntimeException("Stub!"); + } + + public DisplayMetrics getDisplayMetrics() { + throw new RuntimeException("Stub!"); + } + + public Drawable getDrawable(int id) throws NotFoundException { + throw new RuntimeException("Stub!"); + } + + public String getString(int id) throws NotFoundException { + throw new RuntimeException("Stub!"); + } + + public CharSequence getText(int id) throws NotFoundException { + throw new RuntimeException("Stub!"); + } + + public XmlResourceParser getXml(int id) throws NotFoundException { + throw new RuntimeException("Stub!"); + } + + public ResourcesImpl getImpl() { + throw new RuntimeException("Stub!"); + } + + public final Theme newTheme() { + throw new RuntimeException("Stub!"); + } + + public void updateConfiguration(Configuration config, DisplayMetrics metrics) { + throw new RuntimeException("Stub!"); + } + + public final class Theme { + + public void applyStyle(int resId, boolean force) { + throw new RuntimeException("Stub!"); + } + + public TypedArray obtainStyledAttributes(int[] attrs) { + throw new RuntimeException("Stub!"); + } + + public void setTo(Theme other) { + throw new RuntimeException("Stub!"); + } + + } + + public static class NotFoundException extends RuntimeException { + + } + +} diff --git a/AndroidStub/src/main/java/android/content/res/ResourcesImpl.java b/AndroidStub/src/main/java/android/content/res/ResourcesImpl.java new file mode 100644 index 0000000..c25947f --- /dev/null +++ b/AndroidStub/src/main/java/android/content/res/ResourcesImpl.java @@ -0,0 +1,8 @@ +package android.content.res; + +/** + * Created by qiaopu on 2018/5/18. + */ +public class ResourcesImpl { + +} diff --git a/AndroidStub/src/main/java/android/content/res/ResourcesKey.java b/AndroidStub/src/main/java/android/content/res/ResourcesKey.java new file mode 100644 index 0000000..b3cecd8 --- /dev/null +++ b/AndroidStub/src/main/java/android/content/res/ResourcesKey.java @@ -0,0 +1,41 @@ +package android.content.res; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Created by qiaopu on 2018/5/3. + */ +public final class ResourcesKey { + @Nullable + public final String mResDir; + + @Nullable + public final String[] mSplitResDirs; + + @Nullable + public final String[] mOverlayDirs; + + @Nullable + public final String[] mLibDirs; + + public final int mDisplayId; + + @NonNull + public final Configuration mOverrideConfiguration; + + @NonNull + public final CompatibilityInfo mCompatInfo; + + public ResourcesKey(@Nullable String resDir, + @Nullable String[] splitResDirs, + @Nullable String[] overlayDirs, + @Nullable String[] libDirs, + int displayId, + @Nullable Configuration overrideConfig, + @Nullable CompatibilityInfo compatInfo) { + throw new RuntimeException("Stub!"); + } + +} + diff --git a/AndroidStub/src/main/java/android/databinding/DataBinderMapper.java b/AndroidStub/src/main/java/android/databinding/DataBinderMapper.java new file mode 100644 index 0000000..0477ee8 --- /dev/null +++ b/AndroidStub/src/main/java/android/databinding/DataBinderMapper.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.databinding; +import android.view.View; +/** + * This class will be stripped from the jar and then replaced by the annotation processor + * as part of the code generation step. This class's existence is just to ensure that + * compile works and no reflection is needed to access the generated class. + */ +class DataBinderMapper { + public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view, + int layoutId) { + return null; + } + ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View[] view, int layoutId) { + return null; + } + public int getLayoutId(String tag) { return 0; } + public String convertBrIdToString(int id) { + return null; + } + public static int TARGET_MIN_SDK = 0; +} \ No newline at end of file diff --git a/AndroidStub/src/main/java/android/databinding/DataBindingComponent.java b/AndroidStub/src/main/java/android/databinding/DataBindingComponent.java new file mode 100644 index 0000000..a2cefb4 --- /dev/null +++ b/AndroidStub/src/main/java/android/databinding/DataBindingComponent.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.databinding; +/** + * This interface is generated during compilation to contain getters for all used instance + * BindingAdapters. When a BindingAdapter is an instance method, an instance of the class + * implementing the method must be instantiated. This interface will be generated with a getter + * for each class with the name get* where * is simple class name of the declaring BindingAdapter + * class/interface. Name collisions will be resolved by adding a numeric suffix to the getter. + *

+ * An instance of this class may also be passed into static or instance BindingAdapters as the + * first parameter. + *

+ * If you are using Dagger 2, you should extend this interface and annotate the extended interface + * as a Component. + */ +public interface DataBindingComponent { +} \ No newline at end of file diff --git a/CoreLibrary/build.gradle b/CoreLibrary/build.gradle index 12e14f3..840f587 100644 --- a/CoreLibrary/build.gradle +++ b/CoreLibrary/build.gradle @@ -1,3 +1,7 @@ +import com.android.build.gradle.api.ApplicationVariant +import com.android.build.gradle.api.LibraryVariant +import com.google.common.base.Joiner + apply plugin: 'com.android.library' android { @@ -29,13 +33,31 @@ repositories { jcenter() } +final String projectAndroidStub = ':AndroidStub' + dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:22.2.0' - provided project(':AndroidStub') + provided project(projectAndroidStub) testCompile 'junit:junit:4.12' } +// Using Stub classes first when compiling. +afterEvaluate { + project.android.libraryVariants.each { LibraryVariant variant -> + variant.javaCompile.doFirst { JavaCompile javaCompile -> + String projectAndroidStubPath = project.project(projectAndroidStub).projectDir.canonicalPath +// println "projectAndroidStubPath: ${projectAndroidStubPath}" + File stubPath = javaCompile.classpath.find { + it.canonicalPath.startsWith(projectAndroidStubPath) + } + if (stubPath == null) { + throw new RuntimeException("reset bootclasspath error.") + } + javaCompile.options.setBootClasspath(Joiner.on(File.pathSeparator).join(stubPath, javaCompile.options.bootClasspath)) + } + } +} + apply from: 'upload.gradle' diff --git a/CoreLibrary/src/main/AndroidManifest.xml b/CoreLibrary/src/main/AndroidManifest.xml index 06c85a7..27bc178 100644 --- a/CoreLibrary/src/main/AndroidManifest.xml +++ b/CoreLibrary/src/main/AndroidManifest.xml @@ -6,54 +6,56 @@ - + + - - + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - + - + diff --git a/CoreLibrary/src/main/java/android/content/ContentResolverWrapper.java b/CoreLibrary/src/main/java/android/content/ContentResolverWrapper.java new file mode 100644 index 0000000..b2d4da2 --- /dev/null +++ b/CoreLibrary/src/main/java/android/content/ContentResolverWrapper.java @@ -0,0 +1,59 @@ +package android.content; + +import android.annotation.TargetApi; +import android.os.Build; + +/** + * Wrapper of {@link ContentResolver} + * Created by qiaopu on 2018/5/7. + */ +public abstract class ContentResolverWrapper extends ContentResolver { + + ContentResolver mBase; + + public ContentResolverWrapper(Context context) { + super(context); + mBase = context.getContentResolver(); + } + + @Override + protected IContentProvider acquireProvider(Context context, String auth) { + return mBase.acquireProvider(context, auth); + } + + @Override + protected IContentProvider acquireExistingProvider(Context context, String auth) { + return mBase.acquireExistingProvider(context, auth); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Override + protected IContentProvider acquireUnstableProvider(Context context, String auth) { + return mBase.acquireUnstableProvider(context, auth); + } + + @Override + public boolean releaseProvider(IContentProvider icp) { + return mBase.releaseProvider(icp); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Override + public boolean releaseUnstableProvider(IContentProvider icp) { + return mBase.releaseUnstableProvider(icp); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Override + public void unstableProviderDied(IContentProvider icp) { + mBase.unstableProviderDied(icp); + } + + @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) + @Override + public void appNotRespondingViaProvider(IContentProvider icp) { + // dark greylist in Android P +// mBase.appNotRespondingViaProvider(icp); + } + +} diff --git a/CoreLibrary/src/main/java/android/databinding/DataBinderMapperProxy.java b/CoreLibrary/src/main/java/android/databinding/DataBinderMapperProxy.java new file mode 100644 index 0000000..fd56d2a --- /dev/null +++ b/CoreLibrary/src/main/java/android/databinding/DataBinderMapperProxy.java @@ -0,0 +1,123 @@ +package android.databinding; + +import android.support.annotation.NonNull; +import android.util.Log; +import android.view.View; + +import com.didi.virtualapk.PluginManager; +import com.didi.virtualapk.internal.Constants; +import com.didi.virtualapk.internal.LoadedPlugin; + +import java.util.LinkedList; + +/** + * Replace {@link DataBindingUtil#sMapper}. + * Created by qiaopu on 2018/4/11. + */ +public class DataBinderMapperProxy extends DataBinderMapper implements PluginManager.Callback { + public static final String TAG = Constants.TAG_PREFIX + "DataBinderMapperProxy"; + + private final LinkedList mMappers; + private DataBinderMapper[] mCache; + + public DataBinderMapperProxy(@NonNull Object source) { + mMappers = new LinkedList<>(); + + addMapper((DataBinderMapper) source); + } + + @Override + public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view, int layoutId) { + ViewDataBinding viewDataBinding; + + for (DataBinderMapper mapper : getCache()) { + viewDataBinding = mapper.getDataBinder(bindingComponent, view, layoutId); + if (viewDataBinding != null) { +// Log.d(TAG, "Found by mapper: " + mapper); + return viewDataBinding; + } + } + + return null; + } + + @Override + ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View[] view, int layoutId) { + ViewDataBinding viewDataBinding; + + for (DataBinderMapper mapper : getCache()) { + viewDataBinding = mapper.getDataBinder(bindingComponent, view, layoutId); + if (viewDataBinding != null) { +// Log.d(TAG, "Found by mapper: " + mapper); + return viewDataBinding; + } + } + + return null; + } + + @Override + public int getLayoutId(String tag) { + int layoutId; + + for (DataBinderMapper mapper : getCache()) { + layoutId = mapper.getLayoutId(tag); + if (layoutId != 0) { +// Log.d(TAG, "Found by mapper: " + mapper); + return layoutId; + } + } + + return 0; + } + + @Override + public String convertBrIdToString(int id) { + String brId; + + for (DataBinderMapper mapper : getCache()) { + brId = mapper.convertBrIdToString(id); + if (brId != null) { +// Log.d(TAG, "Found by mapper: " + mapper); + return brId; + } + } + + return null; + } + + @Override + public void onAddedLoadedPlugin(LoadedPlugin plugin) { + try { + String clsName = "android.databinding.DataBinderMapper_" + plugin.getPackageName().replace('.', '_'); + Log.d(TAG, "Try to find the class: " + clsName); + Class cls = Class.forName(clsName, true, plugin.getClassLoader()); + Object obj = cls.newInstance(); + + addMapper((DataBinderMapper) obj); + + } catch (Exception e) { + Log.w(TAG, e); + } + } + + private void addMapper(DataBinderMapper mapper) { + int size = 0; + synchronized (mMappers) { + mMappers.add(mapper); + mCache = null; + size = mMappers.size(); + } + + Log.d(TAG, "Added mapper: " + mapper + ", size: " + size); + } + + private DataBinderMapper[] getCache() { + synchronized (mMappers) { + if (mCache == null) { + mCache = mMappers.toArray(new DataBinderMapper[mMappers.size()]); + } + return mCache; + } + } +} diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/PluginManager.java b/CoreLibrary/src/main/java/com/didi/virtualapk/PluginManager.java index c74237d..e9fbb91 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/PluginManager.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/PluginManager.java @@ -26,26 +26,32 @@ import android.content.Context; import android.content.IContentProvider; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Build; +import android.os.Bundle; +import android.os.Handler; import android.util.Log; import android.util.Singleton; -import com.didi.virtualapk.delegate.IContentProviderProxy; -import com.didi.virtualapk.internal.PluginContentResolver; import com.didi.virtualapk.delegate.ActivityManagerProxy; +import com.didi.virtualapk.delegate.IContentProviderProxy; +import com.didi.virtualapk.delegate.RemoteContentProvider; import com.didi.virtualapk.internal.ComponentsHandler; +import com.didi.virtualapk.internal.Constants; import com.didi.virtualapk.internal.LoadedPlugin; import com.didi.virtualapk.internal.VAInstrumentation; -import com.didi.virtualapk.utils.PluginUtil; -import com.didi.virtualapk.utils.ReflectUtil; +import com.didi.virtualapk.internal.utils.PluginUtil; +import com.didi.virtualapk.utils.Reflector; import com.didi.virtualapk.utils.RunUtil; import java.io.File; -import java.io.FileNotFoundException; +import java.io.FileInputStream; +import java.io.InputStream; import java.lang.reflect.Field; +import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -57,52 +63,89 @@ */ public class PluginManager { - public static final String TAG = "PluginManager"; + public static final String TAG = Constants.TAG_PREFIX + "PluginManager"; private static volatile PluginManager sInstance = null; // Context of host app - private Context mContext; - private ComponentsHandler mComponentsHandler; - private Map mPlugins = new ConcurrentHashMap<>(); + protected final Context mContext; + protected final Application mApplication; + protected ComponentsHandler mComponentsHandler; + protected final Map mPlugins = new ConcurrentHashMap<>(); + protected final List mCallbacks = new ArrayList<>(); - private Instrumentation mInstrumentation; // Hooked instrumentation - private IActivityManager mActivityManager; // Hooked IActivityManager binder - private IContentProvider mIContentProvider; // Hooked IContentProvider binder + protected VAInstrumentation mInstrumentation; // Hooked instrumentation + protected IActivityManager mActivityManager; // Hooked IActivityManager binder + protected IContentProvider mIContentProvider; // Hooked IContentProvider binder public static PluginManager getInstance(Context base) { if (sInstance == null) { synchronized (PluginManager.class) { - if (sInstance == null) - sInstance = new PluginManager(base); + if (sInstance == null) { + sInstance = createInstance(base); + } } } return sInstance; } - - private PluginManager(Context context) { - Context app = context.getApplicationContext(); - if (app == null) { - this.mContext = context; - } else { - this.mContext = ((Application)app).getBaseContext(); + + private static PluginManager createInstance(Context context) { + try { + Bundle metaData = context.getPackageManager() + .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA) + .metaData; + + if (metaData == null) { + return new PluginManager(context); + } + + String factoryClass = metaData.getString("VA_FACTORY"); + + if (factoryClass == null) { + return new PluginManager(context); + } + + PluginManager pluginManager = Reflector.on(factoryClass).method("create", Context.class).call(context); + + if (pluginManager != null) { + Log.d(TAG, "Created a instance of " + pluginManager.getClass()); + return pluginManager; + } + + } catch (Exception e) { + Log.w(TAG, "Created the instance error!", e); } - prepare(); + + return new PluginManager(context); } - private void prepare() { - Systems.sHostContext = getHostContext(); - this.hookInstrumentationAndHandler(); - if (Build.VERSION.SDK_INT >= 26) { - this.hookAMSForO(); + protected PluginManager(Context context) { + if (context instanceof Application) { + this.mApplication = (Application) context; + this.mContext = mApplication.getBaseContext(); } else { - this.hookSystemServices(); + final Context app = context.getApplicationContext(); + if (app == null) { + this.mContext = context; + this.mApplication = ActivityThread.currentApplication(); + } else { + this.mApplication = (Application) app; + this.mContext = mApplication.getBaseContext(); + } } + + mComponentsHandler = createComponentsHandler(); + hookCurrentProcess(); + } + + protected void hookCurrentProcess() { + hookInstrumentationAndHandler(); + hookSystemServices(); + hookDataBindingUtil(); } public void init() { - mComponentsHandler = new ComponentsHandler(this); RunUtil.getThreadPool().execute(new Runnable() { @Override public void run() { @@ -111,66 +154,120 @@ public void run() { }); } - private void doInWorkThread() { + protected void doInWorkThread() { + } + + public Application getHostApplication() { + return this.mApplication; + } + + protected ComponentsHandler createComponentsHandler() { + return new ComponentsHandler(this); + } + + protected VAInstrumentation createInstrumentation(Instrumentation origin) throws Exception { + return new VAInstrumentation(this, origin); + } + + protected ActivityManagerProxy createActivityManagerProxy(IActivityManager origin) throws Exception { + return new ActivityManagerProxy(this, origin); + } + + protected LoadedPlugin createLoadedPlugin(File apk) throws Exception { + return new LoadedPlugin(this, this.mContext, apk); + } + + protected void hookDataBindingUtil() { + Reflector.QuietReflector reflector = Reflector.QuietReflector.on("android.databinding.DataBindingUtil").field("sMapper"); + Object old = reflector.get(); + if (old != null) { + try { + Callback callback = Reflector.on("android.databinding.DataBinderMapperProxy").constructor().newInstance(); + reflector.set(callback); + addCallback(callback); + Log.d(TAG, "hookDataBindingUtil succeed : " + callback); + } catch (Reflector.ReflectedException e) { + Log.w(TAG, e); + } + } + } + + public void addCallback(Callback callback) { + if (callback == null) { + return; + } + synchronized (mCallbacks) { + if (mCallbacks.contains(callback)) { + throw new RuntimeException("Already added " + callback + "!"); + } + mCallbacks.add(callback); + } + } + + public void removeCallback(Callback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } } /** * hookSystemServices, but need to compatible with Android O in future. */ - private void hookSystemServices() { + protected void hookSystemServices() { try { - Singleton defaultSingleton = (Singleton) ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault"); - IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get()); + Singleton defaultSingleton; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get(); + } else { + defaultSingleton = Reflector.on(ActivityManagerNative.class).field("gDefault").get(); + } + IActivityManager origin = defaultSingleton.get(); + IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class }, + createActivityManagerProxy(origin)); // Hook IActivityManager from ActivityManagerNative - ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy); + Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy); if (defaultSingleton.get() == activityManagerProxy) { this.mActivityManager = activityManagerProxy; + Log.d(TAG, "hookSystemServices succeed : " + mActivityManager); } } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); } } - - - private void hookAMSForO() { + + protected void hookInstrumentationAndHandler() { try { - Singleton defaultSingleton = (Singleton) ReflectUtil.getField(ActivityManager.class, null, "IActivityManagerSingleton"); - IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get()); - ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void hookInstrumentationAndHandler() { - try { - Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext); - if (baseInstrumentation.getClass().getName().contains("lbe")) { - // reject executing in paralell space, for example, lbe. - System.exit(0); - } - - final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation); - Object activityThread = ReflectUtil.getActivityThread(this.mContext); - ReflectUtil.setInstrumentation(activityThread, instrumentation); - ReflectUtil.setHandlerCallback(this.mContext, instrumentation); + ActivityThread activityThread = ActivityThread.currentActivityThread(); + Instrumentation baseInstrumentation = activityThread.getInstrumentation(); +// if (baseInstrumentation.getClass().getName().contains("lbe")) { +// // reject executing in paralell space, for example, lbe. +// System.exit(0); +// } + + final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation); + + Reflector.with(activityThread).field("mInstrumentation").set(instrumentation); + Handler mainHandler = Reflector.with(activityThread).method("getHandler").call(); + Reflector.with(mainHandler).field("mCallback").set(instrumentation); this.mInstrumentation = instrumentation; + Log.d(TAG, "hookInstrumentationAndHandler succeed : " + mInstrumentation); } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); } } - private void hookIContentProviderAsNeeded() { - Uri uri = Uri.parse(PluginContentResolver.getUri(mContext)); + protected void hookIContentProviderAsNeeded() { + Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext)); mContext.getContentResolver().call(uri, "wakeup", null, null); try { Field authority = null; - Field mProvider = null; - ActivityThread activityThread = (ActivityThread) ReflectUtil.getActivityThread(mContext); - Map mProviderMap = (Map) ReflectUtil.getField(activityThread.getClass(), activityThread, "mProviderMap"); - Iterator iter = mProviderMap.entrySet().iterator(); + Field provider = null; + ActivityThread activityThread = ActivityThread.currentActivityThread(); + Map providerMap = Reflector.with(activityThread).field("mProviderMap").get(); + Iterator iter = providerMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); @@ -185,12 +282,12 @@ private void hookIContentProviderAsNeeded() { } auth = (String) authority.get(key); } - if (auth.equals(PluginContentResolver.getAuthority(mContext))) { - if (mProvider == null) { - mProvider = val.getClass().getDeclaredField("mProvider"); - mProvider.setAccessible(true); + if (auth.equals(RemoteContentProvider.getAuthority(mContext))) { + if (provider == null) { + provider = val.getClass().getDeclaredField("mProvider"); + provider.setAccessible(true); } - IContentProvider rawProvider = (IContentProvider) mProvider.get(val); + IContentProvider rawProvider = (IContentProvider) provider.get(val); IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider); mIContentProvider = proxy; Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider); @@ -198,7 +295,7 @@ private void hookIContentProviderAsNeeded() { } } } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); } } @@ -213,25 +310,33 @@ public void loadPlugin(File apk) throws Exception { } if (!apk.exists()) { - throw new FileNotFoundException(apk.getAbsolutePath()); + // throw the FileNotFoundException by opening a stream. + InputStream in = new FileInputStream(apk); + in.close(); } - LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk); - if (null != plugin) { - this.mPlugins.put(plugin.getPackageName(), plugin); - // try to invoke plugin's application - plugin.invokeApplication(); - } else { - throw new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath()); + LoadedPlugin plugin = createLoadedPlugin(apk); + + if (null == plugin) { + throw new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath()); + } + + this.mPlugins.put(plugin.getPackageName(), plugin); + synchronized (mCallbacks) { + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onAddedLoadedPlugin(plugin); + } } } public LoadedPlugin getLoadedPlugin(Intent intent) { - ComponentName component = PluginUtil.getComponent(intent); - return getLoadedPlugin(component.getPackageName()); + return getLoadedPlugin(PluginUtil.getComponent(intent)); } public LoadedPlugin getLoadedPlugin(ComponentName component) { + if (component == null) { + return null; + } return this.getLoadedPlugin(component.getPackageName()); } @@ -249,7 +354,7 @@ public Context getHostContext() { return this.mContext; } - public Instrumentation getInstrumentation() { + public VAInstrumentation getInstrumentation() { return this.mInstrumentation; } @@ -357,4 +462,7 @@ public List queryBroadcastReceivers(Intent intent, int flags) { return resolveInfos; } + public interface Callback { + void onAddedLoadedPlugin(LoadedPlugin plugin); + } } \ No newline at end of file diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/Systems.java b/CoreLibrary/src/main/java/com/didi/virtualapk/Systems.java deleted file mode 100644 index e913573..0000000 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/Systems.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.didi.virtualapk; - -import android.content.Context; - - -/** - * Created by renyugang on 16/12/22. - */ - -/** - * This is God class, you should not know where it's from and any details. - */ -public class Systems { - - static Context sHostContext; - - /** - * get a Context object anywhere you want. - * @return a Context object - */ - public static Context getContext() { - return sHostContext; - } - -} diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/ActivityManagerProxy.java b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/ActivityManagerProxy.java index 8b60bf4..05e9c5a 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/ActivityManagerProxy.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/ActivityManagerProxy.java @@ -33,28 +33,24 @@ import android.util.Log; import com.didi.virtualapk.PluginManager; -import com.didi.virtualapk.utils.PluginUtil; +import com.didi.virtualapk.internal.Constants; +import com.didi.virtualapk.internal.utils.PluginUtil; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; /** * @author johnsonlee */ public class ActivityManagerProxy implements InvocationHandler { - private static final String TAG = "IActivityManagerProxy"; + private static final String TAG = Constants.TAG_PREFIX + "IActivityManagerProxy"; public static final int INTENT_SENDER_BROADCAST = 1; public static final int INTENT_SENDER_ACTIVITY = 2; public static final int INTENT_SENDER_ACTIVITY_RESULT = 3; public static final int INTENT_SENDER_SERVICE = 4; - public static IActivityManager newInstance(PluginManager pluginManager, IActivityManager activityManager) { - return (IActivityManager) Proxy.newProxyInstance(activityManager.getClass().getClassLoader(), new Class[] { IActivityManager.class }, new ActivityManagerProxy(pluginManager, activityManager)); - } - private PluginManager mPluginManager; private IActivityManager mActivityManager; @@ -87,25 +83,25 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl try { return bindService(proxy, method, args); } catch (Throwable e) { - e.printStackTrace(); + Log.w(TAG, e); } } else if ("unbindService".equals(method.getName())) { try { return unbindService(proxy, method, args); } catch (Throwable e) { - e.printStackTrace(); + Log.w(TAG, e); } } else if ("getIntentSender".equals(method.getName())) { try { getIntentSender(method, args); } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); } } else if ("overridePendingTransition".equals(method.getName())){ try { overridePendingTransition(method, args); } catch (Exception e){ - e.printStackTrace(); + Log.w(TAG, e); } } @@ -135,7 +131,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl } - private Object startService(Object proxy, Method method, Object[] args) throws Throwable { + protected Object startService(Object proxy, Method method, Object[] args) throws Throwable { IApplicationThread appThread = (IApplicationThread) args[0]; Intent target = (Intent) args[1]; ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0); @@ -147,7 +143,7 @@ private Object startService(Object proxy, Method method, Object[] args) throws T return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE); } - private Object stopService(Object proxy, Method method, Object[] args) throws Throwable { + protected Object stopService(Object proxy, Method method, Object[] args) throws Throwable { Intent target = (Intent) args[1]; ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0); if (null == resolveInfo || null == resolveInfo.serviceInfo) { @@ -159,7 +155,7 @@ private Object stopService(Object proxy, Method method, Object[] args) throws Th return 1; } - private Object stopServiceToken(Object proxy, Method method, Object[] args) throws Throwable { + protected Object stopServiceToken(Object proxy, Method method, Object[] args) throws Throwable { ComponentName component = (ComponentName) args[0]; Intent target = new Intent().setComponent(component); ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0); @@ -172,7 +168,7 @@ private Object stopServiceToken(Object proxy, Method method, Object[] args) thro return true; } - private Object bindService(Object proxy, Method method, Object[] args) throws Throwable { + protected Object bindService(Object proxy, Method method, Object[] args) throws Throwable { Intent target = (Intent) args[2]; ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0); if (null == resolveInfo || null == resolveInfo.serviceInfo) { @@ -187,7 +183,7 @@ private Object bindService(Object proxy, Method method, Object[] args) throws Th return 1; } - private Object unbindService(Object proxy, Method method, Object[] args) throws Throwable { + protected Object unbindService(Object proxy, Method method, Object[] args) throws Throwable { IBinder iServiceConnection = (IBinder)args[0]; Intent target = mPluginManager.getComponentsHandler().forgetIServiceConnection(iServiceConnection); if (target == null) { @@ -200,12 +196,12 @@ private Object unbindService(Object proxy, Method method, Object[] args) throws return true; } - private ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { + protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command); return mPluginManager.getHostContext().startService(wrapperIntent); } - private Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { + protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { // fill in service with ComponentName target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name)); String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation(); @@ -225,7 +221,7 @@ private Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundl return intent; } - private void getIntentSender(Method method, Object[] args) { + protected void getIntentSender(Method method, Object[] args) { String hostPackageName = mPluginManager.getHostContext().getPackageName(); args[1] = hostPackageName; @@ -246,7 +242,7 @@ private void getIntentSender(Method method, Object[] args) { } } - private void overridePendingTransition(Method method, Object[] args) { + protected void overridePendingTransition(Method method, Object[] args) { String hostPackageName = mPluginManager.getHostContext().getPackageName(); args[1] = hostPackageName; } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/IContentProviderProxy.java b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/IContentProviderProxy.java index 07282cd..6f7b042 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/IContentProviderProxy.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/IContentProviderProxy.java @@ -24,6 +24,7 @@ import android.util.Log; import com.didi.virtualapk.PluginManager; +import com.didi.virtualapk.internal.Constants; import com.didi.virtualapk.internal.LoadedPlugin; import com.didi.virtualapk.internal.PluginContentResolver; @@ -33,14 +34,12 @@ import java.lang.reflect.Proxy; import java.util.Arrays; -import static com.didi.virtualapk.delegate.RemoteContentProvider.KEY_WRAPPER_URI; - /** * Created by renyugang on 16/12/8. */ public class IContentProviderProxy implements InvocationHandler { - private static final String TAG = "IContentProviderProxy"; + private static final String TAG = Constants.TAG_PREFIX + "IContentProviderProxy"; private IContentProvider mBase; private Context mContext; @@ -84,7 +83,7 @@ private void wrapperUri(Method method, Object[] args) { if (method.getName().equals("call")) { bundleInCallMethod = getBundleParameter(args); if (bundleInCallMethod != null) { - String uriString = bundleInCallMethod.getString(KEY_WRAPPER_URI); + String uriString = bundleInCallMethod.getString(RemoteContentProvider.KEY_WRAPPER_URI); if (uriString != null) { uri = Uri.parse(uriString); } @@ -100,14 +99,9 @@ private void wrapperUri(Method method, Object[] args) { if (info != null) { String pkg = info.packageName; LoadedPlugin plugin = pluginManager.getLoadedPlugin(pkg); - String pluginUri = Uri.encode(uri.toString()); - StringBuilder builder = new StringBuilder(PluginContentResolver.getUri(mContext)); - builder.append("/?plugin=" + plugin.getLocation()); - builder.append("&pkg=" + pkg); - builder.append("&uri=" + pluginUri); - Uri wrapperUri = Uri.parse(builder.toString()); + Uri wrapperUri = PluginContentResolver.wrapperUri(plugin, uri); if (method.getName().equals("call")) { - bundleInCallMethod.putString(KEY_WRAPPER_URI, wrapperUri.toString()); + bundleInCallMethod.putString(RemoteContentProvider.KEY_WRAPPER_URI, wrapperUri.toString()); } else { args[index] = wrapperUri; } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/LocalService.java b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/LocalService.java index b544422..cd4c5f1 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/LocalService.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/LocalService.java @@ -31,9 +31,10 @@ import android.util.Log; import com.didi.virtualapk.PluginManager; +import com.didi.virtualapk.internal.Constants; import com.didi.virtualapk.internal.LoadedPlugin; -import com.didi.virtualapk.utils.PluginUtil; -import com.didi.virtualapk.utils.ReflectUtil; +import com.didi.virtualapk.internal.utils.PluginUtil; +import com.didi.virtualapk.utils.Reflector; import java.lang.reflect.Method; @@ -41,7 +42,7 @@ * @author johnsonlee */ public class LocalService extends Service { - private static final String TAG = "LocalService"; + private static final String TAG = Constants.TAG_PREFIX + "LocalService"; /** * The target service, usually it's a plugin service intent @@ -83,11 +84,16 @@ public int onStartCommand(Intent intent, int flags, int startId) { ComponentName component = target.getComponent(); LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); + + if (plugin == null) { + Log.w(TAG, "Error target: " + target.toURI()); + return START_STICKY; + } // ClassNotFoundException when unmarshalling in Android 5.1 target.setExtrasClassLoader(plugin.getClassLoader()); switch (command) { case EXTRA_COMMAND_START_SERVICE: { - ActivityThread mainThread = (ActivityThread)ReflectUtil.getActivityThread(getBaseContext()); + ActivityThread mainThread = ActivityThread.currentActivityThread(); IApplicationThread appThread = mainThread.getApplicationThread(); Service service; @@ -114,7 +120,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { break; } case EXTRA_COMMAND_BIND_SERVICE: { - ActivityThread mainThread = (ActivityThread)ReflectUtil.getActivityThread(getBaseContext()); + ActivityThread mainThread = ActivityThread.currentActivityThread(); IApplicationThread appThread = mainThread.getApplicationThread(); Service service = null; @@ -133,7 +139,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { service.onCreate(); this.mPluginManager.getComponentsHandler().rememberService(component, service); } catch (Throwable t) { - t.printStackTrace(); + Log.w(TAG, t); } } try { @@ -141,14 +147,12 @@ public int onStartCommand(Intent intent, int flags, int startId) { IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc"); IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection); if (Build.VERSION.SDK_INT >= 26) { - ReflectUtil.invokeNoException(IServiceConnection.class, iServiceConnection, "connected", - new Class[]{ComponentName.class, IBinder.class, boolean.class}, - new Object[]{component, binder, false}); + iServiceConnection.connected(component, binder, false); } else { - iServiceConnection.connected(component, binder); + Reflector.QuietReflector.with(iServiceConnection).method("connected", ComponentName.class, IBinder.class).call(component, binder); } } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); } break; } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteContentProvider.java b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteContentProvider.java index ed4e03d..e37e455 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteContentProvider.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteContentProvider.java @@ -30,6 +30,7 @@ import android.util.Log; import com.didi.virtualapk.PluginManager; +import com.didi.virtualapk.internal.Constants; import com.didi.virtualapk.internal.LoadedPlugin; import com.didi.virtualapk.utils.RunUtil; @@ -44,7 +45,7 @@ */ public class RemoteContentProvider extends ContentProvider { - private static final String TAG = "RemoteContentProvider"; + private static final String TAG = Constants.TAG_PREFIX + "RemoteContentProvider"; public static final String KEY_PKG = "pkg"; public static final String KEY_PLUGIN = "plugin"; @@ -76,7 +77,7 @@ private ContentProvider getContentProvider(final Uri uri) { try { pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN))); } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); } } @@ -91,7 +92,7 @@ public void run() { contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo); sCachedProviders.put(auth, contentProvider); } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); } } }, true); @@ -209,4 +210,12 @@ public Bundle call(String method, String arg, Bundle extras) { return null; } + public static String getAuthority(Context context) { + return context.getPackageName() + ".VirtualAPK.Provider"; + } + + public static String getUri(Context context) { + return "content://" + getAuthority(context); + } + } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteService.java b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteService.java index af007f1..6b3e73b 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteService.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteService.java @@ -19,8 +19,10 @@ import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; +import android.util.Log; import com.didi.virtualapk.PluginManager; +import com.didi.virtualapk.internal.Constants; import com.didi.virtualapk.internal.LoadedPlugin; import java.io.File; @@ -29,6 +31,8 @@ * @author johnsonlee */ public class RemoteService extends LocalService { + + private static final String TAG = Constants.TAG_PREFIX + "RemoteService"; @Override public IBinder onBind(Intent intent) { @@ -50,7 +54,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { try { PluginManager.getInstance(this).loadPlugin(new File(pluginLocation)); } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); } } } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/StubActivity.java b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/StubActivity.java new file mode 100644 index 0000000..18c458d --- /dev/null +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/delegate/StubActivity.java @@ -0,0 +1,36 @@ +package com.didi.virtualapk.delegate; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.support.annotation.Nullable; + +/** + * Created by qiaopu on 2018/6/13. + */ +public class StubActivity extends Activity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Go to the main activity + Intent mainIntent = getPackageManager().getLaunchIntentForPackage(getPackageName()); + + if (mainIntent == null) { + mainIntent = new Intent(Intent.ACTION_MAIN); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + mainIntent.setPackage(getPackageName()); + + ResolveInfo resolveInfo = getPackageManager().resolveActivity(mainIntent, 0); + + if (resolveInfo != null) { + mainIntent.setClassName(this, resolveInfo.activityInfo.name); + } + } + + startActivity(mainIntent); + + finish(); + } +} diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ActivityLifecycleCallbacksProxy.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ActivityLifecycleCallbacksProxy.java new file mode 100644 index 0000000..1da3868 --- /dev/null +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ActivityLifecycleCallbacksProxy.java @@ -0,0 +1,104 @@ +package com.didi.virtualapk.internal; + +import android.app.Activity; +import android.app.ActivityThread; +import android.app.Application; +import android.os.Bundle; + +import com.didi.virtualapk.utils.Reflector; + +import java.util.ArrayList; + +/** + * Created by qiaopu on 2017/8/9. + */ +class ActivityLifecycleCallbacksProxy implements Application.ActivityLifecycleCallbacks { + + final ArrayList mActivityLifecycleCallbacks = + Reflector.QuietReflector.with(ActivityThread.currentApplication()).field("mActivityLifecycleCallbacks").get(); + + Object[] collectActivityLifecycleCallbacks() { + if (mActivityLifecycleCallbacks == null) { + return null; + } + Object[] callbacks = null; + synchronized (mActivityLifecycleCallbacks) { + if (mActivityLifecycleCallbacks.size() > 0) { + callbacks = mActivityLifecycleCallbacks.toArray(); + } + } + return callbacks; + } + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityCreated(activity, + savedInstanceState); + } + } + } + + @Override + public void onActivityStarted(Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityStarted(activity); + } + } + } + + @Override + public void onActivityResumed(Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityResumed(activity); + } + } + } + + @Override + public void onActivityPaused(Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPaused(activity); + } + } + } + + @Override + public void onActivityStopped(Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityStopped(activity); + } + } + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivitySaveInstanceState(activity, + outState); + } + } + } + + @Override + public void onActivityDestroyed(Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityDestroyed(activity); + } + } + } +} diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ComponentsHandler.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ComponentsHandler.java index ee035e7..85ad067 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ComponentsHandler.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ComponentsHandler.java @@ -37,7 +37,7 @@ public class ComponentsHandler { - public static final String TAG = "PluginManager"; + public static final String TAG = Constants.TAG_PREFIX + "PluginManager"; private Context mContext; private PluginManager mPluginManager; diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/Constants.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/Constants.java index 7c5f564..b139f77 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/Constants.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/Constants.java @@ -30,5 +30,8 @@ public class Constants { public static final boolean COMBINE_RESOURCES = true; public static final boolean COMBINE_CLASSLOADER = true; public static final boolean DEBUG = true; + + public static final String TAG = "VA"; + public static final String TAG_PREFIX = TAG + "."; } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/LoadedPlugin.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/LoadedPlugin.java index 88689f0..59962a6 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/LoadedPlugin.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/LoadedPlugin.java @@ -27,10 +27,12 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ChangedPackages; import android.content.pm.FeatureInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; +import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PermissionGroupInfo; @@ -38,26 +40,27 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.SharedLibraryInfo; +import android.content.pm.VersionedPackage; import android.content.res.AssetManager; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; -import android.os.Bundle; import android.os.Process; import android.os.UserHandle; -import android.support.annotation.WorkerThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import com.didi.virtualapk.PluginManager; -import com.didi.virtualapk.utils.DexUtil; -import com.didi.virtualapk.utils.PackageParserCompat; -import com.didi.virtualapk.utils.PluginUtil; -import com.didi.virtualapk.utils.ReflectUtil; +import com.didi.virtualapk.internal.utils.DexUtil; +import com.didi.virtualapk.internal.utils.PackageParserCompat; +import com.didi.virtualapk.internal.utils.PluginUtil; +import com.didi.virtualapk.utils.Reflector; import com.didi.virtualapk.utils.RunUtil; import java.io.File; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -70,79 +73,79 @@ /** * Created by renyugang on 16/8/9. */ -public final class LoadedPlugin { +public class LoadedPlugin { - public static final String TAG = "LoadedPlugin"; + public static final String TAG = Constants.TAG_PREFIX + "LoadedPlugin"; - public static LoadedPlugin create(PluginManager pluginManager, Context host, File apk) throws Exception { - return new LoadedPlugin(pluginManager, host, apk); + protected File getDir(Context context, String name) { + return context.getDir(name, Context.MODE_PRIVATE); } - - private static ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) { - File dexOutputDir = context.getDir(Constants.OPTIMIZE_DIR, Context.MODE_PRIVATE); + + protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception { + File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR); String dexOutputPath = dexOutputDir.getAbsolutePath(); DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent); if (Constants.COMBINE_CLASSLOADER) { - try { - DexUtil.insertDex(loader); - } catch (Exception e) { - e.printStackTrace(); - } + DexUtil.insertDex(loader, parent, libsDir); } return loader; } - private static AssetManager createAssetManager(Context context, File apk) { - try { - AssetManager am = AssetManager.class.newInstance(); - ReflectUtil.invoke(AssetManager.class, am, "addAssetPath", apk.getAbsolutePath()); - return am; - } catch (Exception e) { - e.printStackTrace(); - return null; - } + protected AssetManager createAssetManager(Context context, File apk) throws Exception { + AssetManager am = AssetManager.class.newInstance(); + Reflector.with(am).method("addAssetPath", String.class).call(apk.getAbsolutePath()); + return am; } - @WorkerThread - private static Resources createResources(Context context, File apk) { + protected Resources createResources(Context context, String packageName, File apk) throws Exception { if (Constants.COMBINE_RESOURCES) { - Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath()); - ResourcesManager.hookResources(context, resources); - return resources; + return ResourcesManager.createResources(context, packageName, apk); } else { Resources hostResources = context.getResources(); AssetManager assetManager = createAssetManager(context, apk); return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); } } + + protected PluginPackageManager createPluginPackageManager() { + return new PluginPackageManager(); + } + + public PluginContext createPluginContext(Context context) { + if (context == null) { + return new PluginContext(this); + } + + return new PluginContext(this, context); + } - private static ResolveInfo chooseBestActivity(Intent intent, String s, int flags, List query) { + protected ResolveInfo chooseBestActivity(Intent intent, String s, int flags, List query) { return query.get(0); } - private final String mLocation; - private PluginManager mPluginManager; - private Context mHostContext; - private Context mPluginContext; - private final File mNativeLibDir; - private final PackageParser.Package mPackage; - private final PackageInfo mPackageInfo; - private Resources mResources; - private ClassLoader mClassLoader; - private PluginPackageManager mPackageManager; - - private Map mActivityInfos; - private Map mServiceInfos; - private Map mReceiverInfos; - private Map mProviderInfos; - private Map mProviders; // key is authorities of provider - private Map mInstrumentationInfos; - - private Application mApplication; - - LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws PackageParser.PackageParserException { + protected final String mLocation; + protected PluginManager mPluginManager; + protected Context mHostContext; + protected Context mPluginContext; + protected final File mNativeLibDir; + protected final PackageParser.Package mPackage; + protected final PackageInfo mPackageInfo; + protected Resources mResources; + protected ClassLoader mClassLoader; + protected PluginPackageManager mPackageManager; + + protected Map mActivityInfos; + protected Map mServiceInfos; + protected Map mReceiverInfos; + protected Map mProviderInfos; + protected Map mProviders; // key is authorities of provider + protected Map mInstrumentationInfos; + + protected Application mApplication; + + public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception { this.mPluginManager = pluginManager; this.mHostContext = context; this.mLocation = apk.getAbsolutePath(); @@ -151,7 +154,19 @@ private static ResolveInfo chooseBestActivity(Intent intent, String s, int flags this.mPackageInfo = new PackageInfo(); this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo; this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath(); - this.mPackageInfo.signatures = this.mPackage.mSignatures; + + if (Build.VERSION.SDK_INT >= 28 + || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview + try { + this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures; + } catch (Throwable e) { + PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); + this.mPackageInfo.signatures = info.signatures; + } + } else { + this.mPackageInfo.signatures = this.mPackage.mSignatures; + } + this.mPackageInfo.packageName = this.mPackage.packageName; if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) { throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName); @@ -159,10 +174,11 @@ private static ResolveInfo chooseBestActivity(Intent intent, String s, int flags this.mPackageInfo.versionCode = this.mPackage.mVersionCode; this.mPackageInfo.versionName = this.mPackage.mVersionName; this.mPackageInfo.permissions = new PermissionInfo[0]; - this.mPackageManager = new PluginPackageManager(); - this.mPluginContext = new PluginContext(this); - this.mNativeLibDir = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE); - this.mResources = createResources(context, apk); + this.mPackageManager = createPluginPackageManager(); + this.mPluginContext = createPluginContext(null); + this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR); + this.mPackage.applicationInfo.nativeLibraryDir = this.mNativeLibDir.getAbsolutePath(); + this.mResources = createResources(context, getPackageName(), apk); this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader()); tryToCopyNativeLib(apk); @@ -178,6 +194,7 @@ private static ResolveInfo chooseBestActivity(Intent intent, String s, int flags // Cache activities Map activityInfos = new HashMap(); for (PackageParser.Activity activity : this.mPackage.activities) { + activity.info.metaData = activity.metaData; activityInfos.put(activity.getComponentName(), activity.info); } this.mActivityInfos = Collections.unmodifiableMap(activityInfos); @@ -206,25 +223,21 @@ private static ResolveInfo chooseBestActivity(Intent intent, String s, int flags Map receivers = new HashMap(); for (PackageParser.Activity receiver : this.mPackage.receivers) { receivers.put(receiver.getComponentName(), receiver.info); - - try { - BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance()); - for (PackageParser.ActivityIntentInfo aii : receiver.intents) { - this.mHostContext.registerReceiver(br, aii); - } - } catch (Exception e) { - e.printStackTrace(); + + BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance()); + for (PackageParser.ActivityIntentInfo aii : receiver.intents) { + this.mHostContext.registerReceiver(br, aii); } } this.mReceiverInfos = Collections.unmodifiableMap(receivers); this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]); + + // try to invoke plugin's application + invokeApplication(); } - private void tryToCopyNativeLib(File apk) { - Bundle metaData = this.mPackageInfo.applicationInfo.metaData; - if (metaData != null && metaData.getBoolean("VA_IS_HAVE_LIB")) { - PluginUtil.copyNativeLib(apk, mHostContext, mPackageInfo, mNativeLibDir); - } + protected void tryToCopyNativeLib(File apk) throws Exception { + PluginUtil.copyNativeLib(apk, mHostContext, mPackageInfo, mNativeLibDir); } public String getLocation() { @@ -271,18 +284,26 @@ public Application getApplication() { return mApplication; } - public void invokeApplication() { - if (mApplication != null) { - return; - } - + public void invokeApplication() throws Exception { + final Exception[] temp = new Exception[1]; // make sure application's callback is run on ui thread. RunUtil.runOnUiThread(new Runnable() { @Override public void run() { - mApplication = makeApplication(false, mPluginManager.getInstrumentation()); + if (mApplication != null) { + return; + } + try { + mApplication = makeApplication(false, mPluginManager.getInstrumentation()); + } catch (Exception e) { + temp[0] = e; + } } }, true); + + if (temp[0] != null) { + throw temp[0]; + } } public String getPackageResourcePath() { @@ -359,14 +380,10 @@ public Resources.Theme getTheme() { } public void setTheme(int resid) { - try { - ReflectUtil.setField(Resources.class, this.mResources, "mThemeResId", resid); - } catch (Exception e) { - e.printStackTrace(); - } + Reflector.QuietReflector.with(this.mResources).field("mThemeResId").set(resid); } - private Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { + protected Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) throws Exception { if (null != this.mApplication) { return this.mApplication; } @@ -375,15 +392,12 @@ private Application makeApplication(boolean forceDefaultAppClass, Instrumentatio if (forceDefaultAppClass || null == appClass) { appClass = "android.app.Application"; } - - try { - this.mApplication = instrumentation.newApplication(this.mClassLoader, appClass, this.getPluginContext()); - instrumentation.callApplicationOnCreate(this.mApplication); - return this.mApplication; - } catch (Exception e) { - e.printStackTrace(); - return null; - } + + this.mApplication = instrumentation.newApplication(this.mClassLoader, appClass, this.getPluginContext()); + // inject activityLifecycleCallbacks of the host application + mApplication.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksProxy()); + instrumentation.callApplicationOnCreate(this.mApplication); + return this.mApplication; } public ResolveInfo resolveActivity(Intent intent, int flags) { @@ -488,7 +502,7 @@ public ProviderInfo resolveContentProvider(String name, int flags) { return this.mProviders.get(name); } - private boolean match(PackageParser.Component component, ComponentName target) { + protected boolean match(PackageParser.Component component, ComponentName target) { ComponentName source = component.getComponentName(); if (source == target) return true; if (source != null && target != null @@ -503,9 +517,9 @@ private boolean match(PackageParser.Component component, ComponentName target) { /** * @author johnsonlee */ - private class PluginPackageManager extends PackageManager { + protected class PluginPackageManager extends PackageManager { - private PackageManager mHostPackageManager = mHostContext.getPackageManager(); + protected PackageManager mHostPackageManager = mHostContext.getPackageManager(); @Override public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException { @@ -517,7 +531,19 @@ public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotF return this.mHostPackageManager.getPackageInfo(packageName, flags); } + + @TargetApi(Build.VERSION_CODES.O) + @Override + public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int i) throws NameNotFoundException { + LoadedPlugin plugin = mPluginManager.getLoadedPlugin(versionedPackage.getPackageName()); + if (null != plugin) { + return plugin.mPackageInfo; + } + + return this.mHostPackageManager.getPackageInfo(versionedPackage, i); + } + @Override public String[] currentToCanonicalPackageNames(String[] names) { return this.mHostPackageManager.currentToCanonicalPackageNames(names); @@ -529,7 +555,7 @@ public String[] canonicalToCurrentPackageNames(String[] names) { } @Override - public Intent getLaunchIntentForPackage(String packageName) { + public Intent getLaunchIntentForPackage(@NonNull String packageName) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.getLaunchIntent(); @@ -538,9 +564,9 @@ public Intent getLaunchIntentForPackage(String packageName) { return this.mHostPackageManager.getLaunchIntentForPackage(packageName); } - @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public Intent getLeanbackLaunchIntentForPackage(String packageName) { + @Override + public Intent getLeanbackLaunchIntentForPackage(@NonNull String packageName) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.getLeanbackLaunchIntent(); @@ -550,9 +576,21 @@ public Intent getLeanbackLaunchIntentForPackage(String packageName) { } @Override - public int[] getPackageGids(String packageName) throws NameNotFoundException { + public int[] getPackageGids(@NonNull String packageName) throws NameNotFoundException { return this.mHostPackageManager.getPackageGids(packageName); } + + @TargetApi(Build.VERSION_CODES.N) + @Override + public int[] getPackageGids(String packageName, int flags) throws NameNotFoundException { + return this.mHostPackageManager.getPackageGids(packageName, flags); + } + + @TargetApi(Build.VERSION_CODES.N) + @Override + public int getPackageUid(String packageName, int flags) throws NameNotFoundException { + return this.mHostPackageManager.getPackageUid(packageName, flags); + } @Override public PermissionInfo getPermissionInfo(String name, int flags) throws NameNotFoundException { @@ -629,8 +667,8 @@ public List getInstalledPackages(int flags) { return this.mHostPackageManager.getInstalledPackages(flags); } - @Override @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + @Override public List getPackagesHoldingPermissions(String[] permissions, int flags) { return this.mHostPackageManager.getPackagesHoldingPermissions(permissions, flags); } @@ -640,6 +678,12 @@ public int checkPermission(String permName, String pkgName) { return this.mHostPackageManager.checkPermission(permName, pkgName); } + @TargetApi(Build.VERSION_CODES.M) + @Override + public boolean isPermissionRevokedByPolicy(@NonNull String permName, @NonNull String pkgName) { + return this.mHostPackageManager.isPermissionRevokedByPolicy(permName, pkgName); + } + @Override public boolean addPermission(PermissionInfo info) { return this.mHostPackageManager.addPermission(info); @@ -679,12 +723,63 @@ public String getNameForUid(int uid) { public List getInstalledApplications(int flags) { return this.mHostPackageManager.getInstalledApplications(flags); } - + + @TargetApi(Build.VERSION_CODES.O) + @Override + public boolean isInstantApp() { + return this.mHostPackageManager.isInstantApp(); + } + + @TargetApi(Build.VERSION_CODES.O) + @Override + public boolean isInstantApp(String packageName) { + return this.mHostPackageManager.isInstantApp(packageName); + } + + @TargetApi(Build.VERSION_CODES.O) + @Override + public int getInstantAppCookieMaxBytes() { + return this.mHostPackageManager.getInstantAppCookieMaxBytes(); + } + + @TargetApi(Build.VERSION_CODES.O) + @NonNull + @Override + public byte[] getInstantAppCookie() { + return this.mHostPackageManager.getInstantAppCookie(); + } + + @TargetApi(Build.VERSION_CODES.O) + @Override + public void clearInstantAppCookie() { + this.mHostPackageManager.clearInstantAppCookie(); + } + + @TargetApi(Build.VERSION_CODES.O) + @Override + public void updateInstantAppCookie(@Nullable byte[] cookie) { + this.mHostPackageManager.updateInstantAppCookie(cookie); + } + @Override public String[] getSystemSharedLibraryNames() { return this.mHostPackageManager.getSystemSharedLibraryNames(); } - + + @TargetApi(Build.VERSION_CODES.O) + @NonNull + @Override + public List getSharedLibraries(int flags) { + return this.mHostPackageManager.getSharedLibraries(flags); + } + + @TargetApi(Build.VERSION_CODES.O) + @Nullable + @Override + public ChangedPackages getChangedPackages(int sequenceNumber) { + return this.mHostPackageManager.getChangedPackages(sequenceNumber); + } + @Override public FeatureInfo[] getSystemAvailableFeatures() { return this.mHostPackageManager.getSystemAvailableFeatures(); @@ -695,6 +790,12 @@ public boolean hasSystemFeature(String name) { return this.mHostPackageManager.hasSystemFeature(name); } + @TargetApi(Build.VERSION_CODES.N) + @Override + public boolean hasSystemFeature(String name, int version) { + return this.mHostPackageManager.hasSystemFeature(name, version); + } + @Override public ResolveInfo resolveActivity(Intent intent, int flags) { ResolveInfo resolveInfo = mPluginManager.resolveActivity(intent, flags); @@ -769,7 +870,7 @@ public List queryBroadcastReceivers(Intent intent, int flags) { } } - List all = new ArrayList(); + List all = new ArrayList<>(); List pluginResolveInfos = mPluginManager.queryBroadcastReceivers(intent, flags); if (null != pluginResolveInfos && pluginResolveInfos.size() > 0) { @@ -898,8 +999,8 @@ public Drawable getActivityIcon(Intent intent) throws NameNotFoundException { return this.mHostPackageManager.getActivityIcon(intent); } - @Override @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) + @Override public Drawable getActivityBanner(ComponentName component) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { @@ -909,8 +1010,8 @@ public Drawable getActivityBanner(ComponentName component) throws NameNotFoundEx return this.mHostPackageManager.getActivityBanner(component); } - @Override @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) + @Override public Drawable getActivityBanner(Intent intent) throws NameNotFoundException { ResolveInfo ri = mPluginManager.resolveActivity(intent); if (null != ri) { @@ -946,8 +1047,8 @@ public Drawable getApplicationIcon(String packageName) throws NameNotFoundExcept return this.mHostPackageManager.getApplicationIcon(packageName); } - @Override @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) + @Override public Drawable getApplicationBanner(ApplicationInfo info) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(info.packageName); if (null != plugin) { @@ -957,8 +1058,8 @@ public Drawable getApplicationBanner(ApplicationInfo info) { return this.mHostPackageManager.getApplicationBanner(info); } - @Override @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) + @Override public Drawable getApplicationBanner(String packageName) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { @@ -1009,29 +1110,31 @@ public Drawable getApplicationLogo(String packageName) throws NameNotFoundExcept return this.mHostPackageManager.getApplicationLogo(packageName); } - @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) { return this.mHostPackageManager.getUserBadgedIcon(icon, user); } + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public Drawable getUserBadgeForDensity(UserHandle user, int density) { try { - Method method = PackageManager.class.getMethod("getUserBadgeForDensity", UserHandle.class, int.class); - return (Drawable) method.invoke(this.mHostPackageManager, user, density); + return Reflector.with(this.mHostPackageManager) + .method("getUserBadgeForDensity", UserHandle.class, int.class) + .call(user, density); } catch (Exception e) { throw new RuntimeException(e); } } - @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user, Rect badgeLocation, int badgeDensity) { return this.mHostPackageManager.getUserBadgedDrawableForDensity(drawable, user, badgeLocation, badgeDensity); } - @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) { return this.mHostPackageManager.getUserBadgedLabel(label, user); } @@ -1105,8 +1208,8 @@ public void verifyPendingInstall(int id, int verificationCode) { this.mHostPackageManager.verifyPendingInstall(id, verificationCode); } - @Override @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + @Override public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay) { this.mHostPackageManager.extendVerificationTimeout(id, verificationCodeAtTimeout, millisecondsToDelay); } @@ -1157,7 +1260,7 @@ public void clearPackagePreferredActivities(String packageName) { } @Override - public int getPreferredActivities(List outFilters, List outActivities, String packageName) { + public int getPreferredActivities(@NonNull List outFilters, @NonNull List outActivities, String packageName) { return this.mHostPackageManager.getPreferredActivities(outFilters, outActivities, packageName); } @@ -1185,36 +1288,30 @@ public int getApplicationEnabledSetting(String packageName) { public boolean isSafeMode() { return this.mHostPackageManager.isSafeMode(); } - + + @TargetApi(Build.VERSION_CODES.O) + @Override + public void setApplicationCategoryHint(@NonNull String packageName, int categoryHint) { + this.mHostPackageManager.setApplicationCategoryHint(packageName, categoryHint); + } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override - public PackageInstaller getPackageInstaller() { + public @NonNull PackageInstaller getPackageInstaller() { return this.mHostPackageManager.getPackageInstaller(); } - - @TargetApi(24) - public int[] getPackageGids(String s, int i) throws NameNotFoundException { - return mHostPackageManager.getPackageGids(s); + + @TargetApi(Build.VERSION_CODES.O) + @Override + public boolean canRequestPackageInstalls() { + return this.mHostPackageManager.canRequestPackageInstalls(); } - - public int getPackageUid(String s, int i) throws NameNotFoundException { - Object uid = ReflectUtil.invokeNoException(PackageManager.class, mHostPackageManager, "getPackageUid", - new Class[]{String.class, int.class}, s, i); - if (uid != null) { - return (int) uid; - } else { - throw new NameNotFoundException(s); + + public Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) { + if (itemInfo == null) { + return null; } - } - - @TargetApi(23) - public boolean isPermissionRevokedByPolicy(String s, String s1) { - return false; - } - - @TargetApi(24) - public boolean hasSystemFeature(String s, int i) { - return mHostPackageManager.hasSystemFeature(s); + return itemInfo.loadIcon(this.mHostPackageManager); } } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContentResolver.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContentResolver.java index 6f5248c..5b7eff8 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContentResolver.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContentResolver.java @@ -16,108 +16,76 @@ package com.didi.virtualapk.internal; -import android.content.ContentResolver; +import android.annotation.TargetApi; +import android.content.ContentResolverWrapper; import android.content.Context; import android.content.IContentProvider; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.support.annotation.Keep; import com.didi.virtualapk.PluginManager; import com.didi.virtualapk.delegate.RemoteContentProvider; -import java.lang.reflect.Method; - /** * Created by renyugang on 16/12/7. */ -public class PluginContentResolver extends ContentResolver { - private ContentResolver mBase; +public class PluginContentResolver extends ContentResolverWrapper { private PluginManager mPluginManager; - private static Method sAcquireProvider; - private static Method sAcquireExistingProvider; - private static Method sAcquireUnstableProvider; - - static { - try { - sAcquireProvider = ContentResolver.class.getDeclaredMethod("acquireProvider", - new Class[]{Context.class, String.class}); - sAcquireProvider.setAccessible(true); - sAcquireExistingProvider = ContentResolver.class.getDeclaredMethod("acquireExistingProvider", - new Class[]{Context.class, String.class}); - sAcquireExistingProvider.setAccessible(true); - sAcquireUnstableProvider = ContentResolver.class.getDeclaredMethod("acquireUnstableProvider", - new Class[]{Context.class, String.class}); - sAcquireUnstableProvider.setAccessible(true); - } catch (Exception e) { - //ignored - } - } public PluginContentResolver(Context context) { super(context); - mBase = context.getContentResolver(); mPluginManager = PluginManager.getInstance(context); } - + + @Override protected IContentProvider acquireProvider(Context context, String auth) { - try { - if (mPluginManager.resolveContentProvider(auth, 0) != null) { - return mPluginManager.getIContentProvider(); - } - - return (IContentProvider) sAcquireProvider.invoke(mBase, context, auth); - } catch (Exception e) { - e.printStackTrace(); + if (mPluginManager.resolveContentProvider(auth, 0) != null) { + return mPluginManager.getIContentProvider(); } - - return null; + return super.acquireProvider(context, auth); } + @Override protected IContentProvider acquireExistingProvider(Context context, String auth) { - try { - if (mPluginManager.resolveContentProvider(auth, 0) != null) { - return mPluginManager.getIContentProvider(); - } - - return (IContentProvider) sAcquireExistingProvider.invoke(mBase, context, auth); - } catch (Exception e) { - e.printStackTrace(); + if (mPluginManager.resolveContentProvider(auth, 0) != null) { + return mPluginManager.getIContentProvider(); } - - return null; + return super.acquireExistingProvider(context, auth); } - + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Override protected IContentProvider acquireUnstableProvider(Context context, String auth) { - try { - if (mPluginManager.resolveContentProvider(auth, 0) != null) { - return mPluginManager.getIContentProvider(); - } - - return (IContentProvider) sAcquireUnstableProvider.invoke(mBase, context, auth); - } catch (Exception e) { - e.printStackTrace(); + if (mPluginManager.resolveContentProvider(auth, 0) != null) { + return mPluginManager.getIContentProvider(); } - - return null; + return super.acquireUnstableProvider(context, auth); } + @Override public boolean releaseProvider(IContentProvider provider) { return true; } - + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Override public boolean releaseUnstableProvider(IContentProvider icp) { return true; } - + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Override public void unstableProviderDied(IContentProvider icp) { } + @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) + @Override public void appNotRespondingViaProvider(IContentProvider icp) { } - /** @hide */ protected int resolveUserIdFromAuthority(String auth) { return 0; } @@ -126,7 +94,7 @@ protected int resolveUserIdFromAuthority(String auth) { public static Uri wrapperUri(LoadedPlugin loadedPlugin, Uri pluginUri) { String pkg = loadedPlugin.getPackageName(); String pluginUriString = Uri.encode(pluginUri.toString()); - StringBuilder builder = new StringBuilder(PluginContentResolver.getUri(loadedPlugin.getHostContext())); + StringBuilder builder = new StringBuilder(RemoteContentProvider.getUri(loadedPlugin.getHostContext())); builder.append("/?plugin=" + loadedPlugin.getLocation()); builder.append("&pkg=" + pkg); builder.append("&uri=" + pluginUriString); @@ -136,12 +104,12 @@ public static Uri wrapperUri(LoadedPlugin loadedPlugin, Uri pluginUri) { @Deprecated public static String getAuthority(Context context) { - return context.getPackageName() + ".VirtualAPK.Provider"; + return RemoteContentProvider.getAuthority(context); } @Deprecated public static String getUri(Context context) { - return "content://" + getAuthority(context); + return RemoteContentProvider.getUri(context); } @Keep diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContext.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContext.java index 337fb4c..462bd42 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContext.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContext.java @@ -36,6 +36,11 @@ public PluginContext(LoadedPlugin plugin) { super(plugin.getPluginManager().getHostContext()); this.mPlugin = plugin; } + + public PluginContext(LoadedPlugin plugin, Context base) { + super(base); + this.mPlugin = plugin; + } @Override public Context getApplicationContext() { diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ResourcesManager.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ResourcesManager.java index b54858a..a9f3212 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ResourcesManager.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ResourcesManager.java @@ -16,87 +16,205 @@ package com.didi.virtualapk.internal; +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.ActivityThread; +import android.app.LoadedApk; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.ResourcesImpl; +import android.content.res.ResourcesKey; import android.os.Build; +import android.util.ArrayMap; import android.util.DisplayMetrics; +import android.util.Log; import com.didi.virtualapk.PluginManager; -import com.didi.virtualapk.utils.ReflectUtil; +import com.didi.virtualapk.utils.Reflector; +import java.io.File; import java.lang.ref.WeakReference; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Created by renyugang on 16/8/9. */ class ResourcesManager { + + public static final String TAG = Constants.TAG_PREFIX + "LoadedPlugin"; - public static synchronized Resources createResources(Context hostContext, String apk) { + private static Configuration mDefaultConfiguration; + + public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return createResourcesForN(hostContext, packageName, apk); + } + + Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath()); + ResourcesManager.hookResources(hostContext, resources); + return resources; + } + + private static Resources createResourcesSimple(Context hostContext, String apk) throws Exception { Resources hostResources = hostContext.getResources(); Resources newResources = null; AssetManager assetManager; - try { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - assetManager = AssetManager.class.newInstance(); - ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", hostContext.getApplicationInfo().sourceDir); - } else { - assetManager = hostResources.getAssets(); - } - ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", apk); - List pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins(); - for (LoadedPlugin plugin : pluginList) { - ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", plugin.getLocation()); + Reflector reflector = Reflector.on(AssetManager.class).method("addAssetPath", String.class); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + assetManager = AssetManager.class.newInstance(); + reflector.bind(assetManager); + final int cookie1 = reflector.call(hostContext.getApplicationInfo().sourceDir);; + if (cookie1 == 0) { + throw new RuntimeException("createResources failed, can't addAssetPath for " + hostContext.getApplicationInfo().sourceDir); } - if (isMiUi(hostResources)) { - newResources = MiUiResourcesCompat.createResources(hostResources, assetManager); - } else if (isVivo(hostResources)) { - newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager); - } else if (isNubia(hostResources)) { - newResources = NubiaResourcesCompat.createResources(hostResources, assetManager); - } else if (isNotRawResources(hostResources)) { - newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager); - } else { - // is raw android resources - newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); - } - // lastly, sync all LoadedPlugin to newResources - for (LoadedPlugin plugin : pluginList) { - plugin.updateResources(newResources); + } else { + assetManager = hostResources.getAssets(); + reflector.bind(assetManager); + } + final int cookie2 = reflector.call(apk); + if (cookie2 == 0) { + throw new RuntimeException("createResources failed, can't addAssetPath for " + apk); + } + List pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins(); + for (LoadedPlugin plugin : pluginList) { + final int cookie3 = reflector.call(plugin.getLocation()); + if (cookie3 == 0) { + throw new RuntimeException("createResources failed, can't addAssetPath for " + plugin.getLocation()); } - } catch (Exception e) { - e.printStackTrace(); } - + if (isMiUi(hostResources)) { + newResources = MiUiResourcesCompat.createResources(hostResources, assetManager); + } else if (isVivo(hostResources)) { + newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager); + } else if (isNubia(hostResources)) { + newResources = NubiaResourcesCompat.createResources(hostResources, assetManager); + } else if (isNotRawResources(hostResources)) { + newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager); + } else { + // is raw android resources + newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); + } + // lastly, sync all LoadedPlugin to newResources + for (LoadedPlugin plugin : pluginList) { + plugin.updateResources(newResources); + } + return newResources; - } public static void hookResources(Context base, Resources resources) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return; + } try { - ReflectUtil.setField(base.getClass(), base, "mResources", resources); - Object loadedApk = ReflectUtil.getPackageInfo(base); - ReflectUtil.setField(loadedApk.getClass(), loadedApk, "mResources", resources); - - Object activityThread = ReflectUtil.getActivityThread(base); - Object resManager = ReflectUtil.getField(activityThread.getClass(), activityThread, "mResourcesManager"); - if (Build.VERSION.SDK_INT < 24) { - Map> map = (Map>) ReflectUtil.getField(resManager.getClass(), resManager, "mActiveResources"); - Object key = map.keySet().iterator().next(); - map.put(key, new WeakReference<>(resources)); + Reflector reflector = Reflector.with(base); + reflector.field("mResources").set(resources); + Object loadedApk = reflector.field("mPackageInfo").get(); + Reflector.with(loadedApk).field("mResources").set(resources); + + Object activityThread = ActivityThread.currentActivityThread(); + Object resManager; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + resManager = android.app.ResourcesManager.getInstance(); } else { - // still hook Android N Resources, even though it's unnecessary, then nobody will be strange. - Map map = (Map) ReflectUtil.getFieldNoException(resManager.getClass(), resManager, "mResourceImpls"); - Object key = map.keySet().iterator().next(); - Object resourcesImpl = ReflectUtil.getFieldNoException(Resources.class, resources, "mResourcesImpl"); - map.put(key, new WeakReference<>(resourcesImpl)); + resManager = Reflector.with(activityThread).field("mResourcesManager").get(); } + Map> map = Reflector.with(resManager).field("mActiveResources").get(); + Object key = map.keySet().iterator().next(); + map.put(key, new WeakReference<>(resources)); } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); + } + } + + /** + * Use System Apis to update all existing resources. + *
+ * 1. Update ApplicationInfo.splitSourceDirs and LoadedApk.mSplitResDirs + *
+ * 2. Replace all keys of ResourcesManager.mResourceImpls to new ResourcesKey + *
+ * 3. Use ResourcesManager.appendLibAssetForMainAssetPath(appInfo.publicSourceDir, "${packageName}.vastub") to update all existing resources. + *
+ * + * see android.webkit.WebViewDelegate.addWebViewAssetPath(Context) + */ + @TargetApi(Build.VERSION_CODES.N) + private static Resources createResourcesForN(Context context, String packageName, File apk) throws Exception { + long startTime = System.currentTimeMillis(); + String newAssetPath = apk.getAbsolutePath(); + ApplicationInfo info = context.getApplicationInfo(); + String baseResDir = info.publicSourceDir; + + info.splitSourceDirs = append(info.splitSourceDirs, newAssetPath); + LoadedApk loadedApk = Reflector.with(context).field("mPackageInfo").get(); + + Reflector rLoadedApk = Reflector.with(loadedApk).field("mSplitResDirs"); + String[] splitResDirs = rLoadedApk.get(); + rLoadedApk.set(append(splitResDirs, newAssetPath)); + + final android.app.ResourcesManager resourcesManager = android.app.ResourcesManager.getInstance(); + ArrayMap> originalMap = Reflector.with(resourcesManager).field("mResourceImpls").get(); + + synchronized (resourcesManager) { + HashMap> resolvedMap = new HashMap<>(); + + if (Build.VERSION.SDK_INT >= 28 + || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // P Preview + ResourcesManagerCompatForP.resolveResourcesImplMap(originalMap, resolvedMap, context, loadedApk); + + } else { + ResourcesManagerCompatForN.resolveResourcesImplMap(originalMap, resolvedMap, baseResDir, newAssetPath); + } + + originalMap.clear(); + originalMap.putAll(resolvedMap); + } + + android.app.ResourcesManager.getInstance().appendLibAssetForMainAssetPath(baseResDir, packageName + ".vastub"); + + Resources newResources = context.getResources(); + + // lastly, sync all LoadedPlugin to newResources + for (LoadedPlugin plugin : PluginManager.getInstance(context).getAllLoadedPlugins()) { + plugin.updateResources(newResources); + } + + Log.d(TAG, "createResourcesForN cost time: +" + (System.currentTimeMillis() - startTime) + "ms"); + return newResources; + } + + private static String[] append(String[] paths, String newPath) { + if (contains(paths, newPath)) { + return paths; + } + + final int newPathsCount = 1 + (paths != null ? paths.length : 0); + final String[] newPaths = new String[newPathsCount]; + if (paths != null) { + System.arraycopy(paths, 0, newPaths, 0, paths.length); + } + newPaths[newPathsCount - 1] = newPath; + return newPaths; + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private static boolean contains(String[] array, String value) { + if (array == null) { + return false; + } + for (int i = 0; i < array.length; i++) { + if (Objects.equals(array[i], value)) { + return true; + } } + return false; } private static boolean isMiUi(Resources resources) { @@ -117,34 +235,30 @@ private static boolean isNotRawResources(Resources resources) { private static final class MiUiResourcesCompat { private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception { - Class resourcesClazz = Class.forName("android.content.res.MiuiResources"); - Resources newResources = (Resources) ReflectUtil.invokeConstructor(resourcesClazz, - new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class}, - new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()}); + Reflector reflector = Reflector.on("android.content.res.MiuiResources"); + Resources newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class) + .newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); return newResources; } } private static final class VivoResourcesCompat { private static Resources createResources(Context hostContext, Resources hostResources, AssetManager assetManager) throws Exception { - Class resourcesClazz = Class.forName("android.content.res.VivoResources"); - Resources newResources = (Resources) ReflectUtil.invokeConstructor(resourcesClazz, - new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class}, - new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()}); - ReflectUtil.invokeNoException(resourcesClazz, newResources, "init", - new Class[]{String.class}, hostContext.getPackageName()); - Object themeValues = ReflectUtil.getFieldNoException(resourcesClazz, hostResources, "mThemeValues"); - ReflectUtil.setFieldNoException(resourcesClazz, newResources, "mThemeValues", themeValues); + Reflector reflector = Reflector.on("android.content.res.VivoResources"); + Resources newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class) + .newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); + reflector.method("init", String.class).callByCaller(newResources, hostContext.getPackageName()); + reflector.field("mThemeValues"); + reflector.set(newResources, reflector.get(hostResources)); return newResources; } } private static final class NubiaResourcesCompat { private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception { - Class resourcesClazz = Class.forName("android.content.res.NubiaResources"); - Resources newResources = (Resources) ReflectUtil.invokeConstructor(resourcesClazz, - new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class}, - new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()}); + Reflector reflector = Reflector.on("android.content.res.NubiaResources"); + Resources newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class) + .newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); return newResources; } } @@ -153,10 +267,9 @@ private static final class AdaptationResourcesCompat { private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception { Resources newResources; try { - Class resourcesClazz = hostResources.getClass(); - newResources = (Resources) ReflectUtil.invokeConstructor(resourcesClazz, - new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class}, - new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()}); + Reflector reflector = Reflector.with(hostResources); + newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class) + .newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); } catch (Exception e) { newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); } @@ -165,4 +278,70 @@ private static Resources createResources(Resources hostResources, AssetManager a } } + private static final class ResourcesManagerCompatForN { + + @TargetApi(Build.VERSION_CODES.KITKAT) + public static void resolveResourcesImplMap(Map> originalMap, Map> resolvedMap, String baseResDir, String newAssetPath) throws Exception { + for (Map.Entry> entry : originalMap.entrySet()) { + ResourcesKey key = entry.getKey(); + if (Objects.equals(key.mResDir, baseResDir)) { + resolvedMap.put(new ResourcesKey(key.mResDir, + append(key.mSplitResDirs, newAssetPath), + key.mOverlayDirs, + key.mLibDirs, + key.mDisplayId, + key.mOverrideConfiguration, + key.mCompatInfo), entry.getValue()); + } else { + resolvedMap.put(key, entry.getValue()); + } + } + } + } + + private static final class ResourcesManagerCompatForP { + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public static void resolveResourcesImplMap(Map> originalMap, Map> resolvedMap, Context context, LoadedApk loadedApk) throws Exception { + HashMap newResImplMap = new HashMap<>(); + Map resKeyMap = new HashMap<>(); + Resources newRes; + + // Recreate the resImpl of the context + + // See LoadedApk.getResources() + if (mDefaultConfiguration == null) { + mDefaultConfiguration = new Configuration(); + } + newRes = context.createConfigurationContext(mDefaultConfiguration).getResources(); + newResImplMap.put(newRes.getImpl(), context); + + // Recreate the ResImpl of the activity + for (WeakReference ref : PluginManager.getInstance(context).getInstrumentation().getActivities()) { + Activity activity = ref.get(); + if (activity != null) { + newRes = activity.createConfigurationContext(activity.getResources().getConfiguration()).getResources(); + newResImplMap.put(newRes.getImpl(), activity); + } + } + + // Mapping all resKey and resImpl + for (Map.Entry> entry : originalMap.entrySet()) { + ResourcesImpl resImpl = entry.getValue().get(); + if (resImpl != null) { + resKeyMap.put(resImpl, entry.getKey()); + } + resolvedMap.put(entry.getKey(), entry.getValue()); + } + + // Replace the resImpl to the new resKey and remove the origin resKey + for (Map.Entry entry : newResImplMap.entrySet()) { + ResourcesKey newKey = resKeyMap.get(entry.getKey()); + ResourcesImpl originResImpl = entry.getValue().getResources().getImpl(); + + resolvedMap.put(newKey, new WeakReference<>(originResImpl)); + resolvedMap.remove(resKeyMap.get(originResImpl)); + } + } + } } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/StubActivityInfo.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/StubActivityInfo.java index cb743a5..a7eaaf2 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/StubActivityInfo.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/StubActivityInfo.java @@ -58,7 +58,7 @@ public String getStubActivity(String className, int launchMode, Theme theme) { boolean windowIsTranslucent = array.getBoolean(0, false); array.recycle(); if (Constants.DEBUG) { - Log.d("StubActivityInfo", "getStubActivity, is transparent theme ? " + windowIsTranslucent); + Log.d(Constants.TAG_PREFIX + "StubActivityInfo", "getStubActivity, is transparent theme ? " + windowIsTranslucent); } stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity); switch (launchMode) { diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/VAInstrumentation.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/VAInstrumentation.java index 8dc6892..1ec0802 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/VAInstrumentation.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/VAInstrumentation.java @@ -16,133 +16,180 @@ package com.didi.virtualapk.internal; +import android.annotation.TargetApi; import android.app.Activity; +import android.app.Application; +import android.app.Fragment; import android.app.Instrumentation; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; -import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.PersistableBundle; import android.util.Log; -import android.view.ContextThemeWrapper; import com.didi.virtualapk.PluginManager; -import com.didi.virtualapk.utils.PluginUtil; -import com.didi.virtualapk.utils.ReflectUtil; +import com.didi.virtualapk.delegate.StubActivity; +import com.didi.virtualapk.internal.utils.PluginUtil; +import com.didi.virtualapk.utils.Reflector; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; /** * Created by renyugang on 16/8/10. */ public class VAInstrumentation extends Instrumentation implements Handler.Callback { - public static final String TAG = "VAInstrumentation"; + public static final String TAG = Constants.TAG_PREFIX + "VAInstrumentation"; public static final int LAUNCH_ACTIVITY = 100; - private Instrumentation mBase; + protected Instrumentation mBase; + + protected final ArrayList> mActivities = new ArrayList<>(); - PluginManager mPluginManager; + protected PluginManager mPluginManager; public VAInstrumentation(PluginManager pluginManager, Instrumentation base) { this.mPluginManager = pluginManager; this.mBase = base; } - public ActivityResult execStartActivity( - Context who, IBinder contextThread, IBinder token, Activity target, - Intent intent, int requestCode, Bundle options) { + @Override + public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) { + injectIntent(intent); + return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode); + } + + @Override + public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { + injectIntent(intent); + return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options); + } + + @Override + public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options) { + injectIntent(intent); + return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options); + } + + @Override + public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) { + injectIntent(intent); + return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options); + } + + protected void injectIntent(Intent intent) { mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent); // null component is an implicitly intent if (intent.getComponent() != null) { - Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(), - intent.getComponent().getClassName())); + Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(), intent.getComponent().getClassName())); // resolve intent with Stub Activity if needed this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent); } - - ActivityResult result = realExecStartActivity(who, contextThread, token, target, - intent, requestCode, options); - - return result; - - } - - private ActivityResult realExecStartActivity( - Context who, IBinder contextThread, IBinder token, Activity target, - Intent intent, int requestCode, Bundle options) { - ActivityResult result = null; - try { - Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, - int.class, Bundle.class}; - result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase, - "execStartActivity", parameterTypes, - who, contextThread, token, target, intent, requestCode, options); - } catch (Exception e) { - if (e.getCause() instanceof ActivityNotFoundException) { - throw (ActivityNotFoundException) e.getCause(); - } - e.printStackTrace(); - } - - return result; } @Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { try { cl.loadClass(className); + Log.i(TAG, String.format("newActivity[%s]", className)); + } catch (ClassNotFoundException e) { - LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent); - String targetClassName = PluginUtil.getTargetActivity(intent); - - Log.i(TAG, String.format("newActivity[%s : %s]", className, targetClassName)); - - if (targetClassName != null) { - Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); - activity.setIntent(intent); - + ComponentName component = PluginUtil.getComponent(intent); + + if (component == null) { + return newActivity(mBase.newActivity(cl, className, intent)); + } + + String targetClassName = component.getClassName(); + Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName)); + + LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component); + + if (plugin == null) { + // Not found then goto stub activity. + boolean debuggable = false; try { - // for 4.1+ - ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources()); - } catch (Exception ignored) { - // ignored. + Context context = this.mPluginManager.getHostContext(); + debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } catch (Throwable ex) { + } - - return activity; + + if (debuggable) { + throw new ActivityNotFoundException("error intent: " + intent.toURI()); + } + + Log.i(TAG, "Not found. starting the stub activity: " + StubActivity.class); + return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent)); } + + Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); + activity.setIntent(intent); + + // for 4.1+ + Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources()); + + return newActivity(activity); } - return mBase.newActivity(cl, className, intent); + return newActivity(mBase.newActivity(cl, className, intent)); + } + + @Override + public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return mBase.newApplication(cl, className, context); } @Override public void callActivityOnCreate(Activity activity, Bundle icicle) { + injectActivity(activity); + mBase.callActivityOnCreate(activity, icicle); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) { + injectActivity(activity); + mBase.callActivityOnCreate(activity, icicle, persistentState); + } + + protected void injectActivity(Activity activity) { final Intent intent = activity.getIntent(); if (PluginUtil.isIntentFromPlugin(intent)) { Context base = activity.getBaseContext(); try { LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent); - ReflectUtil.setField(base.getClass(), base, "mResources", plugin.getResources()); - ReflectUtil.setField(ContextWrapper.class, activity, "mBase", plugin.getPluginContext()); - ReflectUtil.setField(Activity.class, activity, "mApplication", plugin.getApplication()); - ReflectUtil.setFieldNoException(ContextThemeWrapper.class, activity, "mBase", plugin.getPluginContext()); + Reflector.with(base).field("mResources").set(plugin.getResources()); + Reflector reflector = Reflector.with(activity); + reflector.field("mBase").set(plugin.createPluginContext(activity.getBaseContext())); + reflector.field("mApplication").set(plugin.getApplication()); // set screenOrientation ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent)); if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { activity.setRequestedOrientation(activityInfo.screenOrientation); } + + // for native activity + ComponentName component = PluginUtil.getComponent(intent); + Intent wrapperIntent = new Intent(intent); + wrapperIntent.setClassName(component.getPackageName(), component.getClassName()); + activity.setIntent(wrapperIntent); + } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); } - } - - mBase.callActivityOnCreate(activity, icicle); } @Override @@ -151,9 +198,10 @@ public boolean handleMessage(Message msg) { // ActivityClientRecord r Object r = msg.obj; try { - Intent intent = (Intent) ReflectUtil.getField(r.getClass(), r, "intent"); - intent.setExtrasClassLoader(VAInstrumentation.class.getClassLoader()); - ActivityInfo activityInfo = (ActivityInfo) ReflectUtil.getField(r.getClass(), r, "activityInfo"); + Reflector reflector = Reflector.with(r); + Intent intent = reflector.field("intent").get(); + intent.setExtrasClassLoader(mPluginManager.getHostContext().getClassLoader()); + ActivityInfo activityInfo = reflector.field("activityInfo").get(); if (PluginUtil.isIntentFromPlugin(intent)) { int theme = PluginUtil.getTheme(mPluginManager.getHostContext(), intent); @@ -163,7 +211,7 @@ public boolean handleMessage(Message msg) { } } } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); } } @@ -185,4 +233,21 @@ public ComponentName getComponentName() { return mBase.getComponentName(); } + protected Activity newActivity(Activity activity) { + synchronized (mActivities) { + for (int i = mActivities.size() - 1; i >= 0; i--) { + if (mActivities.get(i).get() == null) { + mActivities.remove(i); + } + } + mActivities.add(new WeakReference<>(activity)); + } + return activity; + } + + List> getActivities() { + synchronized (mActivities) { + return new ArrayList<>(mActivities); + } + } } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/DexUtil.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/DexUtil.java similarity index 55% rename from CoreLibrary/src/main/java/com/didi/virtualapk/utils/DexUtil.java rename to CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/DexUtil.java index a9e8e31..1c736b1 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/DexUtil.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/DexUtil.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.didi.virtualapk.utils; +package com.didi.virtualapk.internal.utils; +import android.app.ActivityThread; import android.content.Context; import android.os.Build; -import com.didi.virtualapk.Systems; import com.didi.virtualapk.internal.Constants; +import com.didi.virtualapk.utils.Reflector; import java.io.File; import java.lang.reflect.Array; @@ -28,67 +29,56 @@ import java.util.List; import dalvik.system.DexClassLoader; -import dalvik.system.PathClassLoader; public class DexUtil { private static boolean sHasInsertedNativeLibrary = false; - public static void insertDex(DexClassLoader dexClassLoader) throws Exception { - Object baseDexElements = getDexElements(getPathList(getPathClassLoader())); + public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception { + Object baseDexElements = getDexElements(getPathList(baseClassLoader)); Object newDexElements = getDexElements(getPathList(dexClassLoader)); Object allDexElements = combineArray(baseDexElements, newDexElements); - Object pathList = getPathList(getPathClassLoader()); - ReflectUtil.setField(pathList.getClass(), pathList, "dexElements", allDexElements); + Object pathList = getPathList(baseClassLoader); + Reflector.with(pathList).field("dexElements").set(allDexElements); - insertNativeLibrary(dexClassLoader); - - } - - private static PathClassLoader getPathClassLoader() { - PathClassLoader pathClassLoader = (PathClassLoader) DexUtil.class.getClassLoader(); - return pathClassLoader; + insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir); } private static Object getDexElements(Object pathList) throws Exception { - return ReflectUtil.getField(pathList.getClass(), pathList, "dexElements"); + return Reflector.with(pathList).field("dexElements").get(); } - private static Object getPathList(Object baseDexClassLoader) throws Exception { - return ReflectUtil.getField(Class.forName("dalvik.system.BaseDexClassLoader"), baseDexClassLoader, "pathList"); + private static Object getPathList(ClassLoader baseDexClassLoader) throws Exception { + return Reflector.with(baseDexClassLoader).field("pathList").get(); } private static Object combineArray(Object firstArray, Object secondArray) { Class localClass = firstArray.getClass().getComponentType(); int firstArrayLength = Array.getLength(firstArray); - int allLength = firstArrayLength + Array.getLength(secondArray); - Object result = Array.newInstance(localClass, allLength); - for (int k = 0; k < allLength; ++k) { - if (k < firstArrayLength) { - Array.set(result, k, Array.get(firstArray, k)); - } else { - Array.set(result, k, Array.get(secondArray, k - firstArrayLength)); - } - } + int secondArrayLength = Array.getLength(secondArray); + Object result = Array.newInstance(localClass, firstArrayLength + secondArrayLength); + System.arraycopy(firstArray, 0, result, 0, firstArrayLength); + System.arraycopy(secondArray, 0, result, firstArrayLength, secondArrayLength); return result; } - private static synchronized void insertNativeLibrary(DexClassLoader dexClassLoader) throws Exception { + private static synchronized void insertNativeLibrary(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception { if (sHasInsertedNativeLibrary) { return; } sHasInsertedNativeLibrary = true; - Object basePathList = getPathList(getPathClassLoader()); + Context context = ActivityThread.currentApplication(); + Object basePathList = getPathList(baseClassLoader); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { - List nativeLibraryDirectories = (List) ReflectUtil.getField(basePathList.getClass(), - basePathList, "nativeLibraryDirectories"); - nativeLibraryDirectories.add(Systems.getContext().getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE)); + Reflector reflector = Reflector.with(basePathList); + List nativeLibraryDirectories = reflector.field("nativeLibraryDirectories").get(); + nativeLibraryDirectories.add(nativeLibsDir); - Object baseNativeLibraryPathElements = ReflectUtil.getField(basePathList.getClass(), basePathList, "nativeLibraryPathElements"); + Object baseNativeLibraryPathElements = reflector.field("nativeLibraryPathElements").get(); final int baseArrayLength = Array.getLength(baseNativeLibraryPathElements); Object newPathList = getPathList(dexClassLoader); - Object newNativeLibraryPathElements = ReflectUtil.getField(newPathList.getClass(), newPathList, "nativeLibraryPathElements"); + Object newNativeLibraryPathElements = reflector.get(newPathList); Class elementClass = newNativeLibraryPathElements.getClass().getComponentType(); Object allNativeLibraryPathElements = Array.newInstance(elementClass, baseArrayLength + 1); System.arraycopy(baseNativeLibraryPathElements, 0, allNativeLibraryPathElements, 0, baseArrayLength); @@ -110,15 +100,15 @@ private static synchronized void insertNativeLibrary(DexClassLoader dexClassLoad } } - ReflectUtil.setField(basePathList.getClass(), basePathList, "nativeLibraryPathElements", allNativeLibraryPathElements); + reflector.set(allNativeLibraryPathElements); } else { - File[] nativeLibraryDirectories = (File[]) ReflectUtil.getFieldNoException(basePathList.getClass(), - basePathList, "nativeLibraryDirectories"); + Reflector reflector = Reflector.with(basePathList).field("nativeLibraryDirectories"); + File[] nativeLibraryDirectories = reflector.get(); final int N = nativeLibraryDirectories.length; File[] newNativeLibraryDirectories = new File[N + 1]; System.arraycopy(nativeLibraryDirectories, 0, newNativeLibraryDirectories, 0, N); - newNativeLibraryDirectories[N] = Systems.getContext().getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE); - ReflectUtil.setField(basePathList.getClass(), basePathList, "nativeLibraryDirectories", newNativeLibraryDirectories); + newNativeLibraryDirectories[N] = nativeLibsDir; + reflector.set(newNativeLibraryDirectories); } } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/PackageParserCompat.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/PackageParserCompat.java similarity index 50% rename from CoreLibrary/src/main/java/com/didi/virtualapk/utils/PackageParserCompat.java rename to CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/PackageParserCompat.java index 0ffa847..b9cabbf 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/PackageParserCompat.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/PackageParserCompat.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package com.didi.virtualapk.utils; +package com.didi.virtualapk.internal.utils; import android.content.Context; import android.content.pm.PackageParser; import android.os.Build; +import com.didi.virtualapk.utils.Reflector; + import java.io.File; /** @@ -27,37 +29,54 @@ */ public final class PackageParserCompat { - public static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) throws PackageParser.PackageParserException { - if (Build.VERSION.SDK_INT >= 24) { - return PackageParserV24.parsePackage(context, apk, flags); - } else if (Build.VERSION.SDK_INT >= 21) { - return PackageParserLollipop.parsePackage(context, apk, flags); - } else { - return PackageParserLegacy.parsePackage(context, apk, flags); + public static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) { + try { + if (Build.VERSION.SDK_INT >= 28 + || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview + return PackageParserPPreview.parsePackage(context, apk, flags); + } else if (Build.VERSION.SDK_INT >= 24) { + return PackageParserV24.parsePackage(context, apk, flags); + } else if (Build.VERSION.SDK_INT >= 21) { + return PackageParserLollipop.parsePackage(context, apk, flags); + } else { + return PackageParserLegacy.parsePackage(context, apk, flags); + } + + } catch (Throwable e) { + throw new RuntimeException("error", e); + } + } + + private static final class PackageParserPPreview { + + static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws Throwable { + PackageParser parser = new PackageParser(); + PackageParser.Package pkg = parser.parsePackage(apk, flags); + Reflector.with(parser) + .method("collectCertificates", PackageParser.Package.class, boolean.class) + .call(pkg, false); + return pkg; } } private static final class PackageParserV24 { - static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws PackageParser.PackageParserException { + static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws Throwable { PackageParser parser = new PackageParser(); PackageParser.Package pkg = parser.parsePackage(apk, flags); - ReflectUtil.invokeNoException(PackageParser.class, null, "collectCertificates", - new Class[]{PackageParser.Package.class, int.class}, pkg, flags); + Reflector.with(parser) + .method("collectCertificates", PackageParser.Package.class, int.class) + .call(pkg, flags); return pkg; } } private static final class PackageParserLollipop { - static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) throws PackageParser.PackageParserException { + static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) throws Throwable { PackageParser parser = new PackageParser(); PackageParser.Package pkg = parser.parsePackage(apk, flags); - try { - parser.collectCertificates(pkg, flags); - } catch (Throwable e) { - // ignored - } + parser.collectCertificates(pkg, flags); return pkg; } @@ -65,11 +84,12 @@ static final PackageParser.Package parsePackage(final Context context, final Fil private static final class PackageParserLegacy { - static final PackageParser.Package parsePackage(Context context, File apk, int flags) { + static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws Throwable { PackageParser parser = new PackageParser(apk.getAbsolutePath()); PackageParser.Package pkg = parser.parsePackage(apk, apk.getAbsolutePath(), context.getResources().getDisplayMetrics(), flags); - ReflectUtil.invokeNoException(PackageParser.class, parser, "collectCertificates", - new Class[]{PackageParser.Package.class, int.class}, pkg, flags); + Reflector.with(parser) + .method("collectCertificates", PackageParser.Package.class, int.class) + .call(pkg, flags); return pkg; } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/PluginUtil.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/PluginUtil.java similarity index 56% rename from CoreLibrary/src/main/java/com/didi/virtualapk/utils/PluginUtil.java rename to CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/PluginUtil.java index 8acea2e..a1eea83 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/PluginUtil.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/PluginUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.didi.virtualapk.utils; +package com.didi.virtualapk.internal.utils; import android.app.Activity; import android.content.ComponentName; @@ -23,20 +23,18 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; -import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.os.IBinder; -import android.support.annotation.Keep; import android.text.TextUtils; -import android.view.ContextThemeWrapper; +import android.util.Log; import com.didi.virtualapk.PluginManager; import com.didi.virtualapk.internal.Constants; import com.didi.virtualapk.internal.LoadedPlugin; +import com.didi.virtualapk.utils.Reflector; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -46,10 +44,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -57,17 +51,25 @@ * Created by renyugang on 16/8/15. */ public class PluginUtil { - - public static String getTargetActivity(Intent intent) { - return intent.getStringExtra(Constants.KEY_TARGET_ACTIVITY); - } - + + public static final String TAG = Constants.TAG_PREFIX + "NativeLib"; + public static ComponentName getComponent(Intent intent) { - return new ComponentName(intent.getStringExtra(Constants.KEY_TARGET_PACKAGE), + if (intent == null) { + return null; + } + if (isIntentFromPlugin(intent)) { + return new ComponentName(intent.getStringExtra(Constants.KEY_TARGET_PACKAGE), intent.getStringExtra(Constants.KEY_TARGET_ACTIVITY)); + } + + return intent.getComponent(); } public static boolean isIntentFromPlugin(Intent intent) { + if (intent == null) { + return false; + } return intent.getBooleanExtra(Constants.KEY_IS_PLUGIN, false); } @@ -96,7 +98,7 @@ public static int getTheme(Context context, ComponentName component) { return appInfo.theme; } - return PluginUtil.selectDefaultTheme(0, Build.VERSION.SDK_INT); + return selectDefaultTheme(0, Build.VERSION.SDK_INT); } public static int selectDefaultTheme(final int curTheme, final int targetSdkVersion) { @@ -107,7 +109,7 @@ public static int selectDefaultTheme(final int curTheme, final int targetSdkVers android.R.style.Theme_DeviceDefault_Light_DarkActionBar); } - private static int selectSystemTheme(final int curTheme, final int targetSdkVersion, final int orig, final int holo, final int dark, final int deviceDefault) { + public static int selectSystemTheme(final int curTheme, final int targetSdkVersion, final int orig, final int holo, final int dark, final int deviceDefault) { if (curTheme != 0) { return curTheme; } @@ -139,19 +141,20 @@ public static void hookActivityResources(Activity activity, String packageName) final LoadedPlugin plugin = PluginManager.getInstance(activity).getLoadedPlugin(packageName); final Resources resources = plugin.getResources(); if (resources != null) { - ReflectUtil.setField(base.getClass(), base, "mResources", resources); + Reflector.with(base).field("mResources").set(resources); // copy theme Resources.Theme theme = resources.newTheme(); theme.setTo(activity.getTheme()); - int themeResource = (int)ReflectUtil.getField(ContextThemeWrapper.class, activity, "mThemeResource"); + Reflector reflector = Reflector.with(activity); + int themeResource = reflector.field("mThemeResource").get(); theme.applyStyle(themeResource, true); - ReflectUtil.setField(ContextThemeWrapper.class, activity, "mTheme", theme); + reflector.field("mTheme").set(theme); - ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", resources); + reflector.field("mResources").set(resources); } } catch (Exception e) { - e.printStackTrace(); + Log.w(Constants.TAG, e); } } @@ -167,87 +170,113 @@ public static void putBinder(Bundle bundle, String key, IBinder value) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { bundle.putBinder(key, value); } else { - try { - ReflectUtil.invoke(Bundle.class, bundle, "putIBinder", new Class[]{String.class, IBinder.class}, key, value); - } catch (Exception e) { - } + Reflector.QuietReflector.with(bundle).method("putIBinder", String.class, IBinder.class).call(key, value); } } public static IBinder getBinder(Bundle bundle, String key) { + if (bundle == null) { + return null; + } if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { return bundle.getBinder(key); } else { - try { - return (IBinder) ReflectUtil.invoke(Bundle.class, bundle, "getIBinder", key); - } catch (Exception e) { - } - - return null; + return (IBinder) Reflector.QuietReflector.with(bundle) + .method("getIBinder", String.class).call(key); } } - - public static void copyNativeLib(File apk, Context context, PackageInfo packageInfo, File nativeLibDir) { + + public static void copyNativeLib(File apk, Context context, PackageInfo packageInfo, File nativeLibDir) throws Exception { + long startTime = System.currentTimeMillis(); + ZipFile zipfile = new ZipFile(apk.getAbsolutePath()); + try { - String cpuArch; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - cpuArch = Build.SUPPORTED_ABIS[0]; + for (String cpuArch : Build.SUPPORTED_ABIS) { + if (findAndCopyNativeLib(zipfile, context, cpuArch, packageInfo, nativeLibDir)) { + return; + } + } + } else { - cpuArch = Build.CPU_ABI; - } - boolean findSo = false; - - ZipFile zipfile = new ZipFile(apk.getAbsolutePath()); - ZipEntry entry; - Enumeration e = zipfile.entries(); - while (e.hasMoreElements()) { - entry = (ZipEntry) e.nextElement(); - if (entry.isDirectory()) - continue; - if(entry.getName().endsWith(".so") && entry.getName().contains("lib/" + cpuArch)){ - findSo = true; - break; + if (findAndCopyNativeLib(zipfile, context, Build.CPU_ABI, packageInfo, nativeLibDir)) { + return; } } - e = zipfile.entries(); - while (e.hasMoreElements()) { - entry = (ZipEntry) e.nextElement(); - if (entry.isDirectory() || !entry.getName().endsWith(".so")) + + findAndCopyNativeLib(zipfile, context, "armeabi", packageInfo, nativeLibDir); + + } finally { + zipfile.close(); + Log.d(TAG, "Done! +" + (System.currentTimeMillis() - startTime) + "ms"); + } + } + + private static boolean findAndCopyNativeLib(ZipFile zipfile, Context context, String cpuArch, PackageInfo packageInfo, File nativeLibDir) throws Exception { + Log.d(TAG, "Try to copy plugin's cup arch: " + cpuArch); + boolean findLib = false; + boolean findSo = false; + byte buffer[] = null; + String libPrefix = "lib/" + cpuArch + "/"; + ZipEntry entry; + Enumeration e = zipfile.entries(); + + while (e.hasMoreElements()) { + entry = (ZipEntry) e.nextElement(); + String entryName = entry.getName(); + + if (entryName.charAt(0) < 'l') { + continue; + } + if (entryName.charAt(0) > 'l') { + break; + } + if (!findLib && !entryName.startsWith("lib/")) { + continue; + } + findLib = true; + if (!entryName.endsWith(".so") || !entryName.startsWith(libPrefix)) { + continue; + } + + if (buffer == null) { + findSo = true; + Log.d(TAG, "Found plugin's cup arch dir: " + cpuArch); + buffer = new byte[8192]; + } + + String libName = entryName.substring(entryName.lastIndexOf('/') + 1); + Log.d(TAG, "verify so " + libName); + File libFile = new File(nativeLibDir, libName); + String key = packageInfo.packageName + "_" + libName; + if (libFile.exists()) { + int VersionCode = Settings.getSoVersion(context, key); + if (VersionCode == packageInfo.versionCode) { + Log.d(TAG, "skip existing so : " + entry.getName()); continue; - if((findSo && entry.getName().contains("lib/" + cpuArch)) || (!findSo && entry.getName().contains("lib/armeabi/"))){ - String[] temp = entry.getName().split("/"); - String libName = temp[temp.length - 1]; - System.out.println("verify so " + libName); - File libFile = new File(nativeLibDir.getAbsolutePath() + File.separator + libName); - String key = packageInfo.packageName + "_" + libName; - if (libFile.exists()) { - int VersionCode = Settings.getSoVersion(context, key); - if (VersionCode == packageInfo.versionCode) { - System.out.println("skip existing so : " + entry.getName()); - continue; - } - } - FileOutputStream fos = new FileOutputStream(libFile); - System.out.println("copy so " + entry.getName() + " of " + cpuArch); - copySo(zipfile.getInputStream(entry), fos); - Settings.setSoVersion(context, key, packageInfo.versionCode); } - } - - zipfile.close(); - } catch (IOException e) { - e.printStackTrace(); + FileOutputStream fos = new FileOutputStream(libFile); + Log.d(TAG, "copy so " + entry.getName() + " of " + cpuArch); + copySo(buffer, zipfile.getInputStream(entry), fos); + Settings.setSoVersion(context, key, packageInfo.versionCode); } + + if (!findLib) { + Log.d(TAG, "Fast skip all!"); + return true; + } + + return findSo; } - - private static void copySo(InputStream input, OutputStream output) throws IOException { + + private static void copySo(byte[] buffer, InputStream input, OutputStream output) throws IOException { BufferedInputStream bufferedInput = new BufferedInputStream(input); BufferedOutputStream bufferedOutput = new BufferedOutputStream(output); int count; - byte data[] = new byte[8192]; - while ((count = bufferedInput.read(data, 0, 8192)) != -1) { - bufferedOutput.write(data, 0, count); + + while ((count = bufferedInput.read(buffer)) > 0) { + bufferedOutput.write(buffer, 0, count); } bufferedOutput.flush(); bufferedOutput.close(); diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/Settings.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/Settings.java similarity index 96% rename from CoreLibrary/src/main/java/com/didi/virtualapk/utils/Settings.java rename to CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/Settings.java index bc6d7b9..403f3ce 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/Settings.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/Settings.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.didi.virtualapk.utils; +package com.didi.virtualapk.internal.utils; import android.content.Context; import android.content.SharedPreferences; diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/ReflectUtil.java b/CoreLibrary/src/main/java/com/didi/virtualapk/utils/ReflectUtil.java deleted file mode 100644 index 1633985..0000000 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/ReflectUtil.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.didi.virtualapk.utils; - -import android.app.Instrumentation; -import android.content.Context; -import android.os.Handler; -import android.support.annotation.UiThread; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -@SuppressWarnings("rawtypes") -public class ReflectUtil { - - public static Object sActivityThread; - public static Object sLoadedApk; - public static Instrumentation sInstrumentation; - - public static Object getField(Class clazz, Object target, String name) throws Exception { - Field field = clazz.getDeclaredField(name); - field.setAccessible(true); - return field.get(target); - } - - public static Object getFieldNoException(Class clazz, Object target, String name) { - try { - return ReflectUtil.getField(clazz, target, name); - } catch (Exception e) { - //ignored. - } - - return null; - } - - public static void setField(Class clazz, Object target, String name, Object value) throws Exception { - Field field = clazz.getDeclaredField(name); - field.setAccessible(true); - field.set(target, value); - } - - public static void setFieldNoException(Class clazz, Object target, String name, Object value) { - try { - ReflectUtil.setField(clazz, target, name, value); - } catch (Exception e) { - //ignored. - } - } - - @SuppressWarnings("unchecked") - public static Object invoke(Class clazz, Object target, String name, Object... args) - throws Exception { - Class[] parameterTypes = null; - if (args != null) { - parameterTypes = new Class[args.length]; - for (int i = 0; i < args.length; i++) { - parameterTypes[i] = args[i].getClass(); - } - } - - Method method = clazz.getDeclaredMethod(name, parameterTypes); - method.setAccessible(true); - return method.invoke(target, args); - } - - @SuppressWarnings("unchecked") - public static Object invoke(Class clazz, Object target, String name, Class[] parameterTypes, Object... args) - throws Exception { - Method method = clazz.getDeclaredMethod(name, parameterTypes); - method.setAccessible(true); - return method.invoke(target, args); - } - - @SuppressWarnings("unchecked") - public static Object invokeNoException(Class clazz, Object target, String name, Class[] parameterTypes, Object... args) { - try { - return invoke(clazz, target, name, parameterTypes, args); - } catch (Exception e) { - } - - return null; - } - - @SuppressWarnings("unchecked") - public static Object invokeConstructor(Class clazz, Class[] parameterTypes, Object... args) - throws Exception { - Constructor constructor = clazz.getDeclaredConstructor(parameterTypes); - constructor.setAccessible(true); - return constructor.newInstance(args); - } - - @UiThread - public static Object getActivityThread(Context base) { - if (sActivityThread == null) { - try { - Class activityThreadClazz = Class.forName("android.app.ActivityThread"); - Object activityThread = null; - try { - activityThread = ReflectUtil.getField(activityThreadClazz, null, "sCurrentActivityThread"); - } catch (Exception e) { - // ignored - } - if (activityThread == null) { - activityThread = ((ThreadLocal) ReflectUtil.getField(activityThreadClazz, null, "sThreadLocal")).get(); - } - sActivityThread = activityThread; - } catch (Exception e) { - e.printStackTrace(); - } - } - - return sActivityThread; - } - - public static Instrumentation getInstrumentation(Context base) { - if (getActivityThread(base) != null) { - try { - sInstrumentation = (Instrumentation) ReflectUtil.invoke( - sActivityThread.getClass(), sActivityThread, "getInstrumentation"); - } catch (Exception e) { - e.printStackTrace(); - } - } - - return sInstrumentation; - } - - public static void setInstrumentation(Object activityThread, Instrumentation instrumentation) { - try { - ReflectUtil.setField(activityThread.getClass(), activityThread, "mInstrumentation", instrumentation); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static Object getPackageInfo(Context base) { - if (sLoadedApk == null) { - try { - sLoadedApk = ReflectUtil.getField(base.getClass(), base, "mPackageInfo"); - } catch (Exception e) { - e.printStackTrace(); - } - } - - return sLoadedApk; - } - - public static void setHandlerCallback(Context base, Handler.Callback callback) { - try { - Object activityThread = getActivityThread(base); - Handler mainHandler = (Handler) ReflectUtil.invoke(activityThread.getClass(), activityThread, "getHandler", (Object[])null); - ReflectUtil.setField(Handler.class, mainHandler, "mCallback", callback); - } catch (Exception e) { - e.printStackTrace(); - } - } - -} diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/Reflector.java b/CoreLibrary/src/main/java/com/didi/virtualapk/utils/Reflector.java new file mode 100644 index 0000000..9921b6f --- /dev/null +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/utils/Reflector.java @@ -0,0 +1,449 @@ +package com.didi.virtualapk.utils; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.didi.virtualapk.internal.Constants; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * Created by qiaopu on 2018/4/26. + */ +public class Reflector { + + public static final String LOG_TAG = Constants.TAG_PREFIX + "Reflector"; + + protected Class mType; + protected Object mCaller; + protected Constructor mConstructor; + protected Field mField; + protected Method mMethod; + + public static class ReflectedException extends Exception { + + public ReflectedException(String message) { + super(message); + } + public ReflectedException(String message, Throwable cause) { + super(message, cause); + } + + } + public static Reflector on(@NonNull String name) throws ReflectedException { + return on(name, true, Reflector.class.getClassLoader()); + } + + public static Reflector on(@NonNull String name, boolean initialize) throws ReflectedException { + return on(name, initialize, Reflector.class.getClassLoader()); + } + + public static Reflector on(@NonNull String name, boolean initialize, @Nullable ClassLoader loader) throws ReflectedException { + try { + return on(Class.forName(name, initialize, loader)); + } catch (Throwable e) { + throw new ReflectedException("Oops!", e); + } + } + + public static Reflector on(@NonNull Class type) { + Reflector reflector = new Reflector(); + reflector.mType = type; + return reflector; + } + + public static Reflector with(@NonNull Object caller) throws ReflectedException { + return on(caller.getClass()).bind(caller); + } + + protected Reflector() { + + } + + public Reflector constructor(@Nullable Class... parameterTypes) throws ReflectedException { + try { + mConstructor = mType.getDeclaredConstructor(parameterTypes); + mConstructor.setAccessible(true); + mField = null; + mMethod = null; + return this; + } catch (Throwable e) { + throw new ReflectedException("Oops!", e); + } + } + + @SuppressWarnings("unchecked") + public R newInstance(@Nullable Object ... initargs) throws ReflectedException { + if (mConstructor == null) { + throw new ReflectedException("Constructor was null!"); + } + try { + return (R) mConstructor.newInstance(initargs); + } catch (InvocationTargetException e) { + throw new ReflectedException("Oops!", e.getTargetException()); + } catch (Throwable e) { + throw new ReflectedException("Oops!", e); + } + } + + protected Object checked(@Nullable Object caller) throws ReflectedException { + if (caller == null || mType.isInstance(caller)) { + return caller; + } + throw new ReflectedException("Caller [" + caller + "] is not a instance of type [" + mType + "]!"); + } + + protected void check(@Nullable Object caller, @Nullable Member member, @NonNull String name) throws ReflectedException { + if (member == null) { + throw new ReflectedException(name + " was null!"); + } + if (caller == null && !Modifier.isStatic(member.getModifiers())) { + throw new ReflectedException("Need a caller!"); + } + checked(caller); + } + + public Reflector bind(@Nullable Object caller) throws ReflectedException { + mCaller = checked(caller); + return this; + } + + public Reflector unbind() { + mCaller = null; + return this; + } + + public Reflector field(@NonNull String name) throws ReflectedException { + try { + mField = findField(name); + mField.setAccessible(true); + mConstructor = null; + mMethod = null; + return this; + } catch (Throwable e) { + throw new ReflectedException("Oops!", e); + } + } + + protected Field findField(@NonNull String name) throws NoSuchFieldException { + try { + return mType.getField(name); + } catch (NoSuchFieldException e) { + for (Class cls = mType; cls != null; cls = cls.getSuperclass()) { + try { + return cls.getDeclaredField(name); + } catch (NoSuchFieldException ex) { + // Ignored + } + } + throw e; + } + } + + @SuppressWarnings("unchecked") + public R get() throws ReflectedException { + return get(mCaller); + } + + @SuppressWarnings("unchecked") + public R get(@Nullable Object caller) throws ReflectedException { + check(caller, mField, "Field"); + try { + return (R) mField.get(caller); + } catch (Throwable e) { + throw new ReflectedException("Oops!", e); + } + } + + public Reflector set(@Nullable Object value) throws ReflectedException { + return set(mCaller, value); + } + + public Reflector set(@Nullable Object caller, @Nullable Object value) throws ReflectedException { + check(caller, mField, "Field"); + try { + mField.set(caller, value); + return this; + } catch (Throwable e) { + throw new ReflectedException("Oops!", e); + } + } + + public Reflector method(@NonNull String name, @Nullable Class... parameterTypes) throws ReflectedException { + try { + mMethod = findMethod(name, parameterTypes); + mMethod.setAccessible(true); + mConstructor = null; + mField = null; + return this; + } catch (NoSuchMethodException e) { + throw new ReflectedException("Oops!", e); + } + } + + protected Method findMethod(@NonNull String name, @Nullable Class... parameterTypes) throws NoSuchMethodException { + try { + return mType.getMethod(name, parameterTypes); + } catch (NoSuchMethodException e) { + for (Class cls = mType; cls != null; cls = cls.getSuperclass()) { + try { + return cls.getDeclaredMethod(name, parameterTypes); + } catch (NoSuchMethodException ex) { + // Ignored + } + } + throw e; + } + } + + public R call(@Nullable Object... args) throws ReflectedException { + return callByCaller(mCaller, args); + } + + @SuppressWarnings("unchecked") + public R callByCaller(@Nullable Object caller, @Nullable Object... args) throws ReflectedException { + check(caller, mMethod, "Method"); + try { + return (R) mMethod.invoke(caller, args); + } catch (InvocationTargetException e) { + throw new ReflectedException("Oops!", e.getTargetException()); + } catch (Throwable e) { + throw new ReflectedException("Oops!", e); + } + } + + public static class QuietReflector extends Reflector { + + protected Throwable mIgnored; + + public static QuietReflector on(@NonNull String name) { + return on(name, true, QuietReflector.class.getClassLoader()); + } + + public static QuietReflector on(@NonNull String name, boolean initialize) { + return on(name, initialize, QuietReflector.class.getClassLoader()); + } + + public static QuietReflector on(@NonNull String name, boolean initialize, @Nullable ClassLoader loader) { + Class cls = null; + try { + cls = Class.forName(name, initialize, loader); + return on(cls, null); + } catch (Throwable e) { +// Log.w(LOG_TAG, "Oops!", e); + return on(cls, e); + } + } + + public static QuietReflector on(@Nullable Class type) { + return on(type, (type == null) ? new ReflectedException("Type was null!") : null); + } + + private static QuietReflector on(@Nullable Class type, @Nullable Throwable ignored) { + QuietReflector reflector = new QuietReflector(); + reflector.mType = type; + reflector.mIgnored = ignored; + return reflector; + } + + public static QuietReflector with(@Nullable Object caller) { + if (caller == null) { + return on((Class) null); + } + return on(caller.getClass()).bind(caller); + } + + protected QuietReflector() { + + } + + public Throwable getIgnored() { + return mIgnored; + } + + protected boolean skip() { + return skipAlways() || mIgnored != null; + } + + protected boolean skipAlways() { + return mType == null; + } + + @Override + public QuietReflector constructor(@Nullable Class... parameterTypes) { + if (skipAlways()) { + return this; + } + try { + mIgnored = null; + super.constructor(parameterTypes); + } catch (Throwable e) { + mIgnored = e; +// Log.w(LOG_TAG, "Oops!", e); + } + return this; + } + + @Override + public R newInstance(@Nullable Object... initargs) { + if (skip()) { + return null; + } + try { + mIgnored = null; + return super.newInstance(initargs); + } catch (Throwable e) { + mIgnored = e; +// Log.w(LOG_TAG, "Oops!", e); + } + return null; + } + + @Override + public QuietReflector bind(@Nullable Object obj) { + if (skipAlways()) { + return this; + } + try { + mIgnored = null; + super.bind(obj); + } catch (Throwable e) { + mIgnored = e; +// Log.w(LOG_TAG, "Oops!", e); + } + return this; + } + + @Override + public QuietReflector unbind() { + super.unbind(); + return this; + } + + @Override + public QuietReflector field(@NonNull String name) { + if (skipAlways()) { + return this; + } + try { + mIgnored = null; + super.field(name); + } catch (Throwable e) { + mIgnored = e; +// Log.w(LOG_TAG, "Oops!", e); + } + return this; + } + + @Override + public R get() { + if (skip()) { + return null; + } + try { + mIgnored = null; + return super.get(); + } catch (Throwable e) { + mIgnored = e; +// Log.w(LOG_TAG, "Oops!", e); + } + return null; + } + + @Override + public R get(@Nullable Object caller) { + if (skip()) { + return null; + } + try { + mIgnored = null; + return super.get(caller); + } catch (Throwable e) { + mIgnored = e; +// Log.w(LOG_TAG, "Oops!", e); + } + return null; + } + + @Override + public QuietReflector set(@Nullable Object value) { + if (skip()) { + return this; + } + try { + mIgnored = null; + super.set(value); + } catch (Throwable e) { + mIgnored = e; +// Log.w(LOG_TAG, "Oops!", e); + } + return this; + } + + @Override + public QuietReflector set(@Nullable Object caller, @Nullable Object value) { + if (skip()) { + return this; + } + try { + mIgnored = null; + super.set(caller, value); + } catch (Throwable e) { + mIgnored = e; +// Log.w(LOG_TAG, "Oops!", e); + } + return this; + } + + @Override + public QuietReflector method(@NonNull String name, @Nullable Class... parameterTypes) { + if (skipAlways()) { + return this; + } + try { + mIgnored = null; + super.method(name, parameterTypes); + } catch (Throwable e) { + mIgnored = e; +// Log.w(LOG_TAG, "Oops!", e); + } + return this; + } + + @Override + public R call(@Nullable Object... args) { + if (skip()) { + return null; + } + try { + mIgnored = null; + return super.call(args); + } catch (Throwable e) { + mIgnored = e; +// Log.w(LOG_TAG, "Oops!", e); + } + return null; + } + + @Override + public R callByCaller(@Nullable Object caller, @Nullable Object... args) { + if (skip()) { + return null; + } + try { + mIgnored = null; + return super.callByCaller(caller, args); + } catch (Throwable e) { + mIgnored = e; +// Log.w(LOG_TAG, "Oops!", e); + } + return null; + } + } +} diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/RunUtil.java b/CoreLibrary/src/main/java/com/didi/virtualapk/utils/RunUtil.java index 2a1dd56..b0aa93a 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/RunUtil.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/utils/RunUtil.java @@ -23,12 +23,14 @@ import android.os.Looper; import android.os.Message; import android.os.Process; +import android.util.Log; import android.util.Pair; +import com.didi.virtualapk.internal.Constants; + import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; -import java.util.concurrent.ThreadPoolExecutor; /** * Created by renyugang on 16/11/10. @@ -53,7 +55,7 @@ public static void runOnUiThread(Runnable runnable) { * @param waitUtilDone if set true, the caller thread will wait until the specific runnable finished. */ public static void runOnUiThread(Runnable runnable, boolean waitUtilDone) { - if (Looper.myLooper() == Looper.getMainLooper()) { + if (Thread.currentThread() == Looper.getMainLooper().getThread()) { runnable.run(); return; } @@ -68,7 +70,7 @@ public static void runOnUiThread(Runnable runnable, boolean waitUtilDone) { try { countDownLatch.await(); } catch (InterruptedException e) { - e.printStackTrace(); + Log.w(Constants.TAG, e); } } } @@ -77,17 +79,22 @@ public static Executor getThreadPool() { return AsyncTask.THREAD_POOL_EXECUTOR; } - public static String getProcessNameByPid(Context context, int pid) { - ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - List appProcessList = manager.getRunningAppProcesses(); - if (appProcessList != null) { - for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessList) { - if (pid == appProcessInfo.pid) { - return appProcessInfo.processName; + private static String getProcessNameByPid(Context context, int pid) { + try { + ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List appProcessList = manager.getRunningAppProcesses(); + if (appProcessList != null) { + for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessList) { + if (pid == appProcessInfo.pid) { + return appProcessInfo.processName; + } } } + + } catch (Throwable e) { + Log.w(Constants.TAG, e); } - + return null; } @@ -117,11 +124,15 @@ public InternalHandler() { @Override public void handleMessage(Message msg) { if (msg.what == MESSAGE_RUN_ON_UITHREAD) { - Pair pair = (Pair)msg.obj; - Runnable runnable = pair.first; - runnable.run(); - if (pair.second != null) { - pair.second.countDown(); + Pair pair = (Pair) msg.obj; + try { + Runnable runnable = pair.first; + runnable.run(); + + } finally { + if (pair.second != null) { + pair.second.countDown(); + } } } } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/ZipVerifyUtil.java b/CoreLibrary/src/main/java/com/didi/virtualapk/utils/ZipVerifyUtil.java index 622c53a..1618013 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/utils/ZipVerifyUtil.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/utils/ZipVerifyUtil.java @@ -18,6 +18,9 @@ import android.content.Context; import android.util.Base64; +import android.util.Log; + +import com.didi.virtualapk.internal.Constants; import java.io.File; import java.io.FileInputStream; @@ -46,14 +49,18 @@ public class ZipVerifyUtil { public static boolean verifyZip(Context context, String zipPath) { + return verifyZip(context, zipPath, "test.cer"); + } + + public static boolean verifyZip(Context context, String zipPath, String cerName) { try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - InputStream in = context.getAssets().open("test.cer"); + InputStream in = context.getAssets().open(cerName); Certificate certificate = certificateFactory.generateCertificate(in); in.close(); return verifyZip(zipPath, certificate); } catch (IOException | CertificateException e) { - e.printStackTrace(); + Log.w(Constants.TAG, e); return false; } } @@ -65,7 +72,7 @@ public static boolean verifyZip(String zipPath, Certificate remoteCertificate) { remoteCertificate.verify(certificate.getPublicKey()); return true; } catch (Exception e) { - e.printStackTrace(); + Log.w(Constants.TAG, e); return false; } } diff --git a/CoreLibrary/upload.gradle b/CoreLibrary/upload.gradle index 42f9158..3f2b0e1 100644 --- a/CoreLibrary/upload.gradle +++ b/CoreLibrary/upload.gradle @@ -11,7 +11,7 @@ def gitUrl = 'https://github.com/didi/VirtualAPK' // Git仓库的url group = GROUP_ID archivesBaseName = 'core' -version = "0.9.1" +version = "0.9.8" install { diff --git a/PluginDemo/app/build.gradle b/PluginDemo/app/build.gradle index 5ee7120..bf7b36d 100644 --- a/PluginDemo/app/build.gradle +++ b/PluginDemo/app/build.gradle @@ -1,26 +1,41 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 23 - buildToolsVersion "25.0.2" + compileSdkVersion VERSION_COMPILE_SDK + buildToolsVersion VERSION_BUILD_TOOLS defaultConfig { applicationId "com.didi.virtualapk.demo" - minSdkVersion 15 - targetSdkVersion 15 + minSdkVersion VERSION_MIN_SDK + targetSdkVersion VERSION_TARGET_SDK versionName "1.0.0" versionCode 1 } + compileOptions { + sourceCompatibility SOURCE_COMPATIBILITY + } + flavorDimensions "demo" productFlavors { beijing { + dimension "demo" applicationId 'com.didi.virtualapk.demo' } shanghai { + dimension "demo" applicationId 'com.didi.virtualapk.demo' } } + signingConfigs { + release { + storeFile file("../../keystore/test.keystore") + storePassword "test123456" + keyAlias "test" + keyPassword "test123456" + } + } + buildTypes { debug { minifyEnabled false @@ -29,6 +44,7 @@ android { release { minifyEnabled true shrinkResources true + signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -37,8 +53,8 @@ android { dependencies { // the following aars are also compiled in host project, so they will be filterd when build plugin apk. // but, wo can still visit their Class and Resources. - compile 'com.android.support:appcompat-v7:22.2.0' - compile 'com.didi.virtualapk:core:0.9.1' + implementation 'com.android.support:appcompat-v7:23.4.0' +// implementation 'com.didi.virtualapk:core:0.9.8' } apply plugin: 'com.didi.virtualapk.plugin' diff --git a/PluginDemo/app/gradle.properties b/PluginDemo/app/gradle.properties new file mode 100644 index 0000000..ba00769 --- /dev/null +++ b/PluginDemo/app/gradle.properties @@ -0,0 +1,2 @@ +#android.enableD8=false +android.useDexArchive=false \ No newline at end of file diff --git a/PluginDemo/app/proguard-rules.pro b/PluginDemo/app/proguard-rules.pro index 49853ae..5d295ae 100644 --- a/PluginDemo/app/proguard-rules.pro +++ b/PluginDemo/app/proguard-rules.pro @@ -122,5 +122,5 @@ -keep class com.didi.virtualapk.internal.PluginContentResolver { *; } -dontwarn com.didi.virtualapk.** --dontwarn android.content.pm.** +-dontwarn android.** -keep class android.** { *; } \ No newline at end of file diff --git a/PluginDemo/build.gradle b/PluginDemo/build.gradle index ca18371..f283b2c 100644 --- a/PluginDemo/build.gradle +++ b/PluginDemo/build.gradle @@ -1,16 +1,28 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' - classpath 'com.didi.virtualapk:gradle:0.9.4' + classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.didi.virtualapk:gradle:0.9.8.6' } } +ext { + VERSION_COMPILE_SDK = 27 + VERSION_BUILD_TOOLS = '26.0.2' + + VERSION_MIN_SDK = 15 + VERSION_TARGET_SDK = 25 + + SOURCE_COMPATIBILITY = JavaVersion.VERSION_1_7 +} + allprojects { repositories { + google() jcenter() } } diff --git a/PluginDemo/gradle/wrapper/gradle-wrapper.properties b/PluginDemo/gradle/wrapper/gradle-wrapper.properties index 9a778d6..c3510d6 100644 --- a/PluginDemo/gradle/wrapper/gradle-wrapper.properties +++ b/PluginDemo/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/README.md b/README.md index 154e6e6..610a986 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ +# Android 9.0 supported! Please use [the lastest release](https://github.com/didi/VirtualAPK/releases). + # VirtualAPK [![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/didi/VirtualAPK/blob/master/LICENSE) -[![Release Version](https://img.shields.io/badge/release-0.9.1-red.svg)](https://github.com/didi/VirtualAPK/releases) +[![Release Version](https://img.shields.io/badge/release-0.9.8-red.svg)](https://github.com/didi/VirtualAPK/releases) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/didi/VirtualAPK/pulls) VirtualAPK is a powerful yet lightweight plugin framework for Android. It can dynamically load and run an APK file (we call it `LoadedPlugin`) seamlessly as an installed application. Developers can use any Class, Resources, Activity, Service, Receiver and Provider in `LoadedPlugin` as if they are registered in app's manifest file. @@ -28,7 +30,7 @@ Add a dependency in `build.gradle` in root of host project as following. ``` java dependencies { - classpath 'com.didi.virtualapk:gradle:0.9.4' + classpath 'com.didi.virtualapk:gradle:0.9.8.6' } ``` @@ -41,7 +43,7 @@ apply plugin: 'com.didi.virtualapk.host' Compile VirtualAPK in application module of `build.gradle`. ``` java -compile 'com.didi.virtualapk:core:0.9.1' +compile 'com.didi.virtualapk:core:0.9.8' ``` Initialize `PluginManager` in `YourApplication::attachBaseContext()`. @@ -61,7 +63,7 @@ Modify proguard rules to keep VirtualAPK related files. -keep class com.didi.virtualapk.internal.PluginContentResolver { *; } -dontwarn com.didi.virtualapk.** --dontwarn android.content.pm.** +-dontwarn android.** -keep class android.** { *; } ``` @@ -85,7 +87,7 @@ Add a dependency in `build.gradle` in root of plugin project as following. ``` java dependencies { - classpath 'com.didi.virtualapk:gradle:0.9.4' + classpath 'com.didi.virtualapk:gradle:0.9.8.6' } ``` diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 04a8468..233cf88 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,9 +1,9 @@ # Release Notes -## 0.9.0 +## com.didi.virtualapk:core:0.9.0 开源的第一个版本,支持了几乎所有 Android 特性,目前已经被大家广泛使用。 -## 0.9.1 +## com.didi.virtualapk:core:0.9.1 ##### 1. 最初,为了让程序更加健壮,当我们通过无效的 Intent 来启动插件中的 Activity,这个时候程序不会报错。尽管我们的初衷是好的,但现在我们觉得这种方式太过友好了,不利于bug的排查。 ```改动```:现在,启动无效的 Activity 会直接抛出 ActivityNotFoundException。 @@ -26,4 +26,34 @@ ```改动```:hook Android N 的资源,尽管是多余的。 +## com.didi.virtualapk:core:0.9.5 +1. 修复多个bug,强烈建议升级至此版本,以前版本不再维护。 +2. 与 com.didi.virtualapk:gradle:0.9.8.2及以上版本 搭配使用,支持官方 Data Binding。 + +## com.didi.virtualapk:core:0.9.6 +1. 修复部分空指针问题。 + +## com.didi.virtualapk:core:0.9.6 +1. 代码重构。 +2. 修复部分空指针bug。 +3. 适配Android P。 +4. 修复webview初始化后插件资源不可用的bug。 + ## VirtualAPK 的构建部分已经开源了,![点击这里查看](https://github.com/didi/VirtualAPK/tree/master/virtualapk-gradle-plugin) + +## com.didi.virtualapk:gradle:0.9.8.2 +1. 适配android gradle 3.0.0 +2. 修复多个bug,强烈建议升级至此版本,以前版本不再维护。 +3. 插件工程需要定义 productFlavors。 + +## com.didi.virtualapk:gradle:0.9.8.3 +1. 兼容不定义 productFlavors 的配置。 + +## com.didi.virtualapk:gradle:0.9.8.4 +1. 修复当插件依赖library module时构建失败的bug。 +2. 修复依赖本地aar时构建失败的bug。 +3. 修复当插件自定义attr属性时id错误的bug。 + +## com.didi.virtualapk:gradle:0.9.8.6 +1. 适配com.android.tools.build:gradle:3.1.0。 +2. 修复当插件未定义attr资源时anim资源找不到的bug。 diff --git a/app/build.gradle b/app/build.gradle index 264c9de..c8f8daa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,11 +45,11 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' - compile 'com.didi.virtualapk:core:0.9.1' - - //compile project (":CoreLibrary") + implementation 'com.android.support:appcompat-v7:23.4.0' + implementation 'com.didi.virtualapk:core:0.9.8' +// implementation project (':CoreLibrary') } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 2497d80..656714e 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -121,5 +121,5 @@ -keep class com.didi.virtualapk.internal.PluginContentResolver { *; } -dontwarn com.didi.virtualapk.** --dontwarn android.content.pm.** +-dontwarn android.** -keep class android.** { *; } \ No newline at end of file diff --git a/app/src/main/java/com/didi/virtualapk/MainActivity.java b/app/src/main/java/com/didi/virtualapk/MainActivity.java index fe7aeca..d355264 100644 --- a/app/src/main/java/com/didi/virtualapk/MainActivity.java +++ b/app/src/main/java/com/didi/virtualapk/MainActivity.java @@ -1,27 +1,37 @@ package com.didi.virtualapk; +import android.Manifest; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; +import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; -import com.didi.virtualapk.internal.PluginContentResolver; import com.didi.virtualapk.internal.LoadedPlugin; +import com.didi.virtualapk.internal.PluginContentResolver; import java.io.File; - public class MainActivity extends AppCompatActivity { + private static final int PERMISSION_REQUEST_CODE_STORAGE = 20171222; + + private static final String TAG = "MainActivity"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -35,10 +45,50 @@ protected void onCreate(Bundle savedInstanceState) { } textView.setText(cpuArch); Log.d("ryg", "onCreate cpu arch is "+ cpuArch); - this.loadPlugin(this); Log.d("ryg", "onCreate classloader is "+ getClassLoader()); + + if (hasPermission()) { + Log.d(TAG,"loadPlugin"); + + this.loadPlugin(this); + } else { + requestPermission(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (PERMISSION_REQUEST_CODE_STORAGE == requestCode) { + if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { + requestPermission(); + } else { + this.loadPlugin(this); + } + return; + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults); } + + private boolean hasPermission() { + + Log.d(TAG,"hasPermission"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } + return true; + } + + private void requestPermission() { + + Log.d(TAG,"requestPermission"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE_STORAGE); + } + } + + public void onButtonClick(View v) { if (v.getId() == R.id.button) { final String pkg = "com.didi.virtualapk.demo"; @@ -58,23 +108,54 @@ public void onButtonClick(View v) { bookUri = PluginContentResolver.wrapperUri(plugin, bookUri); Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null); - while (bookCursor.moveToNext()) { - int bookId = bookCursor.getInt(0); - String bookName = bookCursor.getString(1); - Log.d("ryg", "query book:" + bookId + ", " + bookName); + if (bookCursor != null) { + while (bookCursor.moveToNext()) { + int bookId = bookCursor.getInt(0); + String bookName = bookCursor.getString(1); + Log.d("ryg", "query book:" + bookId + ", " + bookName); + } + bookCursor.close(); } - bookCursor.close(); } else if (v.getId() == R.id.about) { showAbout(); + } else if (v.getId() == R.id.webview) { + LinearLayout linearLayout = (LinearLayout) v.getParent(); + WebView webView = new WebView(this); + linearLayout.addView(webView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1)); + webView.setWebViewClient(new WebViewClient()); + webView.loadUrl("http://github.com/didi/VirtualAPK"); } } private void loadPlugin(Context base) { + if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + Toast.makeText(this, "sdcard was NOT MOUNTED!", Toast.LENGTH_SHORT).show(); + } PluginManager pluginManager = PluginManager.getInstance(base); File apk = new File(Environment.getExternalStorageDirectory(), "Test.apk"); if (apk.exists()) { try { pluginManager.loadPlugin(apk); + Log.i(TAG, "Loaded plugin from apk: " + apk); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + try { + File file = new File(base.getFilesDir(), "Test.apk"); + java.io.InputStream inputStream = base.getAssets().open("Test.apk", 2); + java.io.FileOutputStream outputStream = new java.io.FileOutputStream(file); + byte[] buf = new byte[1024]; + int len; + + while ((len = inputStream.read(buf)) > 0) { + outputStream.write(buf, 0, len); + } + + outputStream.close(); + inputStream.close(); + pluginManager.loadPlugin(file); + Log.i(TAG, "Loaded plugin from assets: " + file); } catch (Exception e) { e.printStackTrace(); } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 79b54dd..b092c85 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,8 +1,9 @@ -