Repository: didi/VirtualAPK Branch: master Commit: b1a778a06688 Files: 211 Total size: 640.5 KB Directory structure: gitextract__l3v4sn5/ ├── .gitignore ├── AndroidStub/ │ ├── README.md │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── aidl/ │ │ └── android/ │ │ ├── app/ │ │ │ ├── IActivityManager.aidl │ │ │ ├── IApplicationThread.aidl │ │ │ ├── INotificationManager.aidl │ │ │ └── IServiceConnection.aidl │ │ └── content/ │ │ ├── IIntentReceiver.aidl │ │ └── IIntentSender.aidl │ └── java/ │ ├── android/ │ │ ├── app/ │ │ │ ├── ActivityManagerNative.java │ │ │ ├── ActivityThread.java │ │ │ ├── ApplicationThreadNative.java │ │ │ ├── Instrumentation.java │ │ │ ├── LoadedApk.java │ │ │ └── ResourcesManager.java │ │ ├── content/ │ │ │ ├── ContentResolver.java │ │ │ ├── IContentProvider.java │ │ │ ├── pm/ │ │ │ │ ├── ManifestDigest.java │ │ │ │ ├── PackageParser.java │ │ │ │ ├── PackageUserState.java │ │ │ │ └── VerifierInfo.java │ │ │ └── res/ │ │ │ ├── CompatibilityInfo.java │ │ │ ├── Resources.java │ │ │ ├── ResourcesImpl.java │ │ │ └── ResourcesKey.java │ │ ├── databinding/ │ │ │ ├── DataBinderMapper.java │ │ │ └── DataBindingComponent.java │ │ ├── os/ │ │ │ ├── ServiceManager.java │ │ │ └── SystemProperties.java │ │ └── util/ │ │ └── Singleton.java │ └── com/ │ └── android/ │ └── internal/ │ └── R.java ├── CONTRIBUTING.md ├── CoreLibrary/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ ├── src/ │ │ ├── androidTest/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── didi/ │ │ │ └── virtualapk/ │ │ │ └── core/ │ │ │ └── ApplicationTest.java │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ ├── android/ │ │ │ │ │ ├── content/ │ │ │ │ │ │ └── ContentResolverWrapper.java │ │ │ │ │ └── databinding/ │ │ │ │ │ └── DataBinderMapperProxy.java │ │ │ │ └── com/ │ │ │ │ └── didi/ │ │ │ │ └── virtualapk/ │ │ │ │ ├── PluginManager.java │ │ │ │ ├── delegate/ │ │ │ │ │ ├── ActivityManagerProxy.java │ │ │ │ │ ├── IContentProviderProxy.java │ │ │ │ │ ├── LocalService.java │ │ │ │ │ ├── RemoteContentProvider.java │ │ │ │ │ ├── RemoteService.java │ │ │ │ │ └── StubActivity.java │ │ │ │ ├── internal/ │ │ │ │ │ ├── ActivityLifecycleCallbacksProxy.java │ │ │ │ │ ├── ComponentsHandler.java │ │ │ │ │ ├── Constants.java │ │ │ │ │ ├── LoadedPlugin.java │ │ │ │ │ ├── PluginContentResolver.java │ │ │ │ │ ├── PluginContext.java │ │ │ │ │ ├── ResourcesManager.java │ │ │ │ │ ├── StubActivityInfo.java │ │ │ │ │ ├── VAInstrumentation.java │ │ │ │ │ └── utils/ │ │ │ │ │ ├── DexUtil.java │ │ │ │ │ ├── PackageParserCompat.java │ │ │ │ │ ├── PluginUtil.java │ │ │ │ │ └── Settings.java │ │ │ │ └── utils/ │ │ │ │ ├── Reflector.java │ │ │ │ ├── RunUtil.java │ │ │ │ └── ZipVerifyUtil.java │ │ │ └── res/ │ │ │ └── values/ │ │ │ └── strings.xml │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── didi/ │ │ └── virtualapk/ │ │ └── core/ │ │ └── ExampleUnitTest.java │ └── upload.gradle ├── LICENSE ├── PluginDemo/ │ ├── app/ │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── aidl/ │ │ │ └── com/ │ │ │ └── didi/ │ │ │ └── virtualapk/ │ │ │ └── demo/ │ │ │ ├── aidl/ │ │ │ │ ├── Book.aidl │ │ │ │ ├── IBookManager.aidl │ │ │ │ └── IOnNewBookArrivedListener.aidl │ │ │ ├── binderpool/ │ │ │ │ ├── IBinderPool.aidl │ │ │ │ ├── ICompute.aidl │ │ │ │ └── ISecurityCenter.aidl │ │ │ └── manualbinder/ │ │ │ └── Book.aidl │ │ ├── java/ │ │ │ └── com/ │ │ │ └── didi/ │ │ │ └── virtualapk/ │ │ │ └── demo/ │ │ │ ├── MainActivity.java │ │ │ ├── MyApplication.java │ │ │ ├── SecondActivity.java │ │ │ ├── ThirdActivity.java │ │ │ ├── aidl/ │ │ │ │ ├── Book.java │ │ │ │ ├── BookManagerActivity.java │ │ │ │ └── BookManagerService.java │ │ │ ├── binderpool/ │ │ │ │ ├── BinderPool.java │ │ │ │ ├── BinderPoolActivity.java │ │ │ │ ├── BinderPoolService.java │ │ │ │ ├── ComputeImpl.java │ │ │ │ └── SecurityCenterImpl.java │ │ │ ├── manager/ │ │ │ │ ├── BookManager.java │ │ │ │ └── UserManager.java │ │ │ ├── manualbinder/ │ │ │ │ ├── Book.java │ │ │ │ ├── BookManagerImpl.java │ │ │ │ └── IBookManager.java │ │ │ ├── messenger/ │ │ │ │ ├── MessengerActivity.java │ │ │ │ └── MessengerService.java │ │ │ ├── model/ │ │ │ │ └── User.java │ │ │ ├── provider/ │ │ │ │ ├── BookProvider.java │ │ │ │ ├── DbOpenHelper.java │ │ │ │ └── ProviderActivity.java │ │ │ ├── socket/ │ │ │ │ ├── TCPClientActivity.java │ │ │ │ └── TCPServerService.java │ │ │ └── utils/ │ │ │ ├── MyConstants.java │ │ │ └── MyUtils.java │ │ └── res/ │ │ ├── drawable/ │ │ │ └── edit.xml │ │ ├── layout/ │ │ │ ├── activity_binder_pool.xml │ │ │ ├── activity_book_manager.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_messenger.xml │ │ │ ├── activity_provider.xml │ │ │ ├── activity_second.xml │ │ │ ├── activity_tcpclient.xml │ │ │ └── activity_third.xml │ │ └── values/ │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── make.sh │ └── settings.gradle ├── README.md ├── RELEASE-NOTES.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── didi/ │ │ └── virtualapk/ │ │ └── ApplicationTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── didi/ │ │ │ └── virtualapk/ │ │ │ ├── MainActivity.java │ │ │ └── VAApplication.java │ │ └── res/ │ │ ├── layout/ │ │ │ └── activity_main.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── values-en/ │ │ │ └── strings.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ └── test/ │ └── java/ │ └── com/ │ └── didi/ │ └── virtualapk/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── keystore/ │ ├── test.cer │ └── test.keystore ├── settings.gradle └── virtualapk-gradle-plugin/ ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── src/ │ └── main/ │ ├── groovy/ │ │ └── com.didi.virtualapk/ │ │ ├── BasePlugin.groovy │ │ ├── Constants.groovy │ │ ├── VAExtention.groovy │ │ ├── VAHostPlugin.groovy │ │ ├── VAPlugin.groovy │ │ ├── aapt/ │ │ │ ├── AXmlEditor.groovy │ │ │ ├── Aapt.groovy │ │ │ ├── ArscEditor.groovy │ │ │ ├── AssetEditor.groovy │ │ │ ├── CppHexEditor.groovy │ │ │ ├── ResAttr.groovy │ │ │ ├── ResStringFlag.groovy │ │ │ ├── ResStringPoolSpan.groovy │ │ │ ├── ResTableEntry.groovy │ │ │ ├── ResTableType.groovy │ │ │ ├── ResType.groovy │ │ │ ├── ResValueDataType.groovy │ │ │ ├── SymbolParser.groovy │ │ │ └── packageinfo │ │ ├── collector/ │ │ │ ├── HostClassAndResCollector.groovy │ │ │ ├── HostJniLibsCollector.groovy │ │ │ ├── ResourceCollector.groovy │ │ │ ├── dependence/ │ │ │ │ ├── AarDependenceInfo.groovy │ │ │ │ ├── DependenceInfo.groovy │ │ │ │ └── JarDependenceInfo.groovy │ │ │ └── res/ │ │ │ ├── ResourceEntry.groovy │ │ │ └── StyleableEntry.groovy │ │ ├── hooker/ │ │ │ ├── DxTaskHooker.groovy │ │ │ ├── GradleTaskHooker.groovy │ │ │ ├── MergeAssetsHooker.groovy │ │ │ ├── MergeJniLibsHooker.groovy │ │ │ ├── MergeManifestsHooker.groovy │ │ │ ├── PrepareDependenciesHooker.groovy │ │ │ ├── ProcessResourcesHooker.groovy │ │ │ ├── ProguardHooker.groovy │ │ │ ├── ShrinkResourcesHooker.groovy │ │ │ └── TaskHookerManager.groovy │ │ ├── tasks/ │ │ │ └── AssemblePlugin.groovy │ │ ├── transform/ │ │ │ ├── StripClassAndResTransform.groovy │ │ │ └── TransformWrapper.groovy │ │ └── utils/ │ │ ├── CheckList.groovy │ │ ├── FileBinaryCategory.groovy │ │ ├── FileUtil.groovy │ │ ├── PackagingUtils.java │ │ ├── Reflect.java │ │ └── ZipUtil.groovy │ ├── java/ │ │ └── com/ │ │ └── didi/ │ │ └── virtualapk/ │ │ ├── databinding/ │ │ │ └── annotationprocessor/ │ │ │ └── ProcessDataBinding.java │ │ └── utils/ │ │ └── Log.java │ └── resources/ │ └── META-INF/ │ ├── gradle-plugins/ │ │ ├── com.didi.virtualapk.host.properties │ │ └── com.didi.virtualapk.plugin.properties │ └── services/ │ └── javax.annotation.processing.Processor └── upload.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Generated by MacOSX .DS_Store # Generated by VIM .*.swp # Generated by IntelliJ .idea/ *.iml /*/*.iml /*/*/*.iml # PluginDemo PluginDemo/.idea/ PluginDemo/.gradle/ PluginDemo/build/ PluginDemo/*.iml PluginDemo/local.properties PluginDemo/host/ # Generated by Gradle .gradle build/ # Generated by Eclipse .metadata .settings .project .classpath # Files generated by Maven target/ pom.xml.tag pom.xml.releaseBackup pom.xml.versionsBackup pom.xml.next release.properties dependency-reduced-pom.xml buildNumber.properties local.properties buildSrc ================================================ FILE: AndroidStub/README.md ================================================ # Android Framework Stub 该库主要是针对在 app 中无法直接调用 Android Framework 中很多隐藏的 API 而创造的一系列 stub 类用来欺骗编译器,从而避免了使用反射去调用造成性能损失 ================================================ FILE: AndroidStub/build.gradle ================================================ group = 'com.didichuxing.foundation' version = '0.0.5' apply plugin: 'com.android.library' android { compileSdkVersion VERSION_COMPILE_SDK buildToolsVersion VERSION_BUILD_TOOLS defaultConfig { minSdkVersion VERSION_MIN_SDK targetSdkVersion VERSION_TARGET_SDK versionCode 1 versionName "1.0" } compileOptions { sourceCompatibility SOURCE_COMPATIBILITY } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { abortOnError false } } android.libraryVariants.all { variant -> def jarTask = project.tasks.create("jar${variant.name.capitalize()}", Jar) jarTask.excludes = [ 'android/BuildConfig.class', 'android/R.class' ] jarTask.dependsOn variant.javaCompile jarTask.from variant.javaCompile.destinationDir artifacts.add('archives', jarTask) } apply plugin: 'maven' uploadArchives { repositories { mavenDeployer { pom.project { groupId project.group artifactId 'android-framework' version project.version description project.description ?: '' packaging 'jar' } repository(url: MAVEN_RELEASES_REPOSITORY_URL) { authentication(userName: MAVEN_USERNAME, password: MAVEN_PASSWORD) } snapshotRepository(url: MAVEN_SNAPSHOTS_REPOSITORY_URL) { authentication(userName: MAVEN_USERNAME, password: MAVEN_PASSWORD) } } } } dependencies { 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' } ================================================ FILE: AndroidStub/gradle.properties ================================================ MAVEN_RELEASES_REPOSITORY_URL= MAVEN_SNAPSHOTS_REPOSITORY_URL= MAVEN_USERNAME=test MAVEN_PASSWORD=test ================================================ FILE: AndroidStub/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/johnson/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: AndroidStub/src/main/AndroidManifest.xml ================================================ ================================================ FILE: AndroidStub/src/main/aidl/android/app/IActivityManager.aidl ================================================ package android.app; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.content.ComponentName; import android.content.Intent; import android.content.IIntentSender; import android.os.IBinder; import android.os.IInterface; /** * @author johnsonlee */ interface IActivityManager { ComponentName startService(in IApplicationThread caller, in Intent service, in String resolvedType, in String callingPackage, in int userId); int stopService(in IApplicationThread caller, in Intent service, in String resolvedType, in int userId); boolean stopServiceToken(in ComponentName className, in IBinder token, in int startId); void setServiceForeground(in ComponentName className, in IBinder token, in int id, in Notification notification, in boolean keepNotification); int bindService(in IApplicationThread caller, in IBinder token, in Intent service, in String resolvedType, in IServiceConnection connection, in int flags, in String callingPackage, in int userId); boolean unbindService(in IServiceConnection connection); void publishService(in IBinder token, in Intent intent, in IBinder service); void unbindFinished(in IBinder token, in Intent service, in boolean doRebind); IIntentSender getIntentSender(in int type, in String packageName, in IBinder token, in String resultWho, int requestCode, in Intent[] intents, in String[] resolvedTypes, in int flags, in Bundle options, in int userId); void cancelIntentSender(in IIntentSender sender); String getPackageForIntentSender(in IIntentSender sender); int getUidForIntentSender(in IIntentSender sender); } ================================================ FILE: AndroidStub/src/main/aidl/android/app/IApplicationThread.aidl ================================================ package android.app; interface IApplicationThread { } ================================================ FILE: AndroidStub/src/main/aidl/android/app/INotificationManager.aidl ================================================ /* * Copyright 2007, 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.app; import android.app.Notification; import android.content.ComponentName; import android.content.Intent; import android.net.Uri; import android.os.Bundle; interface INotificationManager { void cancelAllNotifications(String pkg, int userId); void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, in Notification notification, inout int[] idReceived, int userId); void cancelNotificationWithTag(String pkg, String tag, int id, int userId); void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled); boolean areNotificationsEnabledForPackage(String pkg, int uid); boolean areNotificationsEnabled(String pkg); } ================================================ FILE: AndroidStub/src/main/aidl/android/app/IServiceConnection.aidl ================================================ /* //device/java/android/android/app/IServiceConnection.aidl ** ** Copyright 2007, 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.app; import android.content.ComponentName; /** @hide */ oneway interface IServiceConnection { // void connected(in ComponentName name, IBinder service); /** Added in Android O */ void connected(in ComponentName name, IBinder service, boolean dead); } ================================================ FILE: AndroidStub/src/main/aidl/android/content/IIntentReceiver.aidl ================================================ /* * Copyright (C) 2006 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.content; import android.content.Intent; import android.os.Bundle; /** * System private API for dispatching intent broadcasts. This is given to the * activity manager as part of registering for an intent broadcasts, and is * called when it receives intents. */ oneway interface IIntentReceiver { void performReceive(in Intent intent, int resultCode, String data, in Bundle extras, boolean ordered, boolean sticky, int sendingUser); } ================================================ FILE: AndroidStub/src/main/aidl/android/content/IIntentSender.aidl ================================================ /* * Copyright (C) 2006 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.content; import android.content.IIntentReceiver; import android.content.Intent; import android.os.Bundle; oneway interface IIntentSender { void send(int code, in Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, in Bundle options); } ================================================ FILE: AndroidStub/src/main/java/android/app/ActivityManagerNative.java ================================================ package android.app; import android.content.Intent; import android.os.Binder; import android.os.IBinder; /** * @author johnsonlee */ public abstract class ActivityManagerNative extends Binder implements IActivityManager { public static IActivityManager getDefault() { throw new RuntimeException("Stub!"); } public static boolean isSystemReady() { throw new RuntimeException("Stub!"); } public static void broadcastStickyIntent(final Intent intent, final String permission, final int userId) { throw new RuntimeException("Stub!"); } static public IActivityManager asInterface(IBinder obj) { throw new RuntimeException("Stub!"); } public ActivityManagerNative() { throw new RuntimeException("Stub!"); } } ================================================ FILE: AndroidStub/src/main/java/android/app/ActivityThread.java ================================================ package android.app; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Handler; import android.os.IBinder; import android.os.Looper; /** * @author johnsonlee */ public final class ActivityThread { public static ActivityThread currentActivityThread() { throw new RuntimeException("Stub!"); } public static boolean isSystem() { throw new RuntimeException("Stub!"); } public static String currentOpPackageName() { throw new RuntimeException("Stub!"); } public static String currentPackageName() { throw new RuntimeException("Stub!"); } public static String currentProcessName() { throw new RuntimeException("Stub!"); } public static Application currentApplication() { throw new RuntimeException("Stub!"); } public ApplicationThread getApplicationThread() { throw new RuntimeException("Stub!"); } public Instrumentation getInstrumentation() { throw new RuntimeException("Stub!"); } public Looper getLooper() { throw new RuntimeException("Stub!"); } public Application getApplication() { throw new RuntimeException("Stub!"); } public String getProcessName() { throw new RuntimeException("Stub!"); } public final ActivityInfo resolveActivityInfo(final Intent intent) { throw new RuntimeException("Stub!"); } public final Activity getActivity(final IBinder token) { throw new RuntimeException("Stub!"); } final Handler getHandler() { throw new RuntimeException("Stub!"); } private class ApplicationThread extends ApplicationThreadNative { } } ================================================ FILE: AndroidStub/src/main/java/android/app/ApplicationThreadNative.java ================================================ package android.app; import android.os.Binder; import android.os.IBinder; /** * @author johnsonlee */ public abstract class ApplicationThreadNative extends Binder implements IApplicationThread { @Override public IBinder asBinder() { throw new RuntimeException("Stub!"); } } ================================================ FILE: AndroidStub/src/main/java/android/app/Instrumentation.java ================================================ 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!"); } } } ================================================ FILE: AndroidStub/src/main/java/android/app/LoadedApk.java ================================================ package android.app; import android.content.BroadcastReceiver; import android.content.Context; import android.content.IIntentReceiver; import android.content.pm.ApplicationInfo; import android.content.res.AssetManager; import android.content.res.Resources; import android.os.Handler; import java.io.File; /** * Created by johnson on 24/8/16. */ public final class LoadedApk { public String getPackageName() { throw new RuntimeException("Stub!"); } public ApplicationInfo getApplicationInfo() { throw new RuntimeException("Stub!"); } public ClassLoader getClassLoader() { throw new RuntimeException("Stub!"); } public String getAppDir() { throw new RuntimeException("Stub!"); } public String getLibDir() { throw new RuntimeException("Stub!"); } public String getResDir() { throw new RuntimeException("Stub!"); } public String[] getSplitAppDirs() { throw new RuntimeException("Stub!"); } public String[] getSplitResDirs() { throw new RuntimeException("Stub!"); } public String[] getOverlayDirs() { throw new RuntimeException("Stub!"); } public String getDataDir() { throw new RuntimeException("Stub!"); } public File getDataDirFile() { throw new RuntimeException("Stub!"); } public AssetManager getAssets(final ActivityThread mainThread) { throw new RuntimeException("Stub!"); } public Resources getResources(final ActivityThread mainThread) { throw new RuntimeException("Stub!"); } public IIntentReceiver getReceiverDispatcher(final BroadcastReceiver r, final Context context, final Handler handler, final Instrumentation instrumentation, final boolean registered) { throw new RuntimeException("Stub!"); } public IIntentReceiver forgetReceiverDispatcher(final Context context, final BroadcastReceiver r) { throw new RuntimeException("Stub!"); } } ================================================ FILE: AndroidStub/src/main/java/android/app/ResourcesManager.java ================================================ 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!"); } } ================================================ FILE: AndroidStub/src/main/java/android/content/ContentResolver.java ================================================ 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!"); } } ================================================ FILE: AndroidStub/src/main/java/android/content/IContentProvider.java ================================================ /* * Copyright (C) 2006 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.content; import android.os.IInterface; /** * The ipc interface to talk to a content provider. * @hide */ public interface IContentProvider extends IInterface { } ================================================ FILE: AndroidStub/src/main/java/android/content/pm/ManifestDigest.java ================================================ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; import java.util.jar.Attributes; /** * @author johnsonlee */ public class ManifestDigest implements Parcelable { ManifestDigest(final byte[] digest) { throw new RuntimeException("Stub!"); } private ManifestDigest(final Parcel source) { throw new RuntimeException("Stub!"); } static ManifestDigest fromAttributes(final Attributes attributes) { throw new RuntimeException("Stub!"); } @Override public int describeContents() { throw new RuntimeException("Stub!"); } @Override public boolean equals(Object o) { throw new RuntimeException("Stub!"); } @Override public int hashCode() { throw new RuntimeException("Stub!"); } @Override public String toString() { throw new RuntimeException("Stub!"); } @Override public void writeToParcel(final Parcel dest, final int flags) { throw new RuntimeException("Stub!"); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public ManifestDigest createFromParcel(Parcel source) { return new ManifestDigest(source); } public ManifestDigest[] newArray(int size) { return new ManifestDigest[size]; } }; } ================================================ FILE: AndroidStub/src/main/java/android/content/pm/PackageParser.java ================================================ package android.content.pm; import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.IntentFilter; import android.content.res.TypedArray; import android.os.Bundle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.DisplayMetrics; import java.io.File; import java.io.PrintWriter; import java.security.PublicKey; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * @author johnsonlee */ public class PackageParser { public final static int PARSE_IS_SYSTEM = 1 << 0; public final static int PARSE_CHATTY = 1 << 1; public final static int PARSE_MUST_BE_APK = 1 << 2; public final static int PARSE_IGNORE_PROCESSES = 1 << 3; public final static int PARSE_FORWARD_LOCK = 1 << 4; public final static int PARSE_EXTERNAL_STORAGE = 1 << 5; public final static int PARSE_IS_SYSTEM_DIR = 1 << 6; public final static int PARSE_IS_PRIVILEGED = 1 << 7; public final static int PARSE_COLLECT_CERTIFICATES = 1 << 8; public final static int PARSE_TRUSTED_OVERLAY = 1 << 9; public static class NewPermissionInfo { public final String name; public final int sdkVersion; public final int fileVersion; public NewPermissionInfo(String name, int sdkVersion, int fileVersion) { throw new RuntimeException("Stub!"); } } public static class SplitPermissionInfo { public final String rootPerm; public final String[] newPerms; public final int targetSdk; public SplitPermissionInfo(String rootPerm, String[] newPerms, int targetSdk) { throw new RuntimeException("Stub!"); } } public static final PackageParser.NewPermissionInfo NEW_PERMISSIONS[] = new PackageParser.NewPermissionInfo[]{ new PackageParser.NewPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.os.Build.VERSION_CODES.DONUT, 0), new PackageParser.NewPermissionInfo(android.Manifest.permission.READ_PHONE_STATE, android.os.Build.VERSION_CODES.DONUT, 0) }; static class ParsePackageItemArgs { final Package owner; final String[] outError; final int nameRes; final int labelRes; final int iconRes; final int logoRes; final int bannerRes; String tag; TypedArray sa; ParsePackageItemArgs(final Package owner, final String[] outError, final int nameRes, final int labelRes, final int iconRes, final int logoRes, final int bannerRes) { throw new RuntimeException("Stub!"); } } static class ParseComponentArgs extends ParsePackageItemArgs { final String[] sepProcesses; final int processRes; final int descriptionRes; final int enabledRes; int flags; ParseComponentArgs(final Package owner, final String[] outError, final int nameRes, final int labelRes, final int iconRes, final int logoRes, final int bannerRes, final String[] sepProcesses, final int processRes, final int descriptionRes, final int enabledRes) { super(owner, outError, nameRes, labelRes, iconRes, logoRes, bannerRes); throw new RuntimeException("Stub!"); } } public static class PackageLite { public final String packageName; public final int versionCode; public final int installLocation; public final VerifierInfo[] verifiers; /** Names of any split APKs, ordered by parsed splitName */ public final String[] splitNames; /** * Path where this package was found on disk. For monolithic packages * this is path to single base APK file; for cluster packages this is * path to the cluster directory. */ public final String codePath; /** Path of base APK */ public final String baseCodePath; /** Paths of any split APKs, ordered by parsed splitName */ public final String[] splitCodePaths; /** Revision code of base APK */ public final int baseRevisionCode; /** Revision codes of any split APKs, ordered by parsed splitName */ public final int[] splitRevisionCodes; public final boolean coreApp; public final boolean multiArch; public final boolean extractNativeLibs; public PackageLite(final String codePath, final ApkLite baseApk, final String[] splitNames, final String[] splitCodePaths, final int[] splitRevisionCodes) { throw new RuntimeException("Stub!"); } public List getAllCodePaths() { throw new RuntimeException("Stub!"); } } public static class ApkLite { public final String codePath; public final String packageName; public final String splitName; public final int versionCode; public final int revisionCode; public final int installLocation; public final VerifierInfo[] verifiers; public final Signature[] signatures; public final boolean coreApp; public final boolean multiArch; public final boolean extractNativeLibs; public ApkLite(final String codePath, final String packageName, final String splitName, final int versionCode, final int revisionCode, final int installLocation, final List verifiers, final Signature[] signatures, final boolean coreApp, final boolean multiArch, final boolean extractNativeLibs) { throw new RuntimeException("Stub!"); } } /** * For Android 5.0+ */ public PackageParser() { throw new RuntimeException("Stub!"); } public PackageParser(final String archiveSourcePath) { throw new RuntimeException("Stub!"); } public void setSeparateProcesses(final String[] procs) { throw new RuntimeException("Stub!"); } public void setOnlyCoreApps(final boolean onlyCoreApps) { throw new RuntimeException("Stub!"); } public void setDisplayMetrics(final DisplayMetrics metrics) { throw new RuntimeException("Stub!"); } public static final boolean isApkFile(final File file) { throw new RuntimeException("Stub!"); } public static PackageInfo generatePackageInfo(final PackageParser.Package p, final int gids[], final int flags, final long firstInstallTime, final long lastUpdateTime, final Set grantedPermissions, final PackageUserState state) { throw new RuntimeException("Stub!"); } public static boolean isAvailable(final PackageUserState state) { throw new RuntimeException("Stub!"); } public static PackageInfo generatePackageInfo(final PackageParser.Package p, final int gids[], final int flags, final long firstInstallTime, final long lastUpdateTime, final Set grantedPermissions, final PackageUserState state, final int userId) { throw new RuntimeException("Stub!"); } /** * Parse only lightweight details about the package at the given location. * Automatically detects if the package is a monolithic style (single APK * file) or cluster style (directory of APKs). *

* This performs sanity checking on cluster style packages, such as * requiring identical package name and version codes, a single base APK, * and unique split names. * * @see PackageParser#parsePackage(File, int) */ public static PackageLite parsePackageLite(final File packageFile, final int flags) throws PackageParserException { throw new RuntimeException("Stub!"); } /** * Parse the package at the given location. Automatically detects if the * package is a monolithic style (single APK file) or cluster style * (directory of APKs). *

* This performs sanity checking on cluster style packages, such as * requiring identical package name and version codes, a single base APK, * and unique split names. *

* Note that this does not perform signature verification; that * must be done separately in {@link #collectCertificates(Package, int)}. * * @see #parsePackageLite(File, int) * @since Android 5.0+ */ public Package parsePackage(final File packageFile, final int flags) throws PackageParserException { throw new RuntimeException("Stub!"); } /** * * @param sourceFile * @param destCodePath * @param metrics * @param flags * @return * @since Android 2.3+ */ public Package parsePackage(final File sourceFile, final String destCodePath, final DisplayMetrics metrics, final int flags) { throw new RuntimeException("Stub!"); } public void collectManifestDigest(final Package pkg) throws PackageParserException { throw new RuntimeException("Stub!"); } public void collectCertificates(final Package pkg, final int flags) throws PackageParserException { throw new RuntimeException("Stub!"); } /** * Utility method that retrieves lightweight details about a single APK * file, including package name, split name, and install location. * * @param apkFile path to a single APK * @param flags optional parse flags, such as * {@link #PARSE_COLLECT_CERTIFICATES} */ public static ApkLite parseApkLite(final File apkFile, final int flags) throws PackageParserException { throw new RuntimeException("Stub!"); } public static ApplicationInfo generateApplicationInfo(final Package p, final int flags, final PackageUserState state) { throw new RuntimeException("Stub!"); } public static ApplicationInfo generateApplicationInfo(final Package p, final int flags, final PackageUserState state, final int userId) { throw new RuntimeException("Stub!"); } public static ApplicationInfo generateApplicationInfo(final ApplicationInfo ai, final int flags, final PackageUserState state, final int userId) { throw new RuntimeException("Stub!"); } public static final PermissionInfo generatePermissionInfo(final Permission p, final int flags) { throw new RuntimeException("Stub!"); } public static final PermissionGroupInfo generatePermissionGroupInfo(final PermissionGroup pg, final int flags) { throw new RuntimeException("Stub!"); } public static final InstrumentationInfo generateInstrumentationInfo(final Instrumentation i, final int flags) { throw new RuntimeException("Stub!"); } public static final ServiceInfo generateServiceInfo(final Service s, final int flags, final PackageUserState state, final int userId) { throw new RuntimeException("Stub!"); } public static final ProviderInfo generateProviderInfo(final Provider p, final int flags, final PackageUserState state, final int userId) { throw new RuntimeException("Stub!"); } public static final ActivityInfo generateActivityInfo(final Activity a, final int flags, final PackageUserState state, final int userId) { throw new RuntimeException("Stub!"); } public static final ActivityInfo generateActivityInfo(final ActivityInfo ai, final int flags, final PackageUserState state, final int userId) { throw new RuntimeException("Stub!"); } /** * Representation of a full package parsed from APK files on disk. A package * consists of a single base APK, and zero or more split APKs. */ public final static class Package { public String packageName; /** Names of any split APKs, ordered by parsed splitName */ public String[] splitNames; // TODO: work towards making these paths invariant public String volumeUuid; /** * Path where this package was found on disk. For monolithic packages * this is path to single base APK file; for cluster packages this is * path to the cluster directory. */ public String codePath; /** Path of base APK */ public String baseCodePath; /** Paths of any split APKs, ordered by parsed splitName */ public String[] splitCodePaths; /** Revision code of base APK */ public int baseRevisionCode; /** Revision codes of any split APKs, ordered by parsed splitName */ public int[] splitRevisionCodes; /** Flags of any split APKs; ordered by parsed splitName */ public int[] splitFlags; /** * Private flags of any split APKs; ordered by parsed splitName. * * {@hide} */ public int[] splitPrivateFlags; public boolean baseHardwareAccelerated; // For now we only support one application per package. public final ApplicationInfo applicationInfo = new ApplicationInfo(); public final ArrayList permissions = new ArrayList(0); public final ArrayList permissionGroups = new ArrayList(0); public final ArrayList activities = new ArrayList(0); public final ArrayList receivers = new ArrayList(0); public final ArrayList providers = new ArrayList(0); public final ArrayList services = new ArrayList(0); public final ArrayList instrumentation = new ArrayList(0); public final ArrayList requestedPermissions = new ArrayList(); public ArrayList protectedBroadcasts; public ArrayList libraryNames = null; public ArrayList usesLibraries = null; public ArrayList usesOptionalLibraries = null; public String[] usesLibraryFiles = null; public ArrayList preferredActivityFilters = null; public ArrayList mOriginalPackages = null; public String mRealPackage = null; public ArrayList mAdoptPermissions = null; // We store the application meta-data independently to avoid multiple unwanted references public Bundle mAppMetaData = null; // The version code declared for this package. public int mVersionCode; // The version name declared for this package. public String mVersionName; // The shared user id that this package wants to use. public String mSharedUserId; // The shared user label that this package wants to use. public int mSharedUserLabel; // 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 // preferred up order. public int mPreferredOrder = 0; // For use by package manager to keep track of where it needs to do dexopt. // public final ArraySet mDexOptPerformed = new ArraySet<>(4); // For use by package manager to keep track of when a package was last used. public long mLastPackageUsageTimeInMills; // // User set enabled state. // public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; // // // Whether the package has been stopped. // public boolean mSetStopped = false; // Additional data supplied by callers. public Object mExtras; // Applications hardware preferences public ArrayList configPreferences = null; // Applications requested features public ArrayList reqFeatures = null; // Applications requested feature groups public ArrayList featureGroups = null; public int installLocation; public boolean coreApp; /* An app that's required for all users and cannot be uninstalled for a user */ public boolean mRequiredForAllUsers; /* The restricted account authenticator type that is used by this application */ public String mRestrictedAccountType; /* The required account type without which this application will not function */ public String mRequiredAccountType; /** * Digest suitable for comparing whether this package's manifest is the * same as another. */ public ManifestDigest manifestDigest; public String mOverlayTarget; public int mOverlayPriority; public boolean mTrustedOverlay; /** * Data used to feed the KeySetManagerService */ public ArraySet mSigningKeys; public ArraySet mUpgradeKeySets; public ArrayMap> mKeySetMapping; /** * The install time abi override for this package, if any. * * TODO: This seems like a horrible place to put the abiOverride because * this isn't something the packageParser parsers. However, this fits in with * the rest of the PackageManager where package scanning randomly pushes * and prods fields out of {@code this.applicationInfo}. */ public String cpuAbiOverride; public Package(String packageName) { throw new RuntimeException("Stub!"); } public List getAllCodePaths() { throw new RuntimeException("Stub!"); } /** * Filtered set of {@link #getAllCodePaths()} that excludes * resource-only APKs. */ public List getAllCodePathsExcludingResourceOnly() { throw new RuntimeException("Stub!"); } public void setPackageName(final String newName) { throw new RuntimeException("Stub!"); } public boolean hasComponentClassName(final String name) { throw new RuntimeException("Stub!"); } public boolean isForwardLocked() { throw new RuntimeException("Stub!"); } public boolean isSystemApp() { throw new RuntimeException("Stub!"); } public boolean isPrivilegedApp() { throw new RuntimeException("Stub!"); } public boolean isUpdatedSystemApp() { throw new RuntimeException("Stub!"); } public boolean canHaveOatDir() { throw new RuntimeException("Stub!"); } @Override public String toString() { throw new RuntimeException("Stub!"); } } public static class Component { public final Package owner; public final ArrayList intents; public final String className; public Bundle metaData; ComponentName componentName; String componentShortName; public Component(final Package owner) { throw new RuntimeException("Stub!"); } public Component(final ParsePackageItemArgs args, final PackageItemInfo outInfo) { throw new RuntimeException("Stub!"); } public Component(final ParseComponentArgs args, final ComponentInfo outInfo) { throw new RuntimeException("Stub!"); } public Component(final Component clone) { throw new RuntimeException("Stub!"); } public ComponentName getComponentName() { throw new RuntimeException("Stub!"); } public void appendComponentShortName(final StringBuilder sb) { throw new RuntimeException("Stub!"); } public void printComponentShortName(final PrintWriter pw) { throw new RuntimeException("Stub!"); } public void setPackageName(final String packageName) { throw new RuntimeException("Stub!"); } } public final static class Permission extends Component { public final PermissionInfo info; public boolean tree; public PermissionGroup group; public Permission(final Package owner) { super(owner); throw new RuntimeException("Stub!"); } public Permission(final Package owner, final PermissionInfo info) { super(owner); throw new RuntimeException("Stub!"); } @Override public void setPackageName(final String packageName) { throw new RuntimeException("Stub!"); } @Override public String toString() { throw new RuntimeException("Stub!"); } } public final static class PermissionGroup extends Component { public final PermissionGroupInfo info; public PermissionGroup(final Package owner) { super(owner); throw new RuntimeException("Stub!"); } public PermissionGroup(final Package owner, final PermissionGroupInfo info) { super(owner); throw new RuntimeException("Stub!"); } @Override public void setPackageName(final String packageName) { throw new RuntimeException("Stub!"); } @Override public String toString() { throw new RuntimeException("Stub!"); } } public final static class Activity extends Component { public final ActivityInfo info; public Activity(final ParseComponentArgs args, final ActivityInfo info) { super(args, info); throw new RuntimeException("Stub!"); } @Override public void setPackageName(final String packageName) { throw new RuntimeException("Stub!"); } @Override public String toString() { throw new RuntimeException("Stub!"); } } public final static class Service extends Component { public final ServiceInfo info; public Service(final ParseComponentArgs args, final ServiceInfo info) { super(args, info); throw new RuntimeException("Stub!"); } @Override public void setPackageName(final String packageName) { throw new RuntimeException("Stub!"); } @Override public String toString() { throw new RuntimeException("Stub!"); } } public final static class Provider extends Component { public final ProviderInfo info; public boolean syncable; public Provider(final ParseComponentArgs args, final ProviderInfo info) { super(args, info); throw new RuntimeException("Stub!"); } public Provider(final Provider existingProvider) { super(existingProvider); throw new RuntimeException("Stub!"); } @Override public void setPackageName(final String packageName) { throw new RuntimeException("Stub!"); } @Override public String toString() { throw new RuntimeException("Stub!"); } } public final static class Instrumentation extends Component { public final InstrumentationInfo info; public Instrumentation(final ParsePackageItemArgs args, final InstrumentationInfo info) { super(args, info); throw new RuntimeException("Stub!"); } @Override public void setPackageName(final String packageName) { throw new RuntimeException("Stub!"); } @Override public String toString() { throw new RuntimeException("Stub!"); } } @SuppressLint("ParcelCreator") public static class IntentInfo extends IntentFilter { public boolean hasDefault; public int labelRes; public CharSequence nonLocalizedLabel; public int icon; public int logo; public int banner; public int preferred; } @SuppressLint("ParcelCreator") public final static class ActivityIntentInfo extends IntentInfo { public final Activity activity; public ActivityIntentInfo(final Activity activity) { throw new RuntimeException("Stub!"); } @Override public String toString() { throw new RuntimeException("Stub!"); } } @SuppressLint("ParcelCreator") public final static class ServiceIntentInfo extends IntentInfo { public final Service service; public ServiceIntentInfo(final Service service) { throw new RuntimeException("Stub!"); } @Override public String toString() { throw new RuntimeException("Stub!"); } } @SuppressLint("ParcelCreator") public static final class ProviderIntentInfo extends IntentInfo { public final Provider provider; public ProviderIntentInfo(final Provider provider) { throw new RuntimeException("Stub!"); } @Override public String toString() { throw new RuntimeException("Stub!"); } } public static class PackageParserException extends Exception { public PackageParserException(int error, String detailMessage) { super(detailMessage); throw new RuntimeException("Stub!"); } public PackageParserException(int error, String detailMessage, Throwable throwable) { super(detailMessage, throwable); throw new RuntimeException("Stub!"); } } public static class SigningDetails { public Signature[] signatures; } } ================================================ FILE: AndroidStub/src/main/java/android/content/pm/PackageUserState.java ================================================ package android.content.pm; import android.util.ArraySet; /** * @author johnsonlee */ public class PackageUserState { public boolean stopped; public boolean notLaunched; public boolean installed; public boolean hidden; // Is the app restricted by owner / admin public int enabled; public boolean blockUninstall; public String lastDisableAppCaller; public ArraySet disabledComponents; public ArraySet enabledComponents; public int domainVerificationStatus; public int appLinkGeneration; public PackageUserState() { throw new RuntimeException("Stub!"); } public PackageUserState(final PackageUserState o) { throw new RuntimeException("Stub!"); } } ================================================ FILE: AndroidStub/src/main/java/android/content/pm/VerifierInfo.java ================================================ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; import java.security.PublicKey; /** * @author johnsonlee */ public class VerifierInfo implements Parcelable{ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public VerifierInfo createFromParcel(final Parcel source) { return new VerifierInfo(source); } public VerifierInfo[] newArray(final int size) { return new VerifierInfo[size]; } }; public VerifierInfo(final String packageName, final PublicKey publicKey) { throw new RuntimeException("Stub!"); } private VerifierInfo(final Parcel source) { throw new RuntimeException("Stub!"); } @Override public int describeContents() { throw new RuntimeException("Stub!"); } @Override public void writeToParcel(final Parcel dest, final int flags) { throw new RuntimeException("Stub!"); } } ================================================ FILE: AndroidStub/src/main/java/android/content/res/CompatibilityInfo.java ================================================ 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!"); } }; } ================================================ FILE: AndroidStub/src/main/java/android/content/res/Resources.java ================================================ 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 { } } ================================================ FILE: AndroidStub/src/main/java/android/content/res/ResourcesImpl.java ================================================ package android.content.res; /** * Created by qiaopu on 2018/5/18. */ public class ResourcesImpl { } ================================================ FILE: AndroidStub/src/main/java/android/content/res/ResourcesKey.java ================================================ 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!"); } } ================================================ FILE: AndroidStub/src/main/java/android/databinding/DataBinderMapper.java ================================================ /* * 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; } ================================================ FILE: AndroidStub/src/main/java/android/databinding/DataBindingComponent.java ================================================ /* * 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 { } ================================================ FILE: AndroidStub/src/main/java/android/os/ServiceManager.java ================================================ package android.os; import java.util.HashMap; import java.util.Map; /** * @author johnsonlee */ public final class ServiceManager { private static HashMap sCache = new HashMap(); public static IBinder getService(final String name) { throw new RuntimeException("Stub!"); } public static void addService(final String name, final IBinder service) { throw new RuntimeException("Stub!"); } public static void addService(final String name, final IBinder service, final boolean allowIsolated) { throw new RuntimeException("Stub!"); } public static IBinder checkService(final String name) { throw new RuntimeException("Stub!"); } public static String[] listServices() throws RemoteException { throw new RuntimeException("Stub!"); } public static void initServiceCache(final Map cache) { throw new RuntimeException("Stub!"); } } ================================================ FILE: AndroidStub/src/main/java/android/os/SystemProperties.java ================================================ package android.os; /** * @author johnsonlee */ public class SystemProperties { public static String get(final String key) { throw new RuntimeException("Stub!"); } public static String get(final String key, final String def) { throw new RuntimeException("Stub!"); } public static int getInt(final String key, final int def) { throw new RuntimeException("Stub!"); } public static long getLong(final String key, final long def) { throw new RuntimeException("Stub!"); } public static boolean getBoolean(final String key, final boolean def) { throw new RuntimeException("Stub!"); } public static void set(final String key, final String val) { throw new RuntimeException("Stub!"); } public static void addChangeCallback(final Runnable callback) { throw new RuntimeException("Stub!"); } } ================================================ FILE: AndroidStub/src/main/java/android/util/Singleton.java ================================================ package android.util; /** * @author johnsonlee */ public abstract class Singleton { public Singleton() { throw new RuntimeException("Stub!"); } protected abstract T create(); public T get() { throw new RuntimeException("Stub!"); } } ================================================ FILE: AndroidStub/src/main/java/com/android/internal/R.java ================================================ package com.android.internal; /** * @author johnsonlee */ public final class R { public static final class id { public static int action0 = 0; public static int actions = 0; public static int action_divider = 0; public static int big_picture = 0; public static int big_text = 0; public static int big_text2 = 0; public static int chronometer = 0; public static int icon = 0; public static int inbox_text0 = 0; public static int inbox_text1 = 0; public static int inbox_text2 = 0; public static int inbox_text3 = 0; public static int inbox_text4 = 0; public static int inbox_text5 = 0; public static int inbox_text6 = 0; public static int inbox_end_pad = 0; public static int info = 0; public static int line1 = 0; public static int line3 = 0; public static int overflow_divider = 0; public static int progress = 0; public static int title = 0; public static int text = 0; public static int text2 = 0; public static int time = 0; public static int right_icon = 0; public static int fillInIntent = 0; public static int status_bar_latest_event_content = 0; } } ================================================ FILE: CONTRIBUTING.md ================================================ # Contribution Guideline Thanks for considering to contribute this project. All issues and pull requests are highly appreciated. ## Pull Requests Before sending pull request to this project, please read and follow guidelines below. 1. Branch: We only accept pull request on `dev` branch. 2. Coding style: Follow the coding style used in VirtualAPK. 3. Commit message: Use English and be aware of your spell. 4. Test: Make sure to test your code. Add device mode, API version, related log, screenshots and other related information in your pull request if possible. NOTE: We assume all your contribution can be licensed under the [Apache License 2.0](https://github.com/didi/VirtualAPK/blob/master/LICENSE). ## Issues We love clearly described issues. :) Following information can help us to resolve the issue faster. * Device mode and hardware information. * API version. * Logs. * Screenshots. * Steps to reproduce the issue. ================================================ FILE: CoreLibrary/.gitignore ================================================ /build ================================================ FILE: CoreLibrary/build.gradle ================================================ 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 { compileSdkVersion VERSION_COMPILE_SDK buildToolsVersion VERSION_BUILD_TOOLS defaultConfig { minSdkVersion VERSION_MIN_SDK targetSdkVersion VERSION_TARGET_SDK versionCode 1 versionName "1.0.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility SOURCE_COMPATIBILITY } lintOptions { abortOnError false } } repositories { mavenCentral() jcenter() } final String projectAndroidStub = ':AndroidStub' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) 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' ================================================ FILE: CoreLibrary/gradle.properties ================================================ ================================================ FILE: CoreLibrary/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/didi/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: CoreLibrary/src/androidTest/java/com/didi/virtualapk/core/ApplicationTest.java ================================================ package com.didi.virtualapk.core; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: CoreLibrary/src/main/AndroidManifest.xml ================================================ ================================================ FILE: CoreLibrary/src/main/java/android/content/ContentResolverWrapper.java ================================================ 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); } } ================================================ FILE: CoreLibrary/src/main/java/android/databinding/DataBinderMapperProxy.java ================================================ 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; } } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/PluginManager.java ================================================ /* * 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.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.ActivityThread; import android.app.Application; import android.app.IActivityManager; import android.app.Instrumentation; import android.content.ComponentName; 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.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.internal.utils.PluginUtil; import com.didi.virtualapk.utils.Reflector; import com.didi.virtualapk.utils.RunUtil; import java.io.File; 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; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Created by renyugang on 16/8/9. */ public class PluginManager { public static final String TAG = Constants.TAG_PREFIX + "PluginManager"; private static volatile PluginManager sInstance = null; // Context of host app protected final Context mContext; protected final Application mApplication; protected ComponentsHandler mComponentsHandler; protected final Map mPlugins = new ConcurrentHashMap<>(); protected final List mCallbacks = new ArrayList<>(); 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 = createInstance(base); } } } return sInstance; } 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); } return new PluginManager(context); } protected PluginManager(Context context) { if (context instanceof Application) { this.mApplication = (Application) context; this.mContext = mApplication.getBaseContext(); } else { 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() { RunUtil.getThreadPool().execute(new Runnable() { @Override public void run() { 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. */ protected void hookSystemServices() { try { 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 Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy); if (defaultSingleton.get() == activityManagerProxy) { this.mActivityManager = activityManagerProxy; Log.d(TAG, "hookSystemServices succeed : " + mActivityManager); } } catch (Exception e) { Log.w(TAG, e); } } protected void hookInstrumentationAndHandler() { try { 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) { Log.w(TAG, e); } } protected void hookIContentProviderAsNeeded() { Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext)); mContext.getContentResolver().call(uri, "wakeup", null, null); try { Field authority = null; 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(); Object val = entry.getValue(); String auth; if (key instanceof String) { auth = (String) key; } else { if (authority == null) { authority = key.getClass().getDeclaredField("authority"); authority.setAccessible(true); } auth = (String) authority.get(key); } if (auth.equals(RemoteContentProvider.getAuthority(mContext))) { if (provider == null) { provider = val.getClass().getDeclaredField("mProvider"); provider.setAccessible(true); } IContentProvider rawProvider = (IContentProvider) provider.get(val); IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider); mIContentProvider = proxy; Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider); break; } } } catch (Exception e) { Log.w(TAG, e); } } /** * load a plugin into memory, then invoke it's Application. * @param apk the file of plugin, should end with .apk * @throws Exception */ public void loadPlugin(File apk) throws Exception { if (null == apk) { throw new IllegalArgumentException("error : apk is null."); } if (!apk.exists()) { // throw the FileNotFoundException by opening a stream. InputStream in = new FileInputStream(apk); in.close(); } 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) { return getLoadedPlugin(PluginUtil.getComponent(intent)); } public LoadedPlugin getLoadedPlugin(ComponentName component) { if (component == null) { return null; } return this.getLoadedPlugin(component.getPackageName()); } public LoadedPlugin getLoadedPlugin(String packageName) { return this.mPlugins.get(packageName); } public List getAllLoadedPlugins() { List list = new ArrayList<>(); list.addAll(mPlugins.values()); return list; } public Context getHostContext() { return this.mContext; } public VAInstrumentation getInstrumentation() { return this.mInstrumentation; } public IActivityManager getActivityManager() { return this.mActivityManager; } public synchronized IContentProvider getIContentProvider() { if (mIContentProvider == null) { hookIContentProviderAsNeeded(); } return mIContentProvider; } public ComponentsHandler getComponentsHandler() { return mComponentsHandler; } public ResolveInfo resolveActivity(Intent intent) { return this.resolveActivity(intent, 0); } public ResolveInfo resolveActivity(Intent intent, int flags) { for (LoadedPlugin plugin : this.mPlugins.values()) { ResolveInfo resolveInfo = plugin.resolveActivity(intent, flags); if (null != resolveInfo) { return resolveInfo; } } return null; } public ResolveInfo resolveService(Intent intent, int flags) { for (LoadedPlugin plugin : this.mPlugins.values()) { ResolveInfo resolveInfo = plugin.resolveService(intent, flags); if (null != resolveInfo) { return resolveInfo; } } return null; } public ProviderInfo resolveContentProvider(String name, int flags) { for (LoadedPlugin plugin : this.mPlugins.values()) { ProviderInfo providerInfo = plugin.resolveContentProvider(name, flags); if (null != providerInfo) { return providerInfo; } } return null; } /** * used in PluginPackageManager, do not invoke it from outside. */ @Deprecated public List queryIntentActivities(Intent intent, int flags) { List resolveInfos = new ArrayList(); for (LoadedPlugin plugin : this.mPlugins.values()) { List result = plugin.queryIntentActivities(intent, flags); if (null != result && result.size() > 0) { resolveInfos.addAll(result); } } return resolveInfos; } /** * used in PluginPackageManager, do not invoke it from outside. */ @Deprecated public List queryIntentServices(Intent intent, int flags) { List resolveInfos = new ArrayList(); for (LoadedPlugin plugin : this.mPlugins.values()) { List result = plugin.queryIntentServices(intent, flags); if (null != result && result.size() > 0) { resolveInfos.addAll(result); } } return resolveInfos; } /** * used in PluginPackageManager, do not invoke it from outside. */ @Deprecated public List queryBroadcastReceivers(Intent intent, int flags) { List resolveInfos = new ArrayList(); for (LoadedPlugin plugin : this.mPlugins.values()) { List result = plugin.queryBroadcastReceivers(intent, flags); if (null != result && result.size() > 0) { resolveInfos.addAll(result); } } return resolveInfos; } public interface Callback { void onAddedLoadedPlugin(LoadedPlugin plugin); } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/delegate/ActivityManagerProxy.java ================================================ /* * 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.delegate; import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.app.IApplicationThread; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.DeadObjectException; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import com.didi.virtualapk.PluginManager; import com.didi.virtualapk.internal.Constants; import com.didi.virtualapk.internal.utils.PluginUtil; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author johnsonlee */ public class ActivityManagerProxy implements InvocationHandler { 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; private PluginManager mPluginManager; private IActivityManager mActivityManager; public ActivityManagerProxy(PluginManager pluginManager, IActivityManager activityManager) { this.mPluginManager = pluginManager; this.mActivityManager = activityManager; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("startService".equals(method.getName())) { try { return startService(proxy, method, args); } catch (Throwable e) { Log.e(TAG, "Start service error", e); } } else if ("stopService".equals(method.getName())) { try { return stopService(proxy, method, args); } catch (Throwable e) { Log.e(TAG, "Stop Service error", e); } } else if ("stopServiceToken".equals(method.getName())) { try { return stopServiceToken(proxy, method, args); } catch (Throwable e) { Log.e(TAG, "Stop service token error", e); } } else if ("bindService".equals(method.getName())) { try { return bindService(proxy, method, args); } catch (Throwable e) { Log.w(TAG, e); } } else if ("unbindService".equals(method.getName())) { try { return unbindService(proxy, method, args); } catch (Throwable e) { Log.w(TAG, e); } } else if ("getIntentSender".equals(method.getName())) { try { getIntentSender(method, args); } catch (Exception e) { Log.w(TAG, e); } } else if ("overridePendingTransition".equals(method.getName())){ try { overridePendingTransition(method, args); } catch (Exception e){ Log.w(TAG, e); } } try { // sometimes system binder has problems. return method.invoke(this.mActivityManager, args); } catch (Throwable th) { Throwable c = th.getCause(); if (c != null && c instanceof DeadObjectException) { // retry connect to system binder IBinder ams = ServiceManager.getService(Context.ACTIVITY_SERVICE); if (ams != null) { IActivityManager am = ActivityManagerNative.asInterface(ams); mActivityManager = am; } } Throwable cause = th; do { if (cause instanceof RemoteException) { throw cause; } } while ((cause = cause.getCause()) != null); throw c != null ? c : th; } } 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); if (null == resolveInfo || null == resolveInfo.serviceInfo) { // is host service return method.invoke(this.mActivityManager, args); } return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE); } 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) { // is hot service return method.invoke(this.mActivityManager, args); } startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_STOP_SERVICE); return 1; } 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); if (null == resolveInfo || null == resolveInfo.serviceInfo) { // is hot service return method.invoke(this.mActivityManager, args); } startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_STOP_SERVICE); return true; } 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) { // is host service return method.invoke(this.mActivityManager, args); } Bundle bundle = new Bundle(); PluginUtil.putBinder(bundle, "sc", (IBinder) args[4]); startDelegateServiceForTarget(target, resolveInfo.serviceInfo, bundle, RemoteService.EXTRA_COMMAND_BIND_SERVICE); mPluginManager.getComponentsHandler().remberIServiceConnection((IBinder) args[4], target); return 1; } 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) { // is host service return method.invoke(this.mActivityManager, args); } ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0); startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_UNBIND_SERVICE); return true; } protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command); return mPluginManager.getHostContext().startService(wrapperIntent); } 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(); // start delegate service to run plugin service inside boolean local = PluginUtil.isLocalService(serviceInfo); Class delegate = local ? LocalService.class : RemoteService.class; Intent intent = new Intent(); intent.setClass(mPluginManager.getHostContext(), delegate); intent.putExtra(RemoteService.EXTRA_TARGET, target); intent.putExtra(RemoteService.EXTRA_COMMAND, command); intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation); if (extras != null) { intent.putExtras(extras); } return intent; } protected void getIntentSender(Method method, Object[] args) { String hostPackageName = mPluginManager.getHostContext().getPackageName(); args[1] = hostPackageName; Intent target = ((Intent[]) args[5])[0]; int intentSenderType = (int)args[0]; if (intentSenderType == INTENT_SENDER_ACTIVITY) { mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(target); mPluginManager.getComponentsHandler().markIntentIfNeeded(target); } else if (intentSenderType == INTENT_SENDER_SERVICE) { ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0); if (resolveInfo != null && resolveInfo.serviceInfo != null) { // find plugin service Intent wrapperIntent = wrapperTargetIntent(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE); ((Intent[]) args[5])[0] = wrapperIntent; } } else if (intentSenderType == INTENT_SENDER_BROADCAST) { // no action } } protected void overridePendingTransition(Method method, Object[] args) { String hostPackageName = mPluginManager.getHostContext().getPackageName(); args[1] = hostPackageName; } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/delegate/IContentProviderProxy.java ================================================ /* * 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.delegate; import android.content.Context; import android.content.IContentProvider; import android.content.pm.ProviderInfo; import android.net.Uri; import android.os.Bundle; 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; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; /** * Created by renyugang on 16/12/8. */ public class IContentProviderProxy implements InvocationHandler { private static final String TAG = Constants.TAG_PREFIX + "IContentProviderProxy"; private IContentProvider mBase; private Context mContext; private IContentProviderProxy(Context context, IContentProvider iContentProvider) { mBase = iContentProvider; mContext = context; } public static IContentProvider newInstance(Context context, IContentProvider iContentProvider) { return (IContentProvider) Proxy.newProxyInstance(iContentProvider.getClass().getClassLoader(), new Class[] { IContentProvider.class }, new IContentProviderProxy(context, iContentProvider)); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Log.v(TAG, method.toGenericString() + " : " + Arrays.toString(args)); wrapperUri(method, args); try { return method.invoke(mBase, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } private void wrapperUri(Method method, Object[] args) { Uri uri = null; int index = 0; if (args != null) { for (int i = 0; i < args.length; i++) { if (args[i] instanceof Uri) { uri = (Uri) args[i]; index = i; break; } } } Bundle bundleInCallMethod = null; if (method.getName().equals("call")) { bundleInCallMethod = getBundleParameter(args); if (bundleInCallMethod != null) { String uriString = bundleInCallMethod.getString(RemoteContentProvider.KEY_WRAPPER_URI); if (uriString != null) { uri = Uri.parse(uriString); } } } if (uri == null) { return; } PluginManager pluginManager = PluginManager.getInstance(mContext); ProviderInfo info = pluginManager.resolveContentProvider(uri.getAuthority(), 0); if (info != null) { String pkg = info.packageName; LoadedPlugin plugin = pluginManager.getLoadedPlugin(pkg); Uri wrapperUri = PluginContentResolver.wrapperUri(plugin, uri); if (method.getName().equals("call")) { bundleInCallMethod.putString(RemoteContentProvider.KEY_WRAPPER_URI, wrapperUri.toString()); } else { args[index] = wrapperUri; } } } private Bundle getBundleParameter(Object[] args) { Bundle bundle = null; if (args != null) { for (int i = 0; i < args.length; i++) { if (args[i] instanceof Bundle) { bundle = (Bundle) args[i]; break; } } } return bundle; } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/delegate/LocalService.java ================================================ /* * 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.delegate; import android.app.ActivityThread; import android.app.Application; import android.app.IActivityManager; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Build; 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 com.didi.virtualapk.internal.utils.PluginUtil; import com.didi.virtualapk.utils.Reflector; import java.lang.reflect.Method; /** * @author johnsonlee */ public class LocalService extends Service { private static final String TAG = Constants.TAG_PREFIX + "LocalService"; /** * The target service, usually it's a plugin service intent */ public static final String EXTRA_TARGET = "target"; public static final String EXTRA_COMMAND = "command"; public static final String EXTRA_PLUGIN_LOCATION = "plugin_location"; public static final int EXTRA_COMMAND_START_SERVICE = 1; public static final int EXTRA_COMMAND_STOP_SERVICE = 2; public static final int EXTRA_COMMAND_BIND_SERVICE = 3; public static final int EXTRA_COMMAND_UNBIND_SERVICE = 4; private PluginManager mPluginManager; @Override public IBinder onBind(Intent intent) { return new Binder(); } @Override public void onCreate() { super.onCreate(); mPluginManager = PluginManager.getInstance(this); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (null == intent || !intent.hasExtra(EXTRA_TARGET) || !intent.hasExtra(EXTRA_COMMAND)) { return START_STICKY; } Intent target = intent.getParcelableExtra(EXTRA_TARGET); int command = intent.getIntExtra(EXTRA_COMMAND, 0); if (null == target || command <= 0) { return START_STICKY; } 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.currentActivityThread(); IApplicationThread appThread = mainThread.getApplicationThread(); Service service; if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) { service = this.mPluginManager.getComponentsHandler().getService(component); } else { try { service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance(); Application app = plugin.getApplication(); IBinder token = appThread.asBinder(); Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class); IActivityManager am = mPluginManager.getActivityManager(); attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am); service.onCreate(); this.mPluginManager.getComponentsHandler().rememberService(component, service); } catch (Throwable t) { return START_STICKY; } } service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement()); break; } case EXTRA_COMMAND_BIND_SERVICE: { ActivityThread mainThread = ActivityThread.currentActivityThread(); IApplicationThread appThread = mainThread.getApplicationThread(); Service service = null; if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) { service = this.mPluginManager.getComponentsHandler().getService(component); } else { try { service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance(); Application app = plugin.getApplication(); IBinder token = appThread.asBinder(); Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class); IActivityManager am = mPluginManager.getActivityManager(); attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am); service.onCreate(); this.mPluginManager.getComponentsHandler().rememberService(component, service); } catch (Throwable t) { Log.w(TAG, t); } } try { IBinder binder = service.onBind(target); IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc"); IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection); if (Build.VERSION.SDK_INT >= 26) { iServiceConnection.connected(component, binder, false); } else { Reflector.QuietReflector.with(iServiceConnection).method("connected", ComponentName.class, IBinder.class).call(component, binder); } } catch (Exception e) { Log.w(TAG, e); } break; } case EXTRA_COMMAND_STOP_SERVICE: { Service service = this.mPluginManager.getComponentsHandler().forgetService(component); if (null != service) { try { service.onDestroy(); } catch (Exception e) { Log.e(TAG, "Unable to stop service " + service + ": " + e.toString()); } } else { Log.i(TAG, component + " not found"); } break; } case EXTRA_COMMAND_UNBIND_SERVICE: { Service service = this.mPluginManager.getComponentsHandler().forgetService(component); if (null != service) { try { service.onUnbind(target); service.onDestroy(); } catch (Exception e) { Log.e(TAG, "Unable to unbind service " + service + ": " + e.toString()); } } else { Log.i(TAG, component + " not found"); } break; } } return START_STICKY; } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteContentProvider.java ================================================ /* * 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.delegate; import android.content.ContentProvider; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.content.pm.ProviderInfo; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.annotation.NonNull; 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; import java.io.File; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * Created by renyugang on 16/12/7. */ public class RemoteContentProvider extends ContentProvider { private static final String TAG = Constants.TAG_PREFIX + "RemoteContentProvider"; public static final String KEY_PKG = "pkg"; public static final String KEY_PLUGIN = "plugin"; public static final String KEY_URI = "uri"; public static final String KEY_WRAPPER_URI = "wrapper_uri"; private static Map sCachedProviders = new HashMap<>(); @Override public boolean onCreate() { Log.d(TAG, "onCreate, current thread:" + Thread.currentThread().getName()); return true; } private ContentProvider getContentProvider(final Uri uri) { final PluginManager pluginManager = PluginManager.getInstance(getContext()); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); final String auth = pluginUri.getAuthority(); ContentProvider cachedProvider = sCachedProviders.get(auth); if (cachedProvider != null) { return cachedProvider; } synchronized (sCachedProviders) { LoadedPlugin plugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG)); if (plugin == null) { try { pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN))); } catch (Exception e) { Log.w(TAG, e); } } final ProviderInfo providerInfo = pluginManager.resolveContentProvider(auth, 0); if (providerInfo != null) { RunUtil.runOnUiThread(new Runnable() { @Override public void run() { try { LoadedPlugin loadedPlugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG)); ContentProvider contentProvider = (ContentProvider) Class.forName(providerInfo.name).newInstance(); contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo); sCachedProviders.put(auth, contentProvider); } catch (Exception e) { Log.w(TAG, e); } } }, true); return sCachedProviders.get(auth); } } return null; } @Override public String getType(Uri uri) { ContentProvider provider = getContentProvider(uri); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); if (provider != null) { return provider.getType(pluginUri); } return null; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ContentProvider provider = getContentProvider(uri); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); if (provider != null) { return provider.query(pluginUri, projection, selection, selectionArgs, sortOrder); } return null; } @Override public Uri insert(Uri uri, ContentValues values) { ContentProvider provider = getContentProvider(uri); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); if (provider != null) { return provider.insert(pluginUri, values); } return uri; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { ContentProvider provider = getContentProvider(uri); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); if (provider != null) { return provider.delete(pluginUri, selection, selectionArgs); } return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { ContentProvider provider = getContentProvider(uri); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); if (provider != null) { return provider.update(pluginUri, values, selection, selectionArgs); } return 0; } @Override public int bulkInsert(Uri uri, ContentValues[] values) { ContentProvider provider = getContentProvider(uri); Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); if (provider != null) { return provider.bulkInsert(pluginUri, values); } return 0; } @NonNull @Override public ContentProviderResult[] applyBatch(ArrayList operations) throws OperationApplicationException { try { Field uriField = ContentProviderOperation.class.getDeclaredField("mUri"); uriField.setAccessible(true); for (ContentProviderOperation operation : operations) { Uri pluginUri = Uri.parse(operation.getUri().getQueryParameter(KEY_URI)); uriField.set(operation, pluginUri); } } catch (Exception e) { return new ContentProviderResult[0]; } if (operations.size() > 0) { ContentProvider provider = getContentProvider(operations.get(0).getUri()); if (provider != null) { return provider.applyBatch(operations); } } return new ContentProviderResult[0]; } @Override public Bundle call(String method, String arg, Bundle extras) { Log.d(TAG, "call " + method + " with extras : " + extras); if (extras == null || extras.getString(KEY_WRAPPER_URI) == null) { return null; } Uri uri = Uri.parse(extras.getString(KEY_WRAPPER_URI)); ContentProvider provider = getContentProvider(uri); if (provider != null) { return provider.call(method, arg, extras); } return null; } public static String getAuthority(Context context) { return context.getPackageName() + ".VirtualAPK.Provider"; } public static String getUri(Context context) { return "content://" + getAuthority(context); } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteService.java ================================================ /* * 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.delegate; 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; /** * @author johnsonlee */ public class RemoteService extends LocalService { private static final String TAG = Constants.TAG_PREFIX + "RemoteService"; @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null) { return super.onStartCommand(intent, flags, startId); } Intent target = intent.getParcelableExtra(EXTRA_TARGET); if (target != null) { String pluginLocation = intent.getStringExtra(EXTRA_PLUGIN_LOCATION); ComponentName component = target.getComponent(); LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(component); if (plugin == null && pluginLocation != null) { try { PluginManager.getInstance(this).loadPlugin(new File(pluginLocation)); } catch (Exception e) { Log.w(TAG, e); } } } return super.onStartCommand(intent, flags, startId); } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/delegate/StubActivity.java ================================================ 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(); } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/ActivityLifecycleCallbacksProxy.java ================================================ 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); } } } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/ComponentsHandler.java ================================================ /* * 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.internal; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.os.IBinder; import android.support.v4.util.ArrayMap; import android.util.Log; import com.didi.virtualapk.PluginManager; import java.util.concurrent.atomic.AtomicInteger; /** * Created by renyugang on 17/6/7. */ public class ComponentsHandler { public static final String TAG = Constants.TAG_PREFIX + "PluginManager"; private Context mContext; private PluginManager mPluginManager; private StubActivityInfo mStubActivityInfo = new StubActivityInfo(); private ArrayMap mServices = new ArrayMap(); private ArrayMap mBoundServices = new ArrayMap(); private ArrayMap mServiceCounters = new ArrayMap(); public ComponentsHandler(PluginManager pluginManager) { mPluginManager = pluginManager; mContext = pluginManager.getHostContext(); } /** * transform intent from implicit to explicit */ public Intent transformIntentToExplicitAsNeeded(Intent intent) { ComponentName component = intent.getComponent(); if (component == null || component.getPackageName().equals(mContext.getPackageName())) { ResolveInfo info = mPluginManager.resolveActivity(intent); if (info != null && info.activityInfo != null) { component = new ComponentName(info.activityInfo.packageName, info.activityInfo.name); intent.setComponent(component); } } return intent; } public void markIntentIfNeeded(Intent intent) { if (intent.getComponent() == null) { return; } String targetPackageName = intent.getComponent().getPackageName(); String targetClassName = intent.getComponent().getClassName(); // search map and return specific launchmode stub activity if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) { intent.putExtra(Constants.KEY_IS_PLUGIN, true); intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName); intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName); dispatchStubActivity(intent); } } private void dispatchStubActivity(Intent intent) { ComponentName component = intent.getComponent(); String targetClassName = intent.getComponent().getClassName(); LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent); ActivityInfo info = loadedPlugin.getActivityInfo(component); if (info == null) { throw new RuntimeException("can not find " + component); } int launchMode = info.launchMode; Resources.Theme themeObj = loadedPlugin.getResources().newTheme(); themeObj.applyStyle(info.theme, true); String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj); Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity)); intent.setClassName(mContext, stubActivity); } public AtomicInteger getServiceCounter(Service service) { return this.mServiceCounters.get(service); } /** * Retrieve the started service by component name * * @param component * @return */ public Service getService(ComponentName component) { return this.mServices.get(component); } /** * Put the started service into service registry, and then increase the counter associate with * the service * * @param component * @param service */ public void rememberService(ComponentName component, Service service) { synchronized (this.mServices) { this.mServices.put(component, service); this.mServiceCounters.put(service, new AtomicInteger(0)); } } /** * Remove the service from service registry * * @param component * @return */ public Service forgetService(ComponentName component) { synchronized (this.mServices) { Service service = this.mServices.remove(component); this.mServiceCounters.remove(service); return service; } } /** * Remove the bound service from service registry * * @param iServiceConnection IServiceConnection binder when unbindService * @return */ public Intent forgetIServiceConnection(IBinder iServiceConnection) { synchronized (this.mBoundServices) { Intent intent = this.mBoundServices.remove(iServiceConnection); return intent; } } /** * save the bound service * * @param iServiceConnection IServiceConnection binder when bindService * @return */ public void remberIServiceConnection(IBinder iServiceConnection, Intent intent) { synchronized (this.mBoundServices) { mBoundServices.put(iServiceConnection, intent); } } /** * Check if a started service with the specified component exists in the registry * * @param component * @return */ public boolean isServiceAvailable(ComponentName component) { return this.mServices.containsKey(component); } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/Constants.java ================================================ /* * 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.internal; /** * Created by renyugang on 16/8/15. */ public class Constants { public static final String KEY_IS_PLUGIN = "isPlugin"; public static final String KEY_TARGET_PACKAGE = "target.package"; public static final String KEY_TARGET_ACTIVITY = "target.activity"; public static final String OPTIMIZE_DIR = "dex"; public static final String NATIVE_DIR = "valibs"; 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 + "."; } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/LoadedPlugin.java ================================================ /* * 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.internal; import android.annotation.TargetApi; import android.app.Application; import android.app.Instrumentation; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; 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; import android.content.pm.PermissionInfo; 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.Process; import android.os.UserHandle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.didi.virtualapk.PluginManager; 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.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import dalvik.system.DexClassLoader; /** * Created by renyugang on 16/8/9. */ public class LoadedPlugin { public static final String TAG = Constants.TAG_PREFIX + "LoadedPlugin"; protected File getDir(Context context, String name) { return context.getDir(name, 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) { DexUtil.insertDex(loader, parent, libsDir); } return loader; } 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; } protected Resources createResources(Context context, String packageName, File apk) throws Exception { if (Constants.COMBINE_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); } protected ResolveInfo chooseBestActivity(Intent intent, String s, int flags, List query) { return query.get(0); } 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(); this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK); this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData; this.mPackageInfo = new PackageInfo(); this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo; this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath(); 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); } this.mPackageInfo.versionCode = this.mPackage.mVersionCode; this.mPackageInfo.versionName = this.mPackage.mVersionName; this.mPackageInfo.permissions = new PermissionInfo[0]; 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); // Cache instrumentations Map instrumentations = new HashMap(); for (PackageParser.Instrumentation instrumentation : this.mPackage.instrumentation) { instrumentations.put(instrumentation.getComponentName(), instrumentation.info); } this.mInstrumentationInfos = Collections.unmodifiableMap(instrumentations); this.mPackageInfo.instrumentation = instrumentations.values().toArray(new InstrumentationInfo[instrumentations.size()]); // 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); this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]); // Cache services Map serviceInfos = new HashMap(); for (PackageParser.Service service : this.mPackage.services) { serviceInfos.put(service.getComponentName(), service.info); } this.mServiceInfos = Collections.unmodifiableMap(serviceInfos); this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]); // Cache providers Map providers = new HashMap(); Map providerInfos = new HashMap(); for (PackageParser.Provider provider : this.mPackage.providers) { providers.put(provider.info.authority, provider.info); providerInfos.put(provider.getComponentName(), provider.info); } this.mProviders = Collections.unmodifiableMap(providers); this.mProviderInfos = Collections.unmodifiableMap(providerInfos); this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]); // Register broadcast receivers dynamically Map receivers = new HashMap(); for (PackageParser.Activity receiver : this.mPackage.receivers) { receivers.put(receiver.getComponentName(), receiver.info); 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(); } protected void tryToCopyNativeLib(File apk) throws Exception { PluginUtil.copyNativeLib(apk, mHostContext, mPackageInfo, mNativeLibDir); } public String getLocation() { return this.mLocation; } public String getPackageName() { return this.mPackage.packageName; } public PackageManager getPackageManager() { return this.mPackageManager; } public AssetManager getAssets() { return getResources().getAssets(); } public Resources getResources() { return this.mResources; } public void updateResources(Resources newResources) { this.mResources = newResources; } public ClassLoader getClassLoader() { return this.mClassLoader; } public PluginManager getPluginManager() { return this.mPluginManager; } public Context getHostContext() { return this.mHostContext; } public Context getPluginContext() { return this.mPluginContext; } public Application getApplication() { return mApplication; } 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() { 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() { int myUid = Process.myUid(); ApplicationInfo appInfo = this.mPackage.applicationInfo; return appInfo.uid == myUid ? appInfo.sourceDir : appInfo.publicSourceDir; } public String getCodePath() { return this.mPackage.applicationInfo.sourceDir; } public Intent getLaunchIntent() { ContentResolver resolver = this.mPluginContext.getContentResolver(); Intent launcher = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER); for (PackageParser.Activity activity : this.mPackage.activities) { for (PackageParser.ActivityIntentInfo intentInfo : activity.intents) { if (intentInfo.match(resolver, launcher, false, TAG) > 0) { return Intent.makeMainActivity(activity.getComponentName()); } } } return null; } public Intent getLeanbackLaunchIntent() { ContentResolver resolver = this.mPluginContext.getContentResolver(); Intent launcher = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER); for (PackageParser.Activity activity : this.mPackage.activities) { for (PackageParser.ActivityIntentInfo intentInfo : activity.intents) { if (intentInfo.match(resolver, launcher, false, TAG) > 0) { Intent intent = new Intent(Intent.ACTION_MAIN); intent.setComponent(activity.getComponentName()); intent.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER); return intent; } } } return null; } public ApplicationInfo getApplicationInfo() { return this.mPackage.applicationInfo; } public PackageInfo getPackageInfo() { return this.mPackageInfo; } public ActivityInfo getActivityInfo(ComponentName componentName) { return this.mActivityInfos.get(componentName); } public ServiceInfo getServiceInfo(ComponentName componentName) { return this.mServiceInfos.get(componentName); } public ActivityInfo getReceiverInfo(ComponentName componentName) { return this.mReceiverInfos.get(componentName); } public ProviderInfo getProviderInfo(ComponentName componentName) { return this.mProviderInfos.get(componentName); } public Resources.Theme getTheme() { Resources.Theme theme = this.mResources.newTheme(); theme.applyStyle(PluginUtil.selectDefaultTheme(this.mPackage.applicationInfo.theme, Build.VERSION.SDK_INT), false); return theme; } public void setTheme(int resid) { Reflector.QuietReflector.with(this.mResources).field("mThemeResId").set(resid); } protected Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) throws Exception { if (null != this.mApplication) { return this.mApplication; } String appClass = this.mPackage.applicationInfo.className; if (forceDefaultAppClass || null == appClass) { appClass = "android.app.Application"; } 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) { List query = this.queryIntentActivities(intent, flags); if (null == query || query.isEmpty()) { return null; } ContentResolver resolver = this.mPluginContext.getContentResolver(); return chooseBestActivity(intent, intent.resolveTypeIfNeeded(resolver), flags, query); } public List queryIntentActivities(Intent intent, int flags) { ComponentName component = intent.getComponent(); List resolveInfos = new ArrayList(); ContentResolver resolver = this.mPluginContext.getContentResolver(); for (PackageParser.Activity activity : this.mPackage.activities) { if (match(activity, component)) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = activity.info; resolveInfos.add(resolveInfo); } else if (component == null) { // only match implicit intent for (PackageParser.ActivityIntentInfo intentInfo : activity.intents) { if (intentInfo.match(resolver, intent, true, TAG) >= 0) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = activity.info; resolveInfos.add(resolveInfo); break; } } } } return resolveInfos; } public ResolveInfo resolveService(Intent intent, int flags) { List query = this.queryIntentServices(intent, flags); if (null == query || query.isEmpty()) { return null; } ContentResolver resolver = this.mPluginContext.getContentResolver(); return chooseBestActivity(intent, intent.resolveTypeIfNeeded(resolver), flags, query); } public List queryIntentServices(Intent intent, int flags) { ComponentName component = intent.getComponent(); List resolveInfos = new ArrayList(); ContentResolver resolver = this.mPluginContext.getContentResolver(); for (PackageParser.Service service : this.mPackage.services) { if (match(service, component)) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.serviceInfo = service.info; resolveInfos.add(resolveInfo); } else if (component == null) { // only match implicit intent for (PackageParser.ServiceIntentInfo intentInfo : service.intents) { if (intentInfo.match(resolver, intent, true, TAG) >= 0) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.serviceInfo = service.info; resolveInfos.add(resolveInfo); break; } } } } return resolveInfos; } public List queryBroadcastReceivers(Intent intent, int flags) { ComponentName component = intent.getComponent(); List resolveInfos = new ArrayList(); ContentResolver resolver = this.mPluginContext.getContentResolver(); for (PackageParser.Activity receiver : this.mPackage.receivers) { if (receiver.getComponentName().equals(component)) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = receiver.info; resolveInfos.add(resolveInfo); } else if (component == null) { // only match implicit intent for (PackageParser.ActivityIntentInfo intentInfo : receiver.intents) { if (intentInfo.match(resolver, intent, true, TAG) >= 0) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = receiver.info; resolveInfos.add(resolveInfo); break; } } } } return resolveInfos; } public ProviderInfo resolveContentProvider(String name, int flags) { return this.mProviders.get(name); } protected boolean match(PackageParser.Component component, ComponentName target) { ComponentName source = component.getComponentName(); if (source == target) return true; if (source != null && target != null && source.getClassName().equals(target.getClassName()) && (source.getPackageName().equals(target.getPackageName()) || mHostContext.getPackageName().equals(target.getPackageName()))) { return true; } return false; } /** * @author johnsonlee */ protected class PluginPackageManager extends PackageManager { protected PackageManager mHostPackageManager = mHostContext.getPackageManager(); @Override public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.mPackageInfo; } 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); } @Override public String[] canonicalToCurrentPackageNames(String[] names) { return this.mHostPackageManager.canonicalToCurrentPackageNames(names); } @Override public Intent getLaunchIntentForPackage(@NonNull String packageName) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.getLaunchIntent(); } return this.mHostPackageManager.getLaunchIntentForPackage(packageName); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public Intent getLeanbackLaunchIntentForPackage(@NonNull String packageName) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.getLeanbackLaunchIntent(); } return this.mHostPackageManager.getLeanbackLaunchIntentForPackage(packageName); } @Override 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 { return this.mHostPackageManager.getPermissionInfo(name, flags); } @Override public List queryPermissionsByGroup(String group, int flags) throws NameNotFoundException { return this.mHostPackageManager.queryPermissionsByGroup(group, flags); } @Override public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) throws NameNotFoundException { return this.mHostPackageManager.getPermissionGroupInfo(name, flags); } @Override public List getAllPermissionGroups(int flags) { return this.mHostPackageManager.getAllPermissionGroups(flags); } @Override public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.getApplicationInfo(); } return this.mHostPackageManager.getApplicationInfo(packageName, flags); } @Override public ActivityInfo getActivityInfo(ComponentName component, int flags) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { return plugin.mActivityInfos.get(component); } return this.mHostPackageManager.getActivityInfo(component, flags); } @Override public ActivityInfo getReceiverInfo(ComponentName component, int flags) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { return plugin.mReceiverInfos.get(component); } return this.mHostPackageManager.getReceiverInfo(component, flags); } @Override public ServiceInfo getServiceInfo(ComponentName component, int flags) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { return plugin.mServiceInfos.get(component); } return this.mHostPackageManager.getServiceInfo(component, flags); } @Override public ProviderInfo getProviderInfo(ComponentName component, int flags) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { return plugin.mProviderInfos.get(component); } return this.mHostPackageManager.getProviderInfo(component, flags); } @Override public List getInstalledPackages(int flags) { return this.mHostPackageManager.getInstalledPackages(flags); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) @Override public List getPackagesHoldingPermissions(String[] permissions, int flags) { return this.mHostPackageManager.getPackagesHoldingPermissions(permissions, flags); } @Override 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); } @Override public boolean addPermissionAsync(PermissionInfo info) { return this.mHostPackageManager.addPermissionAsync(info); } @Override public void removePermission(String name) { this.mHostPackageManager.removePermission(name); } @Override public int checkSignatures(String pkg1, String pkg2) { return this.mHostPackageManager.checkSignatures(pkg1, pkg2); } @Override public int checkSignatures(int uid1, int uid2) { return this.mHostPackageManager.checkSignatures(uid1, uid2); } @Override public String[] getPackagesForUid(int uid) { return this.mHostPackageManager.getPackagesForUid(uid); } @Override public String getNameForUid(int uid) { return this.mHostPackageManager.getNameForUid(uid); } @Override 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(); } @Override 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); if (null != resolveInfo) { return resolveInfo; } return this.mHostPackageManager.resolveActivity(intent, flags); } @Override public List queryIntentActivities(Intent intent, int flags) { ComponentName component = intent.getComponent(); if (null == component) { if (intent.getSelector() != null) { intent = intent.getSelector(); component = intent.getComponent(); } } if (null != component) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { ActivityInfo activityInfo = plugin.getActivityInfo(component); if (activityInfo != null) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = activityInfo; return Arrays.asList(resolveInfo); } } } List all = new ArrayList(); List pluginResolveInfos = mPluginManager.queryIntentActivities(intent, flags); if (null != pluginResolveInfos && pluginResolveInfos.size() > 0) { all.addAll(pluginResolveInfos); } List hostResolveInfos = this.mHostPackageManager.queryIntentActivities(intent, flags); if (null != hostResolveInfos && hostResolveInfos.size() > 0) { all.addAll(hostResolveInfos); } return all; } @Override public List queryIntentActivityOptions(ComponentName caller, Intent[] specifics, Intent intent, int flags) { return this.mHostPackageManager.queryIntentActivityOptions(caller, specifics, intent, flags); } @Override public List queryBroadcastReceivers(Intent intent, int flags) { ComponentName component = intent.getComponent(); if (null == component) { if (intent.getSelector() != null) { intent = intent.getSelector(); component = intent.getComponent(); } } if (null != component) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { ActivityInfo activityInfo = plugin.getReceiverInfo(component); if (activityInfo != null) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = activityInfo; return Arrays.asList(resolveInfo); } } } List all = new ArrayList<>(); List pluginResolveInfos = mPluginManager.queryBroadcastReceivers(intent, flags); if (null != pluginResolveInfos && pluginResolveInfos.size() > 0) { all.addAll(pluginResolveInfos); } List hostResolveInfos = this.mHostPackageManager.queryBroadcastReceivers(intent, flags); if (null != hostResolveInfos && hostResolveInfos.size() > 0) { all.addAll(hostResolveInfos); } return all; } @Override public ResolveInfo resolveService(Intent intent, int flags) { ResolveInfo resolveInfo = mPluginManager.resolveService(intent, flags); if (null != resolveInfo) { return resolveInfo; } return this.mHostPackageManager.resolveService(intent, flags); } @Override public List queryIntentServices(Intent intent, int flags) { ComponentName component = intent.getComponent(); if (null == component) { if (intent.getSelector() != null) { intent = intent.getSelector(); component = intent.getComponent(); } } if (null != component) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { ServiceInfo serviceInfo = plugin.getServiceInfo(component); if (serviceInfo != null) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.serviceInfo = serviceInfo; return Arrays.asList(resolveInfo); } } } List all = new ArrayList(); List pluginResolveInfos = mPluginManager.queryIntentServices(intent, flags); if (null != pluginResolveInfos && pluginResolveInfos.size() > 0) { all.addAll(pluginResolveInfos); } List hostResolveInfos = this.mHostPackageManager.queryIntentServices(intent, flags); if (null != hostResolveInfos && hostResolveInfos.size() > 0) { all.addAll(hostResolveInfos); } return all; } @Override @TargetApi(Build.VERSION_CODES.KITKAT) public List queryIntentContentProviders(Intent intent, int flags) { return this.mHostPackageManager.queryIntentContentProviders(intent, flags); } @Override public ProviderInfo resolveContentProvider(String name, int flags) { ProviderInfo providerInfo = mPluginManager.resolveContentProvider(name, flags); if (null != providerInfo) { return providerInfo; } return this.mHostPackageManager.resolveContentProvider(name, flags); } @Override public List queryContentProviders(String processName, int uid, int flags) { return this.mHostPackageManager.queryContentProviders(processName, uid, flags); } @Override public InstrumentationInfo getInstrumentationInfo(ComponentName component, int flags) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { return plugin.mInstrumentationInfos.get(component); } return this.mHostPackageManager.getInstrumentationInfo(component, flags); } @Override public List queryInstrumentation(String targetPackage, int flags) { return this.mHostPackageManager.queryInstrumentation(targetPackage, flags); } @Override public Drawable getDrawable(String packageName, int resid, ApplicationInfo appInfo) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.mResources.getDrawable(resid); } return this.mHostPackageManager.getDrawable(packageName, resid, appInfo); } @Override public Drawable getActivityIcon(ComponentName component) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { return plugin.mResources.getDrawable(plugin.mActivityInfos.get(component).icon); } return this.mHostPackageManager.getActivityIcon(component); } @Override public Drawable getActivityIcon(Intent intent) throws NameNotFoundException { ResolveInfo ri = mPluginManager.resolveActivity(intent); if (null != ri) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(ri.resolvePackageName); return plugin.mResources.getDrawable(ri.activityInfo.icon); } return this.mHostPackageManager.getActivityIcon(intent); } @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) @Override public Drawable getActivityBanner(ComponentName component) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { return plugin.mResources.getDrawable(plugin.mActivityInfos.get(component).banner); } return this.mHostPackageManager.getActivityBanner(component); } @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) @Override public Drawable getActivityBanner(Intent intent) throws NameNotFoundException { ResolveInfo ri = mPluginManager.resolveActivity(intent); if (null != ri) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(ri.resolvePackageName); return plugin.mResources.getDrawable(ri.activityInfo.banner); } return this.mHostPackageManager.getActivityBanner(intent); } @Override public Drawable getDefaultActivityIcon() { return this.mHostPackageManager.getDefaultActivityIcon(); } @Override public Drawable getApplicationIcon(ApplicationInfo info) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(info.packageName); if (null != plugin) { return plugin.mResources.getDrawable(info.icon); } return this.mHostPackageManager.getApplicationIcon(info); } @Override public Drawable getApplicationIcon(String packageName) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.mResources.getDrawable(plugin.mPackage.applicationInfo.icon); } return this.mHostPackageManager.getApplicationIcon(packageName); } @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) @Override public Drawable getApplicationBanner(ApplicationInfo info) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(info.packageName); if (null != plugin) { return plugin.mResources.getDrawable(info.banner); } return this.mHostPackageManager.getApplicationBanner(info); } @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) @Override public Drawable getApplicationBanner(String packageName) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.mResources.getDrawable(plugin.mPackage.applicationInfo.banner); } return this.mHostPackageManager.getApplicationBanner(packageName); } @Override public Drawable getActivityLogo(ComponentName component) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { return plugin.mResources.getDrawable(plugin.mActivityInfos.get(component).logo); } return this.mHostPackageManager.getActivityLogo(component); } @Override public Drawable getActivityLogo(Intent intent) throws NameNotFoundException { ResolveInfo ri = mPluginManager.resolveActivity(intent); if (null != ri) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(ri.resolvePackageName); return plugin.mResources.getDrawable(ri.activityInfo.logo); } return this.mHostPackageManager.getActivityLogo(intent); } @Override public Drawable getApplicationLogo(ApplicationInfo info) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(info.packageName); if (null != plugin) { return plugin.mResources.getDrawable(0 != info.logo ? info.logo : android.R.drawable.sym_def_app_icon); } return this.mHostPackageManager.getApplicationLogo(info); } @Override public Drawable getApplicationLogo(String packageName) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.mResources.getDrawable(0 != plugin.mPackage.applicationInfo.logo ? plugin.mPackage.applicationInfo.logo : android.R.drawable.sym_def_app_icon); } return this.mHostPackageManager.getApplicationLogo(packageName); } @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 { return Reflector.with(this.mHostPackageManager) .method("getUserBadgeForDensity", UserHandle.class, int.class) .call(user, density); } catch (Exception e) { throw new RuntimeException(e); } } @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); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) { return this.mHostPackageManager.getUserBadgedLabel(label, user); } @Override public CharSequence getText(String packageName, int resid, ApplicationInfo appInfo) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.mResources.getText(resid); } return this.mHostPackageManager.getText(packageName, resid, appInfo); } @Override public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return plugin.mResources.getXml(resid); } return this.mHostPackageManager.getXml(packageName, resid, appInfo); } @Override public CharSequence getApplicationLabel(ApplicationInfo info) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(info.packageName); if (null != plugin) { try { return plugin.mResources.getText(info.labelRes); } catch (Resources.NotFoundException e) { // ignored. } } return this.mHostPackageManager.getApplicationLabel(info); } @Override public Resources getResourcesForActivity(ComponentName component) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component); if (null != plugin) { return plugin.mResources; } return this.mHostPackageManager.getResourcesForActivity(component); } @Override public Resources getResourcesForApplication(ApplicationInfo app) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(app.packageName); if (null != plugin) { return plugin.mResources; } return this.mHostPackageManager.getResourcesForApplication(app); } @Override public Resources getResourcesForApplication(String appPackageName) throws NameNotFoundException { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(appPackageName); if (null != plugin) { return plugin.mResources; } return this.mHostPackageManager.getResourcesForApplication(appPackageName); } @Override public void verifyPendingInstall(int id, int verificationCode) { this.mHostPackageManager.verifyPendingInstall(id, verificationCode); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay) { this.mHostPackageManager.extendVerificationTimeout(id, verificationCodeAtTimeout, millisecondsToDelay); } @Override public void setInstallerPackageName(String targetPackage, String installerPackageName) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(targetPackage); if (null != plugin) { return; } this.mHostPackageManager.setInstallerPackageName(targetPackage, installerPackageName); } @Override public String getInstallerPackageName(String packageName) { LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); if (null != plugin) { return mHostContext.getPackageName(); } return this.mHostPackageManager.getInstallerPackageName(packageName); } @Override public void addPackageToPreferred(String packageName) { this.mHostPackageManager.addPackageToPreferred(packageName); } @Override public void removePackageFromPreferred(String packageName) { this.mHostPackageManager.removePackageFromPreferred(packageName); } @Override public List getPreferredPackages(int flags) { return this.mHostPackageManager.getPreferredPackages(flags); } @Override public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { this.mHostPackageManager.addPreferredActivity(filter, match, set, activity); } @Override public void clearPackagePreferredActivities(String packageName) { this.mHostPackageManager.clearPackagePreferredActivities(packageName); } @Override public int getPreferredActivities(@NonNull List outFilters, @NonNull List outActivities, String packageName) { return this.mHostPackageManager.getPreferredActivities(outFilters, outActivities, packageName); } @Override public void setComponentEnabledSetting(ComponentName component, int newState, int flags) { this.mHostPackageManager.setComponentEnabledSetting(component, newState, flags); } @Override public int getComponentEnabledSetting(ComponentName component) { return this.mHostPackageManager.getComponentEnabledSetting(component); } @Override public void setApplicationEnabledSetting(String packageName, int newState, int flags) { this.mHostPackageManager.setApplicationEnabledSetting(packageName, newState, flags); } @Override public int getApplicationEnabledSetting(String packageName) { return this.mHostPackageManager.getApplicationEnabledSetting(packageName); } @Override 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 @NonNull PackageInstaller getPackageInstaller() { return this.mHostPackageManager.getPackageInstaller(); } @TargetApi(Build.VERSION_CODES.O) @Override public boolean canRequestPackageInstalls() { return this.mHostPackageManager.canRequestPackageInstalls(); } public Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) { if (itemInfo == null) { return null; } return itemInfo.loadIcon(this.mHostPackageManager); } } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContentResolver.java ================================================ /* * 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.internal; 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; /** * Created by renyugang on 16/12/7. */ public class PluginContentResolver extends ContentResolverWrapper { private PluginManager mPluginManager; public PluginContentResolver(Context context) { super(context); mPluginManager = PluginManager.getInstance(context); } @Override protected IContentProvider acquireProvider(Context context, String auth) { if (mPluginManager.resolveContentProvider(auth, 0) != null) { return mPluginManager.getIContentProvider(); } return super.acquireProvider(context, auth); } @Override protected IContentProvider acquireExistingProvider(Context context, String auth) { if (mPluginManager.resolveContentProvider(auth, 0) != null) { return mPluginManager.getIContentProvider(); } return super.acquireExistingProvider(context, auth); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override protected IContentProvider acquireUnstableProvider(Context context, String auth) { if (mPluginManager.resolveContentProvider(auth, 0) != null) { return mPluginManager.getIContentProvider(); } 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) { } protected int resolveUserIdFromAuthority(String auth) { return 0; } @Keep public static Uri wrapperUri(LoadedPlugin loadedPlugin, Uri pluginUri) { String pkg = loadedPlugin.getPackageName(); String pluginUriString = Uri.encode(pluginUri.toString()); StringBuilder builder = new StringBuilder(RemoteContentProvider.getUri(loadedPlugin.getHostContext())); builder.append("/?plugin=" + loadedPlugin.getLocation()); builder.append("&pkg=" + pkg); builder.append("&uri=" + pluginUriString); Uri wrapperUri = Uri.parse(builder.toString()); return wrapperUri; } @Deprecated public static String getAuthority(Context context) { return RemoteContentProvider.getAuthority(context); } @Deprecated public static String getUri(Context context) { return RemoteContentProvider.getUri(context); } @Keep public static Bundle getBundleForCall(Uri uri) { Bundle bundle = new Bundle(); bundle.putString(RemoteContentProvider.KEY_WRAPPER_URI, uri.toString()); return bundle; } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContext.java ================================================ /* * 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.internal; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; /** * Created by renyugang on 16/8/12. */ class PluginContext extends ContextWrapper { private final LoadedPlugin mPlugin; 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() { return this.mPlugin.getApplication(); } // @Override // public ApplicationInfo getApplicationInfo() { // return this.mPlugin.getApplicationInfo(); // } private Context getHostContext() { return getBaseContext(); } @Override public ContentResolver getContentResolver() { return new PluginContentResolver(getHostContext()); } @Override public ClassLoader getClassLoader() { return this.mPlugin.getClassLoader(); } // @Override // public String getPackageName() { // return this.mPlugin.getPackageName(); // } // @Override // public String getPackageResourcePath() { // return this.mPlugin.getPackageResourcePath(); // } // @Override // public String getPackageCodePath() { // return this.mPlugin.getCodePath(); // } @Override public PackageManager getPackageManager() { return this.mPlugin.getPackageManager(); } @Override public Object getSystemService(String name) { // intercept CLIPBOARD_SERVICE,NOTIFICATION_SERVICE if (name.equals(Context.CLIPBOARD_SERVICE)) { return getHostContext().getSystemService(name); } else if (name.equals(Context.NOTIFICATION_SERVICE)) { return getHostContext().getSystemService(name); } return super.getSystemService(name); } @Override public Resources getResources() { return this.mPlugin.getResources(); } @Override public AssetManager getAssets() { return this.mPlugin.getAssets(); } @Override public Resources.Theme getTheme() { return this.mPlugin.getTheme(); } @Override public void startActivity(Intent intent) { ComponentsHandler componentsHandler = mPlugin.getPluginManager().getComponentsHandler(); componentsHandler.transformIntentToExplicitAsNeeded(intent); super.startActivity(intent); } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/ResourcesManager.java ================================================ /* * 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.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.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"; 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; 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); } } 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()); } } 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 { 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 { 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) { 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) { return resources.getClass().getName().equals("android.content.res.MiuiResources"); } private static boolean isVivo(Resources resources) { return resources.getClass().getName().equals("android.content.res.VivoResources"); } private static boolean isNubia(Resources resources) { return resources.getClass().getName().equals("android.content.res.NubiaResources"); } private static boolean isNotRawResources(Resources resources) { return !resources.getClass().getName().equals("android.content.res.Resources"); } private static final class MiUiResourcesCompat { private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception { 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 { 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 { 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; } } private static final class AdaptationResourcesCompat { private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception { Resources newResources; try { 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()); } return newResources; } } 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)); } } } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/StubActivityInfo.java ================================================ /* * 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.internal; import android.content.pm.ActivityInfo; import android.content.res.TypedArray; import android.util.Log; import android.content.res.Resources.Theme; import java.util.HashMap; /** * Created by renyugang on 16/8/15. */ class StubActivityInfo { public static final int MAX_COUNT_STANDARD = 1; public static final int MAX_COUNT_SINGLETOP = 8; public static final int MAX_COUNT_SINGLETASK = 8; public static final int MAX_COUNT_SINGLEINSTANCE = 8; public static final String corePackage = "com.didi.virtualapk.core"; public static final String STUB_ACTIVITY_STANDARD = "%s.A$%d"; public static final String STUB_ACTIVITY_SINGLETOP = "%s.B$%d"; public static final String STUB_ACTIVITY_SINGLETASK = "%s.C$%d"; public static final String STUB_ACTIVITY_SINGLEINSTANCE = "%s.D$%d"; public final int usedStandardStubActivity = 1; public int usedSingleTopStubActivity = 0; public int usedSingleTaskStubActivity = 0; public int usedSingleInstanceStubActivity = 0; private HashMap mCachedStubActivity = new HashMap<>(); public String getStubActivity(String className, int launchMode, Theme theme) { String stubActivity= mCachedStubActivity.get(className); if (stubActivity != null) { return stubActivity; } TypedArray array = theme.obtainStyledAttributes(new int[]{ android.R.attr.windowIsTranslucent, android.R.attr.windowBackground }); boolean windowIsTranslucent = array.getBoolean(0, false); array.recycle(); if (Constants.DEBUG) { Log.d(Constants.TAG_PREFIX + "StubActivityInfo", "getStubActivity, is transparent theme ? " + windowIsTranslucent); } stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity); switch (launchMode) { case ActivityInfo.LAUNCH_MULTIPLE: { stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity); if (windowIsTranslucent) { stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, 2); } break; } case ActivityInfo.LAUNCH_SINGLE_TOP: { usedSingleTopStubActivity = usedSingleTopStubActivity % MAX_COUNT_SINGLETOP + 1; stubActivity = String.format(STUB_ACTIVITY_SINGLETOP, corePackage, usedSingleTopStubActivity); break; } case ActivityInfo.LAUNCH_SINGLE_TASK: { usedSingleTaskStubActivity = usedSingleTaskStubActivity % MAX_COUNT_SINGLETASK + 1; stubActivity = String.format(STUB_ACTIVITY_SINGLETASK, corePackage, usedSingleTaskStubActivity); break; } case ActivityInfo.LAUNCH_SINGLE_INSTANCE: { usedSingleInstanceStubActivity = usedSingleInstanceStubActivity % MAX_COUNT_SINGLEINSTANCE + 1; stubActivity = String.format(STUB_ACTIVITY_SINGLEINSTANCE, corePackage, usedSingleInstanceStubActivity); break; } default:break; } mCachedStubActivity.put(className, stubActivity); return stubActivity; } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/VAInstrumentation.java ================================================ /* * 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.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.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 com.didi.virtualapk.PluginManager; 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 = Constants.TAG_PREFIX + "VAInstrumentation"; public static final int LAUNCH_ACTIVITY = 100; protected Instrumentation mBase; protected final ArrayList> mActivities = new ArrayList<>(); protected PluginManager mPluginManager; public VAInstrumentation(PluginManager pluginManager, Instrumentation base) { this.mPluginManager = pluginManager; this.mBase = base; } @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())); // resolve intent with Stub Activity if needed this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent); } } @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) { 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 { Context context = this.mPluginManager.getHostContext(); debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; } catch (Throwable ex) { } 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 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); 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) { Log.w(TAG, e); } } } @Override public boolean handleMessage(Message msg) { if (msg.what == LAUNCH_ACTIVITY) { // ActivityClientRecord r Object r = msg.obj; try { 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); if (theme != 0) { Log.i(TAG, "resolve theme, current theme:" + activityInfo.theme + " after :0x" + Integer.toHexString(theme)); activityInfo.theme = theme; } } } catch (Exception e) { Log.w(TAG, e); } } return false; } @Override public Context getContext() { return mBase.getContext(); } @Override public Context getTargetContext() { return mBase.getTargetContext(); } @Override 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); } } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/DexUtil.java ================================================ /* * 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.internal.utils; import android.app.ActivityThread; import android.content.Context; import android.os.Build; import com.didi.virtualapk.internal.Constants; import com.didi.virtualapk.utils.Reflector; import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.List; import dalvik.system.DexClassLoader; public class DexUtil { private static boolean sHasInsertedNativeLibrary = false; 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(baseClassLoader); Reflector.with(pathList).field("dexElements").set(allDexElements); insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir); } private static Object getDexElements(Object pathList) throws Exception { return Reflector.with(pathList).field("dexElements").get(); } 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 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, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception { if (sHasInsertedNativeLibrary) { return; } sHasInsertedNativeLibrary = true; Context context = ActivityThread.currentApplication(); Object basePathList = getPathList(baseClassLoader); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { Reflector reflector = Reflector.with(basePathList); List nativeLibraryDirectories = reflector.field("nativeLibraryDirectories").get(); nativeLibraryDirectories.add(nativeLibsDir); Object baseNativeLibraryPathElements = reflector.field("nativeLibraryPathElements").get(); final int baseArrayLength = Array.getLength(baseNativeLibraryPathElements); Object newPathList = getPathList(dexClassLoader); Object newNativeLibraryPathElements = reflector.get(newPathList); Class elementClass = newNativeLibraryPathElements.getClass().getComponentType(); Object allNativeLibraryPathElements = Array.newInstance(elementClass, baseArrayLength + 1); System.arraycopy(baseNativeLibraryPathElements, 0, allNativeLibraryPathElements, 0, baseArrayLength); Field soPathField; if (Build.VERSION.SDK_INT >= 26) { soPathField = elementClass.getDeclaredField("path"); } else { soPathField = elementClass.getDeclaredField("dir"); } soPathField.setAccessible(true); final int newArrayLength = Array.getLength(newNativeLibraryPathElements); for (int i = 0; i < newArrayLength; i++) { Object element = Array.get(newNativeLibraryPathElements, i); String dir = ((File)soPathField.get(element)).getAbsolutePath(); if (dir.contains(Constants.NATIVE_DIR)) { Array.set(allNativeLibraryPathElements, baseArrayLength, element); break; } } reflector.set(allNativeLibraryPathElements); } else { 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] = nativeLibsDir; reflector.set(newNativeLibraryDirectories); } } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/PackageParserCompat.java ================================================ /* * 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.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; /** * @author johnsonlee */ public final class PackageParserCompat { 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 Throwable { PackageParser parser = new PackageParser(); PackageParser.Package pkg = parser.parsePackage(apk, 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 Throwable { PackageParser parser = new PackageParser(); PackageParser.Package pkg = parser.parsePackage(apk, flags); parser.collectCertificates(pkg, flags); return pkg; } } private static final class PackageParserLegacy { 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); Reflector.with(parser) .method("collectCertificates", PackageParser.Package.class, int.class) .call(pkg, flags); return pkg; } } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/PluginUtil.java ================================================ /* * 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.internal.utils; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.text.TextUtils; 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; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Created by renyugang on 16/8/15. */ public class PluginUtil { public static final String TAG = Constants.TAG_PREFIX + "NativeLib"; public static ComponentName getComponent(Intent intent) { 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); } public static int getTheme(Context context, Intent intent) { return PluginUtil.getTheme(context, PluginUtil.getComponent(intent)); } public static int getTheme(Context context, ComponentName component) { LoadedPlugin loadedPlugin = PluginManager.getInstance(context).getLoadedPlugin(component); if (null == loadedPlugin) { return 0; } ActivityInfo info = loadedPlugin.getActivityInfo(component); if (null == info) { return 0; } if (0 != info.theme) { return info.theme; } ApplicationInfo appInfo = info.applicationInfo; if (null != appInfo && appInfo.theme != 0) { return appInfo.theme; } return selectDefaultTheme(0, Build.VERSION.SDK_INT); } public static int selectDefaultTheme(final int curTheme, final int targetSdkVersion) { return selectSystemTheme(curTheme, targetSdkVersion, android.R.style.Theme, android.R.style.Theme_Holo, android.R.style.Theme_DeviceDefault, android.R.style.Theme_DeviceDefault_Light_DarkActionBar); } 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; } if (targetSdkVersion < 11 /* Build.VERSION_CODES.HONEYCOMB */) { return orig; } if (targetSdkVersion < 14 /* Build.VERSION_CODES.ICE_CREAM_SANDWICH */) { return holo; } if (targetSdkVersion < 24 /* Build.VERSION_CODES.N */) { return dark; } return deviceDefault; } public static void hookActivityResources(Activity activity, String packageName) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isVivo(activity.getResources())) { // for 5.0+ vivo return; } // designed for 5.0 - only, but some bad phones not work, eg:letv try { Context base = activity.getBaseContext(); final LoadedPlugin plugin = PluginManager.getInstance(activity).getLoadedPlugin(packageName); final Resources resources = plugin.getResources(); if (resources != null) { Reflector.with(base).field("mResources").set(resources); // copy theme Resources.Theme theme = resources.newTheme(); theme.setTo(activity.getTheme()); Reflector reflector = Reflector.with(activity); int themeResource = reflector.field("mThemeResource").get(); theme.applyStyle(themeResource, true); reflector.field("mTheme").set(theme); reflector.field("mResources").set(resources); } } catch (Exception e) { Log.w(Constants.TAG, e); } } public static final boolean isLocalService(final ServiceInfo serviceInfo) { return TextUtils.isEmpty(serviceInfo.processName) || serviceInfo.applicationInfo.packageName.equals(serviceInfo.processName); } public static boolean isVivo(Resources resources) { return resources.getClass().getName().equals("android.content.res.VivoResources"); } 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 { 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 { return (IBinder) Reflector.QuietReflector.with(bundle) .method("getIBinder", String.class).call(key); } } 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 { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { for (String cpuArch : Build.SUPPORTED_ABIS) { if (findAndCopyNativeLib(zipfile, context, cpuArch, packageInfo, nativeLibDir)) { return; } } } else { if (findAndCopyNativeLib(zipfile, context, Build.CPU_ABI, packageInfo, nativeLibDir)) { return; } } 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; } } 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(byte[] buffer, InputStream input, OutputStream output) throws IOException { BufferedInputStream bufferedInput = new BufferedInputStream(input); BufferedOutputStream bufferedOutput = new BufferedOutputStream(output); int count; while ((count = bufferedInput.read(buffer)) > 0) { bufferedOutput.write(buffer, 0, count); } bufferedOutput.flush(); bufferedOutput.close(); output.close(); bufferedInput.close(); input.close(); } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/Settings.java ================================================ /* * 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.internal.utils; import android.content.Context; import android.content.SharedPreferences; /** * Created by renyugang on 16/8/23. */ public class Settings { private static final String FILE_NAME = "VirtualAPK_Settings"; public static void setSoVersion(Context context, String name, int version) { SharedPreferences preferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putInt(name, version); editor.commit(); } public static int getSoVersion(Context context, String name) { SharedPreferences preferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); return preferences.getInt(name, 0); } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/utils/Reflector.java ================================================ 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; } } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/utils/RunUtil.java ================================================ /* * 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.ActivityManager; import android.content.Context; import android.os.AsyncTask; import android.os.Handler; 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; /** * Created by renyugang on 16/11/10. */ public class RunUtil { private static final int MESSAGE_RUN_ON_UITHREAD = 0x1; private static Handler sHandler; /** * execute a runnable on ui thread, then return immediately. see also {@link #runOnUiThread(Runnable, boolean)} * @param runnable the runnable prepared to run */ public static void runOnUiThread(Runnable runnable) { runOnUiThread(runnable, false); } /** * execute a runnable on ui thread * @param runnable the runnable prepared to run * @param waitUtilDone if set true, the caller thread will wait until the specific runnable finished. */ public static void runOnUiThread(Runnable runnable, boolean waitUtilDone) { if (Thread.currentThread() == Looper.getMainLooper().getThread()) { runnable.run(); return; } CountDownLatch countDownLatch = null; if (waitUtilDone) { countDownLatch = new CountDownLatch(1); } Pair pair = new Pair<>(runnable, countDownLatch); getHandler().obtainMessage(MESSAGE_RUN_ON_UITHREAD, pair).sendToTarget(); if (waitUtilDone) { try { countDownLatch.await(); } catch (InterruptedException e) { Log.w(Constants.TAG, e); } } } public static Executor getThreadPool() { return AsyncTask.THREAD_POOL_EXECUTOR; } 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; } public static boolean isMainProcess(Context context) { String processName = getProcessNameByPid(context, Process.myPid()); if (context.getPackageName().equals(processName)) { return true; } return false; } private static Handler getHandler() { synchronized (RunUtil.class) { if (sHandler == null) { sHandler = new InternalHandler(); } return sHandler; } } private static class InternalHandler extends Handler { public InternalHandler() { super(Looper.getMainLooper()); } @Override public void handleMessage(Message msg) { if (msg.what == MESSAGE_RUN_ON_UITHREAD) { Pair pair = (Pair) msg.obj; try { Runnable runnable = pair.first; runnable.run(); } finally { if (pair.second != null) { pair.second.countDown(); } } } } } } ================================================ FILE: CoreLibrary/src/main/java/com/didi/virtualapk/utils/ZipVerifyUtil.java ================================================ /* * 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.content.Context; import android.util.Base64; import android.util.Log; import com.didi.virtualapk.internal.Constants; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.util.Enumeration; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Created by renyugang on 16/5/12. */ /** * verify signature of zip file
* usage: boolean valid = ZipVerifyUtil.verifyZip(context, zipPath) */ 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(cerName); Certificate certificate = certificateFactory.generateCertificate(in); in.close(); return verifyZip(zipPath, certificate); } catch (IOException | CertificateException e) { Log.w(Constants.TAG, e); return false; } } public static boolean verifyZip(String zipPath, Certificate remoteCertificate) { try { String certPath = checkZipFileForCertificate(zipPath); Certificate certificate = getCertificateFromZip(zipPath, certPath); remoteCertificate.verify(certificate.getPublicKey()); return true; } catch (Exception e) { Log.w(Constants.TAG, e); return false; } } public static Certificate getCertificateFromZip(String zipPath, String certPath) throws Exception { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); ZipFile zip = new ZipFile(new File(zipPath)); InputStream in = zip.getInputStream(zip.getEntry(certPath)); Certificate certificate = certificateFactory.generateCertificates(in).iterator().next(); in.close(); zip.close(); return certificate; } public static String checkZipFileForCertificate(String zipPath) throws IOException { String certPath = ""; ZipFile zip = new ZipFile(new File(zipPath)); // This call will throw a java.lang.SecurityException if someone has tampered // with the signature of _any_ element of the JAR file. // Alas, it will proceed without a problem if the JAR file is not signed at all InputStream is = zip.getInputStream(zip.getEntry("META-INF/MANIFEST.MF")); Manifest man = new Manifest(is); is.close(); Set signed = new HashSet(); for (Map.Entry entry : man.getEntries().entrySet()) { for (Object attrkey : entry.getValue().keySet()) { if (attrkey instanceof Attributes.Name && ((Attributes.Name) attrkey).toString().indexOf("-Digest") != -1) signed.add(entry.getKey()); } } Set entries = new HashSet(); for (Enumeration entry = (Enumeration) zip.entries(); entry.hasMoreElements();) { ZipEntry ze = entry.nextElement(); if (!ze.isDirectory()) { String name = ze.getName(); if (!name.startsWith("META-INF/")) { entries.add(name); } else if (name.endsWith(".RSA") || name.endsWith(".DSA")) { certPath = name; } } } // contains all entries in the Manifest that are not signed. // Ususally, this contains: // * MANIFEST.MF itself // * *.SF files containing the signature of MANIFEST.MF // * *.DSA files containing public keys of the signer Set unsigned = new HashSet(entries); unsigned.removeAll(signed); // contains all the entries with a signature that are not present in the JAR Set missing = new HashSet(signed); missing.removeAll(entries); zip.close(); if (unsigned.isEmpty() && missing.isEmpty()) { return certPath; } return null; } public static Certificate getCertificate(String certificatePath) throws Exception { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); FileInputStream in = new FileInputStream(certificatePath); Certificate certificate = certificateFactory.generateCertificate(in); in.close(); return certificate; } private static byte[] decode(String base64) throws Exception { return Base64.decode(base64.getBytes(), Base64.DEFAULT); } } ================================================ FILE: CoreLibrary/src/main/res/values/strings.xml ================================================ CoreLibrary ================================================ FILE: CoreLibrary/src/test/java/com/didi/virtualapk/core/ExampleUnitTest.java ================================================ package com.didi.virtualapk.core; import org.junit.Test; import static org.junit.Assert.*; /** * To work on unit tests, switch the Test Artifact in the Build Variants view. */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: CoreLibrary/upload.gradle ================================================ apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'com.jfrog.bintray' def GROUP_ID = 'com.didi.virtualapk' def ARTIFACT_ID = 'core' def siteUrl = 'https://github.com/didi/VirtualAPK' // 项目的主页 def gitUrl = 'https://github.com/didi/VirtualAPK' // Git仓库的url group = GROUP_ID archivesBaseName = 'core' version = "0.9.8" install { repositories.mavenInstaller { // This generates POM.xml with proper parameters pom { artifactId = ARTIFACT_ID project { packaging 'aar' // Add your description here name 'A powerful but lightweight plugin framework for Android' //项目描述 url siteUrl // Set your license licenses { license { name 'Apache License 2.0' url 'http://www.apache.org/licenses/LICENSE-2.0' } } developers { developer { id 'singwhatiwanna' //填写的一些基本信息 name 'DiDi' email 'singwhatiwanna@gmail.com' } } scm { connection gitUrl developerConnection gitUrl url siteUrl } } } } } task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } task javadoc(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } artifacts { //archives javadocJar archives sourcesJar } Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) bintray { user = properties.getProperty("bintray.user") key = properties.getProperty("bintray.apikey") configurations = ['archives'] pkg { repo = "maven" name = "${GROUP_ID}:${ARTIFACT_ID}" //发布到JCenter上的项目名字 websiteUrl = siteUrl vcsUrl = gitUrl licenses = ["Apache-2.0"] publish = true } } ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 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. ================================================ FILE: PluginDemo/app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion VERSION_COMPILE_SDK buildToolsVersion VERSION_BUILD_TOOLS defaultConfig { applicationId "com.didi.virtualapk.demo" 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 shrinkResources false } release { minifyEnabled true shrinkResources true signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } 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. implementation 'com.android.support:appcompat-v7:23.4.0' // implementation 'com.didi.virtualapk:core:0.9.8' } apply plugin: 'com.didi.virtualapk.plugin' virtualApk { packageId = 0x6f // the package id of Resources. targetHost = '../../VirtualAPK/app' // the path of application module in host project. applyHostMapping = true //optional, default value: true. } ================================================ FILE: PluginDemo/app/gradle.properties ================================================ #android.enableD8=false android.useDexArchive=false ================================================ FILE: PluginDemo/app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/didi/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -dontpreverify -verbose -dontoptimize -dontshrink -allowaccessmodification -keepattributes *Annotation* -keepattributes Exceptions -keepattributes JavascriptInterface -keepattributes LineNumberTable -keepattributes Signature -keepattributes SourceFile -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public class com.android.vending.licensing.ILicensingService -dontnote com.android.vending.licensing.ILicensingService -keepclasseswithmembernames class * { native ; } -keepclassmembers enum * { public static ; public static **[] values(); public static ** valueOf(java.lang.String); } -keep class com.google.**{*;} -keep class sun.misc.Unsafe { *; } -keepnames class * implements java.io.Serializable -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; !static !transient ; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } -keep class * extends java.util.ListResourceBundle { protected Object[][] getContents(); } -keepclassmembers class * extends android.view.View { public (android.content.Context); public (android.content.Context); public (android.content.Context, android.util.AttributeSet); public (android.content.Context, android.util.AttributeSet, int); void set*(***); *** get*(); } -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } -keep class **.R$* { public static ; } -keep @android.support.annotation.Keep class * -keep @android.support.annotation.Keep interface * -keepclassmembers class * { @android.support.annotation.Keep ; } -keepclassmembers class * { @android.support.annotation.Keep ; } -keepclassmembers interface * { @android.support.annotation.Keep ; } -keepclassmembers class * { @android.webkit.JavascriptInterface ; } -assumenosideeffects class android.util.Log { public static boolean isLoggable(java.lang.String,int); public static int v(...); public static int i(...); public static int w(...); public static int d(...); public static int e(...); } ####################################################################### -keep class com.didi.virtualapk.internal.VAInstrumentation { *; } -keep class com.didi.virtualapk.internal.PluginContentResolver { *; } -dontwarn com.didi.virtualapk.** -dontwarn android.** -keep class android.** { *; } ================================================ FILE: PluginDemo/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/aidl/Book.aidl ================================================ package com.didi.virtualapk.demo.aidl; parcelable Book; ================================================ FILE: PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/aidl/IBookManager.aidl ================================================ package com.didi.virtualapk.demo.aidl; import com.didi.virtualapk.demo.aidl.Book; import com.didi.virtualapk.demo.aidl.IOnNewBookArrivedListener; interface IBookManager { List getBookList(); void addBook(in Book book); void registerListener(IOnNewBookArrivedListener listener); void unregisterListener(IOnNewBookArrivedListener listener); } ================================================ FILE: PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/aidl/IOnNewBookArrivedListener.aidl ================================================ package com.didi.virtualapk.demo.aidl; import com.didi.virtualapk.demo.aidl.Book; interface IOnNewBookArrivedListener { void onNewBookArrived(in Book newBook); } ================================================ FILE: PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/binderpool/IBinderPool.aidl ================================================ package com.didi.virtualapk.demo.binderpool; interface IBinderPool { /** * @param binderCode, the unique token of specific Binder
* @return specific Binder who's token is binderCode. */ IBinder queryBinder(int binderCode); } ================================================ FILE: PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/binderpool/ICompute.aidl ================================================ package com.didi.virtualapk.demo.binderpool; interface ICompute { int add(int a, int b); } ================================================ FILE: PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/binderpool/ISecurityCenter.aidl ================================================ package com.didi.virtualapk.demo.binderpool; interface ISecurityCenter { String encrypt(String content); String decrypt(String password); } ================================================ FILE: PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/manualbinder/Book.aidl ================================================ package com.didi.virtualapk.demo.manualbinder; parcelable Book; ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/MainActivity.java ================================================ package com.didi.virtualapk.demo; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import com.didi.virtualapk.demo.R; import com.didi.virtualapk.demo.aidl.Book; import com.didi.virtualapk.demo.manager.UserManager; import com.didi.virtualapk.demo.model.User; import com.didi.virtualapk.demo.utils.MyConstants; import com.didi.virtualapk.demo.utils.MyUtils; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; public class MainActivity extends Activity { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); UserManager.sUserId = 2; findViewById(R.id.button1).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setClass(MainActivity.this, SecondActivity.class); User user = new User(0, "jake", true); user.book = new Book(); intent.putExtra("extra_user", (Serializable) user); startActivity(intent); } }); } @Override protected void onResume() { Log.d(TAG, "UserManage.sUserId=" + UserManager.sUserId); persistToFile(); super.onResume(); } private void persistToFile() { new Thread(new Runnable() { @Override public void run() { User user = new User(1, "hello world", false); File dir = new File(MyConstants.CHAPTER_2_PATH); if (!dir.exists()) { dir.mkdirs(); } File cachedFile = new File(MyConstants.CACHE_FILE_PATH); ObjectOutputStream objectOutputStream = null; try { objectOutputStream = new ObjectOutputStream( new FileOutputStream(cachedFile)); objectOutputStream.writeObject(user); Log.d(TAG, "persist user:" + user); } catch (IOException e) { e.printStackTrace(); } finally { MyUtils.close(objectOutputStream); } } }).start(); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/MyApplication.java ================================================ package com.didi.virtualapk.demo; import com.didi.virtualapk.demo.utils.MyUtils; import android.app.Application; import android.os.Process; import android.util.Log; public class MyApplication extends Application { private static final String TAG = "MyApplication"; @Override public void onCreate() { super.onCreate(); String processName = MyUtils.getProcessName(getApplicationContext(), Process.myPid()); Log.d(TAG, "application start, process name:" + processName); new Thread(new Runnable() { @Override public void run() { doWorkInBackground(); } }).start(); } private void doWorkInBackground() { // init binder pool //BinderPool.getInsance(getApplicationContext()); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/SecondActivity.java ================================================ package com.didi.virtualapk.demo; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; import com.didi.virtualapk.demo.R; import com.didi.virtualapk.demo.model.User; import com.didi.virtualapk.demo.utils.MyConstants; import com.didi.virtualapk.demo.utils.MyUtils; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; public class SecondActivity extends Activity { private static final String TAG = "SecondActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); findViewById(R.id.button1).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setClass(SecondActivity.this, ThirdActivity.class); intent.putExtra("time", System.currentTimeMillis()); startActivity(intent); } }); Log.d(TAG, "onCreate"); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.d(TAG, "onNewIntent"); } @Override protected void onResume() { super.onResume(); //User user = (User) getIntent().getSerializableExtra("extra_user"); //Log.d(TAG, "user:" + user.toString()); // Log.d(TAG, "UserManage.sUserId=" + UserManager.sUserId); //recoverFromFile(); } private void recoverFromFile() { new Thread(new Runnable() { @Override public void run() { User user = null; File cachedFile = new File(MyConstants.CACHE_FILE_PATH); if (cachedFile.exists()) { ObjectInputStream objectInputStream = null; try { objectInputStream = new ObjectInputStream( new FileInputStream(cachedFile)); user = (User) objectInputStream.readObject(); Log.d(TAG, "recover user:" + user); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { MyUtils.close(objectInputStream); } } } }).start(); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/ThirdActivity.java ================================================ package com.didi.virtualapk.demo; import com.didi.virtualapk.demo.R; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; public class ThirdActivity extends Activity { private static final String TAG = "ThirdActivity"; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity_third); findViewById(R.id.button1).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setClass(ThirdActivity.this, SecondActivity.class); intent.putExtra("time", System.currentTimeMillis()); startActivity(intent); } }); Log.d(TAG, "onCreate"); } @Override protected void onStart() { // TODO Auto-generated method stub super.onStart(); Log.d(TAG, "onStart"); } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); Log.d(TAG, "onResume"); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Log.d(TAG, "onSaveInstanceState"); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); Log.d(TAG, "onRestoreInstanceState"); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/aidl/Book.java ================================================ package com.didi.virtualapk.demo.aidl; import android.os.Parcel; import android.os.Parcelable; public class Book implements Parcelable { public int bookId; public String bookName; public Book() { } public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeInt(bookId); out.writeString(bookName); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public Book createFromParcel(Parcel in) { return new Book(in); } public Book[] newArray(int size) { return new Book[size]; } }; private Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); } @Override public String toString() { return String.format("[bookId:%s, bookName:%s]", bookId, bookName); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/aidl/BookManagerActivity.java ================================================ package com.didi.virtualapk.demo.aidl; import java.util.List; import com.didi.virtualapk.demo.R; import com.didi.virtualapk.demo.socket.TCPClientActivity; import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.EditText; public class BookManagerActivity extends AppCompatActivity { private static final String TAG = "BookManagerActivity"; private static final int MESSAGE_NEW_BOOK_ARRIVED = 1; private IBookManager mRemoteBookManager; private EditText mEditText; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_NEW_BOOK_ARRIVED: Log.d(TAG, "receive new book :" + msg.obj); mEditText.append(msg.obj.toString() + "\n"); break; default: super.handleMessage(msg); } } }; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName()); if (mRemoteBookManager == null) return; mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mRemoteBookManager = null; // TODO:这里重新绑定远程Service } }; private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); mRemoteBookManager = bookManager; try { mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0); List list = bookManager.getBookList(); Log.i(TAG, "query book list, list type:" + list.getClass().getCanonicalName()); Log.i(TAG, "query book list:" + list.toString()); Book newBook = new Book(3, "Android进阶"); bookManager.addBook(newBook); Log.i(TAG, "add book:" + newBook); List newList = bookManager.getBookList(); Log.i(TAG, "query book list:" + newList.toString()); bookManager.registerListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } public void onServiceDisconnected(ComponentName className) { mRemoteBookManager = null; Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName()); } }; private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book newBook) throws RemoteException { mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook) .sendToTarget(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_book_manager); mEditText = (EditText)findViewById(R.id.editText); Intent intent = new Intent(this, BookManagerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } public void onButton1Click(View view) { new Thread(new Runnable() { @Override public void run() { if (mRemoteBookManager != null) { try { List newList = mRemoteBookManager.getBookList(); } catch (RemoteException e) { e.printStackTrace(); } } } }).start(); Intent intent = new Intent(this, TCPClientActivity.class); startActivity(intent); } @Override protected void onDestroy() { if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) { try { Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener); mRemoteBookManager .unregisterListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); super.onDestroy(); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/aidl/BookManagerService.java ================================================ package com.didi.virtualapk.demo.aidl; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; public class BookManagerService extends Service { private static final String TAG = "BMS"; private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false); private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList(); // private CopyOnWriteArrayList mListenerList = // new CopyOnWriteArrayList(); private RemoteCallbackList mListenerList = new RemoteCallbackList(); private Binder mBinder = new IBookManager.Stub() { @Override public List getBookList() throws RemoteException { // SystemClock.sleep(5000); return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"); Log.d(TAG, "check=" + check); if (check == PackageManager.PERMISSION_DENIED) { //return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid( getCallingUid()); if (packages != null && packages.length > 0) { packageName = packages[0]; } Log.d(TAG, "onTransact: " + packageName); if (!packageName.startsWith("com.ryg")) { //return false; } return super.onTransact(code, data, reply, flags); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener); final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "registerListener, current size:" + N); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException { boolean success = mListenerList.unregister(listener); if (success) { Log.d(TAG, "unregister success."); } else { Log.d(TAG, "not found, can not unregister."); } final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "unregisterListener, current size:" + N); }; }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "Ios")); new Thread(new ServiceWorker()).start(); } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onDestroy() { mIsServiceDestoryed.set(true); super.onDestroy(); } private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); final int N = mListenerList.beginBroadcast(); for (int i = 0; i < N; i++) { IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i); if (l != null) { try { l.onNewBookArrived(book); } catch (RemoteException e) { e.printStackTrace(); } } } mListenerList.finishBroadcast(); } private class ServiceWorker implements Runnable { @Override public void run() { // do background processing here..... while (!mIsServiceDestoryed.get()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId, "new book#" + bookId); try { onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } } } } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/binderpool/BinderPool.java ================================================ package com.didi.virtualapk.demo.binderpool; import java.util.concurrent.CountDownLatch; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; public class BinderPool { private static final String TAG = "BinderPool"; public static final int BINDER_NONE = -1; public static final int BINDER_COMPUTE = 0; public static final int BINDER_SECURITY_CENTER = 1; private Context mContext; private IBinderPool mBinderPool; private static volatile BinderPool sInstance; private CountDownLatch mConnectBinderPoolCountDownLatch; private BinderPool(Context context) { mContext = context.getApplicationContext(); connectBinderPoolService(); } public static BinderPool getInsance(Context context) { if (sInstance == null) { synchronized (BinderPool.class) { if (sInstance == null) { sInstance = new BinderPool(context); } } } return sInstance; } private synchronized void connectBinderPoolService() { mConnectBinderPoolCountDownLatch = new CountDownLatch(1); Intent service = new Intent(mContext, BinderPoolService.class); mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE); try { mConnectBinderPoolCountDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } /** * query binder by binderCode from binder pool * * @param binderCode * the unique token of binder * @return binder who's token is binderCode
* return null when not found or BinderPoolService died. */ public IBinder queryBinder(int binderCode) { IBinder binder = null; try { if (mBinderPool != null) { binder = mBinderPool.queryBinder(binderCode); } } catch (RemoteException e) { e.printStackTrace(); } return binder; } private ServiceConnection mBinderPoolConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { // ignored. } @Override public void onServiceConnected(ComponentName name, IBinder service) { mBinderPool = IBinderPool.Stub.asInterface(service); try { mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0); } catch (RemoteException e) { e.printStackTrace(); } mConnectBinderPoolCountDownLatch.countDown(); } }; private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.w(TAG, "binder died."); mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0); mBinderPool = null; connectBinderPoolService(); } }; public static class BinderPoolImpl extends IBinderPool.Stub { public BinderPoolImpl() { super(); } @Override public IBinder queryBinder(int binderCode) throws RemoteException { IBinder binder = null; switch (binderCode) { case BINDER_SECURITY_CENTER: { binder = new SecurityCenterImpl(); break; } case BINDER_COMPUTE: { binder = new ComputeImpl(); break; } default: break; } return binder; } } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/binderpool/BinderPoolActivity.java ================================================ package com.didi.virtualapk.demo.binderpool; import com.didi.virtualapk.demo.R; import android.app.Activity; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; public class BinderPoolActivity extends Activity { private static final String TAG = "BinderPoolActivity"; private ISecurityCenter mSecurityCenter; private ICompute mCompute; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_binder_pool); new Thread(new Runnable() { @Override public void run() { doWork(); } }).start(); } private void doWork() { BinderPool binderPool = BinderPool.getInsance(BinderPoolActivity.this); IBinder securityBinder = binderPool .queryBinder(BinderPool.BINDER_SECURITY_CENTER); ; mSecurityCenter = (ISecurityCenter) SecurityCenterImpl .asInterface(securityBinder); Log.d(TAG, "visit ISecurityCenter"); String msg = "helloworld-安卓"; System.out.println("content:" + msg); try { String password = mSecurityCenter.encrypt(msg); System.out.println("encrypt:" + password); System.out.println("decrypt:" + mSecurityCenter.decrypt(password)); } catch (RemoteException e) { e.printStackTrace(); } Log.d(TAG, "visit ICompute"); IBinder computeBinder = binderPool .queryBinder(BinderPool.BINDER_COMPUTE); ; mCompute = ComputeImpl.asInterface(computeBinder); try { System.out.println("3+5=" + mCompute.add(3, 5)); } catch (RemoteException e) { e.printStackTrace(); } } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/binderpool/BinderPoolService.java ================================================ package com.didi.virtualapk.demo.binderpool; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.util.Log; public class BinderPoolService extends Service { private static final String TAG = "BinderPoolService"; private Binder mBinderPool = new BinderPool.BinderPoolImpl(); @Override public void onCreate() { super.onCreate(); } @Override public IBinder onBind(Intent intent) { Log.d(TAG, "onBind"); return mBinderPool; } @Override public void onDestroy() { super.onDestroy(); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/binderpool/ComputeImpl.java ================================================ package com.didi.virtualapk.demo.binderpool; import android.os.RemoteException; public class ComputeImpl extends ICompute.Stub { @Override public int add(int a, int b) throws RemoteException { return a + b; } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/binderpool/SecurityCenterImpl.java ================================================ package com.didi.virtualapk.demo.binderpool; import android.os.RemoteException; public class SecurityCenterImpl extends ISecurityCenter.Stub { private static final char SECRET_CODE = '^'; @Override public String encrypt(String content) throws RemoteException { char[] chars = content.toCharArray(); for (int i = 0; i < chars.length; i++) { chars[i] ^= SECRET_CODE; } return new String(chars); } @Override public String decrypt(String password) throws RemoteException { return encrypt(password); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/manager/BookManager.java ================================================ package com.didi.virtualapk.demo.manager; import com.didi.virtualapk.demo.aidl.IBookManager; import android.os.IBinder; public class BookManager { private IBookManager mBookManager; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { if (mBookManager == null) return; mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mBookManager = null; // TODO:这里重新绑定远程Service } }; } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/manager/UserManager.java ================================================ package com.didi.virtualapk.demo.manager; public class UserManager { public static int sUserId = 1; } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/manualbinder/Book.java ================================================ package com.didi.virtualapk.demo.manualbinder; import android.os.Parcel; import android.os.Parcelable; public class Book implements Parcelable { public int bookId; public String bookName; public Book() { } public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeInt(bookId); out.writeString(bookName); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public Book createFromParcel(Parcel in) { return new Book(in); } public Book[] newArray(int size) { return new Book[size]; } }; private Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/manualbinder/BookManagerImpl.java ================================================ package com.didi.virtualapk.demo.manualbinder; import java.util.List; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; public class BookManagerImpl extends Binder implements IBookManager { /** Construct the stub at attach it to the interface. */ public BookManagerImpl() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an IBookManager interface, generating a proxy * if needed. */ public static IBookManager asInterface(IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof IBookManager))) { return ((IBookManager) iin); } return new BookManagerImpl.Proxy(obj); } @Override public IBinder asBinder() { return this; } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getBookList: { data.enforceInterface(DESCRIPTOR); List result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); Book arg0; if ((0 != data.readInt())) { arg0 = Book.CREATOR.createFromParcel(data); } else { arg0 = null; } this.addBook(arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } @Override public List getBookList() throws RemoteException { // TODO 待实现 return null; } @Override public void addBook(Book book) throws RemoteException { // TODO 待实现 } private static class Proxy implements IBookManager { private IBinder mRemote; Proxy(IBinder remote) { mRemote = remote; } @Override public IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public List getBookList() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); List result; try { data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(TRANSACTION_getBookList, data, reply, 0); reply.readException(); result = reply.createTypedArrayList(Book.CREATOR); } finally { reply.recycle(); data.recycle(); } return result; } @Override public void addBook(Book book) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(DESCRIPTOR); if ((book != null)) { data.writeInt(1); book.writeToParcel(data, 0); } else { data.writeInt(0); } mRemote.transact(TRANSACTION_addBook, data, reply, 0); reply.readException(); } finally { reply.recycle(); data.recycle(); } } } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/manualbinder/IBookManager.java ================================================ package com.didi.virtualapk.demo.manualbinder; import java.util.List; import android.os.IBinder; import android.os.IInterface; import android.os.RemoteException; public interface IBookManager extends IInterface { static final String DESCRIPTOR = "com.ryg.chapter_2.manualbinder.IBookManager"; static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1); public List getBookList() throws RemoteException; public void addBook(Book book) throws RemoteException; } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/messenger/MessengerActivity.java ================================================ package com.didi.virtualapk.demo.messenger; import com.didi.virtualapk.demo.R; import com.didi.virtualapk.demo.R.layout; import com.didi.virtualapk.demo.utils.MyConstants; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.util.Log; public class MessengerActivity extends Activity { private static final String TAG = "MessengerActivity"; private Messenger mService; private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler()); private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MyConstants.MSG_FROM_SERVICE: Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply")); break; default: super.handleMessage(msg); } } } private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { mService = new Messenger(service); Log.d(TAG, "bind service"); Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT); Bundle data = new Bundle(); data.putString("msg", "hello, this is client."); msg.setData(data); msg.replyTo = mGetReplyMessenger; try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } public void onServiceDisconnected(ComponentName className) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_messenger); Intent intent = new Intent("com.ryg.MessengerService.launch"); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/messenger/MessengerService.java ================================================ package com.didi.virtualapk.demo.messenger; import com.didi.virtualapk.demo.utils.MyConstants; import android.app.Service; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.util.Log; public class MessengerService extends Service { private static final String TAG = "MessengerService"; private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MyConstants.MSG_FROM_CLIENT: Log.i(TAG, "receive msg from Client:" + msg.getData().getString("msg")); Messenger client = msg.replyTo; Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE); Bundle bundle = new Bundle(); bundle.putString("reply", "嗯,你的消息我已经收到,稍后会回复你。"); relpyMessage.setData(bundle); try { client.send(relpyMessage); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } } } private final Messenger mMessenger = new Messenger(new MessengerHandler()); @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/model/User.java ================================================ package com.didi.virtualapk.demo.model; import java.io.Serializable; import com.didi.virtualapk.demo.aidl.Book; import android.os.Parcel; import android.os.Parcelable; public class User implements Parcelable, Serializable { private static final long serialVersionUID = 519067123721295773L; public int userId; public String userName; public boolean isMale; public Book book; public User() { } public User(int userId, String userName, boolean isMale) { this.userId = userId; this.userName = userName; this.isMale = isMale; } public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeInt(userId); out.writeString(userName); out.writeInt(isMale ? 1 : 0); out.writeParcelable(book, 0); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public User createFromParcel(Parcel in) { return new User(in); } public User[] newArray(int size) { return new User[size]; } }; private User(Parcel in) { userId = in.readInt(); userName = in.readString(); isMale = in.readInt() == 1; book = in .readParcelable(Thread.currentThread().getContextClassLoader()); } @Override public String toString() { return String.format( "User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}", userId, userName, isMale, book); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/provider/BookProvider.java ================================================ package com.didi.virtualapk.demo.provider; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.util.Log; public class BookProvider extends ContentProvider { private static final String TAG = "BookProvider"; public static final String AUTHORITY = "com.didi.virtualapk.demo.book.provider"; public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book"); public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user"); public static final int BOOK_URI_CODE = 0; public static final int USER_URI_CODE = 1; private static final UriMatcher sUriMatcher = new UriMatcher( UriMatcher.NO_MATCH); static { sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE); sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE); } private Context mContext; private SQLiteDatabase mDb; @Override public boolean onCreate() { Log.d(TAG, "onCreate, current thread:" + Thread.currentThread().getName()); mContext = getContext(); initProviderData(); return true; } private void initProviderData() { mDb = new DbOpenHelper(mContext).getWritableDatabase(); mDb.execSQL("delete from " + DbOpenHelper.BOOK_TABLE_NAME); mDb.execSQL("delete from " + DbOpenHelper.USER_TALBE_NAME); mDb.execSQL("insert into book values(3,'Android');"); mDb.execSQL("insert into book values(4,'Ios');"); mDb.execSQL("insert into book values(5,'Html5');"); mDb.execSQL("insert into user values(1,'jake',1);"); mDb.execSQL("insert into user values(2,'jasmine',0);"); } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.d(TAG, "query, current thread:" + Thread.currentThread().getName()); String table = getTableName(uri); if (table == null) { throw new IllegalArgumentException("Unsupported URI: " + uri); } return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null); } @Override public String getType(Uri uri) { Log.d(TAG, "getType"); return null; } @Override public Uri insert(Uri uri, ContentValues values) { Log.d(TAG, "insert"); String table = getTableName(uri); if (table == null) { throw new IllegalArgumentException("Unsupported URI: " + uri); } mDb.insert(table, null, values); mContext.getContentResolver().notifyChange(uri, null); return uri; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.d(TAG, "delete"); String table = getTableName(uri); if (table == null) { throw new IllegalArgumentException("Unsupported URI: " + uri); } int count = mDb.delete(table, selection, selectionArgs); if (count > 0) { getContext().getContentResolver().notifyChange(uri, null); } return count; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.d(TAG, "update"); String table = getTableName(uri); if (table == null) { throw new IllegalArgumentException("Unsupported URI: " + uri); } int row = mDb.update(table, values, selection, selectionArgs); if (row > 0) { getContext().getContentResolver().notifyChange(uri, null); } return row; } private String getTableName(Uri uri) { String tableName = null; switch (sUriMatcher.match(uri)) { case BOOK_URI_CODE: tableName = DbOpenHelper.BOOK_TABLE_NAME; break; case USER_URI_CODE: tableName = DbOpenHelper.USER_TALBE_NAME; break; default:break; } return tableName; } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/provider/DbOpenHelper.java ================================================ package com.didi.virtualapk.demo.provider; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class DbOpenHelper extends SQLiteOpenHelper { private static final String DB_NAME = "book_provider.db"; public static final String BOOK_TABLE_NAME = "book"; public static final String USER_TALBE_NAME = "user"; private static final int DB_VERSION = 3; private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)"; private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS " + USER_TALBE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT," + "sex INT)"; public DbOpenHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_BOOK_TABLE); db.execSQL(CREATE_USER_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO ignored } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/provider/ProviderActivity.java ================================================ package com.didi.virtualapk.demo.provider; import com.didi.virtualapk.demo.R; import com.didi.virtualapk.demo.aidl.Book; import com.didi.virtualapk.demo.model.User; import android.app.Activity; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.util.Log; public class ProviderActivity extends Activity { private static final String TAG = "ProviderActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_provider); // Uri uri = Uri.parse("content://com.ryg.chapter_2.book.provider"); // getContentResolver().query(uri, null, null, null, null); // getContentResolver().query(uri, null, null, null, null); // getContentResolver().query(uri, null, null, null, null); Uri bookUri = Uri.parse("content://com.ryg.chapter_2.book.provider/book"); ContentValues values = new ContentValues(); values.put("_id", 6); values.put("name", "程序设计的艺术"); getContentResolver().insert(bookUri, values); Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null); while (bookCursor.moveToNext()) { Book book = new Book(); book.bookId = bookCursor.getInt(0); book.bookName = bookCursor.getString(1); Log.d(TAG, "query book:" + book.toString()); } bookCursor.close(); Uri userUri = Uri.parse("content://com.ryg.chapter_2.book.provider/user"); Cursor userCursor = getContentResolver().query(userUri, new String[]{"_id", "name", "sex"}, null, null, null); while (userCursor.moveToNext()) { User user = new User(); user.userId = userCursor.getInt(0); user.userName = userCursor.getString(1); user.isMale = userCursor.getInt(2) == 1; Log.d(TAG, "query user:" + user.toString()); } userCursor.close(); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/socket/TCPClientActivity.java ================================================ package com.didi.virtualapk.demo.socket; import java.io.*; import java.net.Socket; import java.sql.Date; import java.text.SimpleDateFormat; import com.didi.virtualapk.demo.R; import com.didi.virtualapk.demo.utils.MyUtils; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.support.v4.app.FragmentActivity; import android.text.TextUtils; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class TCPClientActivity extends FragmentActivity implements OnClickListener { private static final int MESSAGE_RECEIVE_NEW_MSG = 1; private static final int MESSAGE_SOCKET_CONNECTED = 2; private Button mSendButton; private TextView mMessageTextView; private EditText mMessageEditText; private PrintWriter mPrintWriter; private Socket mClientSocket; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_RECEIVE_NEW_MSG: { mMessageTextView.setText(mMessageTextView.getText() + (String) msg.obj); break; } case MESSAGE_SOCKET_CONNECTED: { mSendButton.setEnabled(true); break; } default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_tcpclient); mMessageTextView = (TextView) findViewById(R.id.msg_container); mSendButton = (Button) findViewById(R.id.send); mSendButton.setOnClickListener(this); mMessageEditText = (EditText) findViewById(R.id.msg); Intent service = new Intent(this, TCPServerService.class); startService(service); new Thread() { @Override public void run() { connectTCPServer(); } }.start(); } @Override protected void onDestroy() { if (mClientSocket != null) { try { mClientSocket.shutdownInput(); mClientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } super.onDestroy(); } @Override public void onClick(View v) { if (v == mSendButton) { final String msg = mMessageEditText.getText().toString(); if (!TextUtils.isEmpty(msg) && mPrintWriter != null) { String time = formatDateTime(System.currentTimeMillis()); final String showedMsg = "self " + time + ":" + msg + "\n"; mMessageEditText.setText(""); mMessageTextView.setText(mMessageTextView.getText() + showedMsg); new Thread(new Runnable() { @Override public void run() { mPrintWriter.println(msg); } }).start(); } } } @SuppressLint("SimpleDateFormat") private String formatDateTime(long time) { return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time)); } private void connectTCPServer() { Socket socket = null; while (socket == null) { try { socket = new Socket("localhost", 8688); mClientSocket = socket; mPrintWriter = new PrintWriter(new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())), true); mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED); System.out.println("connect server success"); } catch (IOException e) { SystemClock.sleep(1000); System.out.println("connect tcp server failed, retry..."); } } try { // 接收服务器端的消息 BufferedReader br = new BufferedReader(new InputStreamReader( socket.getInputStream())); while (!TCPClientActivity.this.isFinishing()) { String msg = br.readLine(); System.out.println("receive :" + msg); if (msg != null) { String time = formatDateTime(System.currentTimeMillis()); final String showedMsg = "server " + time + ":" + msg + "\n"; mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg) .sendToTarget(); } } System.out.println("quit..."); MyUtils.close(mPrintWriter); MyUtils.close(br); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/socket/TCPServerService.java ================================================ package com.didi.virtualapk.demo.socket; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Random; import com.didi.virtualapk.demo.utils.MyUtils; import android.app.Service; import android.content.Intent; import android.os.IBinder; public class TCPServerService extends Service { private boolean mIsServiceDestoryed = false; private String[] mDefinedMessages = new String[] { "你好啊,哈哈", "请问你叫什么名字呀?", "今天北京天气不错啊,shy", "你知道吗?我可是可以和多个人同时聊天的哦", "给你讲个笑话吧:据说爱笑的人运气不会太差,不知道真假。" }; @Override public void onCreate() { new Thread(new TcpServer()).start(); super.onCreate(); } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { mIsServiceDestoryed = true; super.onDestroy(); } private class TcpServer implements Runnable { @SuppressWarnings("resource") @Override public void run() { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(8688); } catch (IOException e) { System.err.println("establish tcp server failed, port:8688"); e.printStackTrace(); return; } while (!mIsServiceDestoryed) { try { // 接受客户端请求 final Socket client = serverSocket.accept(); System.out.println("accept"); new Thread() { @Override public void run() { try { responseClient(client); } catch (IOException e) { e.printStackTrace(); } }; }.start(); } catch (IOException e) { e.printStackTrace(); } } } } private void responseClient(Socket client) throws IOException { // 用于接收客户端消息 BufferedReader in = new BufferedReader(new InputStreamReader( client.getInputStream())); // 用于向客户端发送消息 PrintWriter out = new PrintWriter(new BufferedWriter( new OutputStreamWriter(client.getOutputStream())), true); out.println("欢迎来到聊天室!"); while (!mIsServiceDestoryed) { String str = in.readLine(); System.out.println("msg from client:" + str); if (str == null) { break; } int i = new Random().nextInt(mDefinedMessages.length); String msg = mDefinedMessages[i]; out.println(msg); System.out.println("send :" + msg); } System.out.println("client quit."); // 关闭流 MyUtils.close(out); MyUtils.close(in); client.close(); } } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/utils/MyConstants.java ================================================ package com.didi.virtualapk.demo.utils; import android.os.Environment; public class MyConstants { public static final String CHAPTER_2_PATH = Environment .getExternalStorageDirectory().getPath() + "/singwhatiwanna/chapter_2/"; public static final String CACHE_FILE_PATH = CHAPTER_2_PATH + "usercache"; public static final int MSG_FROM_CLIENT = 0; public static final int MSG_FROM_SERVICE = 1; } ================================================ FILE: PluginDemo/app/src/main/java/com/didi/virtualapk/demo/utils/MyUtils.java ================================================ package com.didi.virtualapk.demo.utils; import java.io.Closeable; import java.io.IOException; import java.util.List; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.content.Context; public class MyUtils { public static String getProcessName(Context cxt, int pid) { ActivityManager am = (ActivityManager) cxt .getSystemService(Context.ACTIVITY_SERVICE); List runningApps = am.getRunningAppProcesses(); if (runningApps == null) { return null; } for (RunningAppProcessInfo procInfo : runningApps) { if (procInfo.pid == pid) { return procInfo.processName; } } return null; } public static void close(Closeable closeable) { try { if (closeable != null) { closeable.close(); } } catch (IOException e) { e.printStackTrace(); } } public static void executeInThread(Runnable runnable) { new Thread(runnable).start(); } } ================================================ FILE: PluginDemo/app/src/main/res/drawable/edit.xml ================================================ ================================================ FILE: PluginDemo/app/src/main/res/layout/activity_binder_pool.xml ================================================ ================================================ FILE: PluginDemo/app/src/main/res/layout/activity_book_manager.xml ================================================